【愚公系列】2024年02月 大数据教学课程 018-Hadoop辅助软件安装

在这里插入图片描述

🏆 作者简介,愚公搬代码

🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,51CTO博客专家等。

🏆《近期荣誉》:2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主等。

🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。

🏆🎉欢迎 👍点赞✍评论⭐收藏

文章目录

  • 🚀前言
  • 🚀一、Hadoop辅助软件安装
    • 🔎1.JDK
      • 🦋1.1 查看自带的openjdk并卸载
      • 🦋1.2 创建安装目录
      • 🦋1.3 上传并 解压
      • 🦋1.4 配置环境变量
    • 🔎2.MySQL
    • 🔎3.Zookeeper
      • 🦋3.1 Zookeeper 的概述
      • 🦋3.2 Zookeeper的特点
      • 🦋3.3 Zookeeper的应用场景
        • ☀️3.3.1 数据发布/订阅
        • ☀️3.3.2 命名服务
        • ☀️3.3.3 分布式协调/通知
        • ☀️3.3.4 分布式锁
        • ☀️3.3.5 分布式队列
      • 🦋3.4 Zookeeper的架构
      • 🦋3.5 Zookeeper的选举机制
        • ☀️3.5.1 服务器启动时期的Leader选举
        • ☀️3.5.2 服务器运行时期的Leader选举
      • 🦋3.6 Zookeeper安装
        • ☀️3.6.1 下载zookeeeper的压缩包,下载网址如下
        • ☀️3.6.2 解压
        • ☀️3.6.3 修改配置文件
        • ☀️3.6.4 添加myid配置
        • ☀️3.6.5 安装包分发并修改myid的值
        • ☀️3.6.6 三台机器启动zookeeper服务
      • 🦋3.7 Zookeeper的Shell 客户端操作
      • 🦋3.8 Znode 的特点
      • 🦋3.9 Znode 的类型
      • 🦋3.10 通知机制
      • 🦋3.11 会话
  • 🚀感谢:给读者的一封信

🚀前言

Hadoop是一个开源的分布式计算平台,用于处理大数据量和复杂的数据处理任务。在搭建和使用Hadoop集群时,通常需要一些辅助软件来支持其运行和管理。下面是一些常用的辅助软件:

  1. JDK(Java Development Kit):Hadoop是用Java语言编写的,所以需要安装JDK来支持Hadoop的运行。可以从Oracle官方网站下载适用于操作系统的JDK安装包。

  2. MySQL:Hadoop可以与关系型数据库进行交互,而MySQL是一个常用的关系型数据库管理系统。可以使用MySQL存储和管理Hadoop集群的元数据信息。

  3. Zookeeper:Zookeeper是一个开源的分布式协调服务,可以用于管理和维护Hadoop集群的配置信息、状态信息和命名服务。Hadoop中的一些组件如HBase、Storm等都依赖于Zookeeper来实现分布式协调和一致性。

安装和配置这些辅助软件时,需要按照各自的官方文档进行操作,并确保与Hadoop集群相互兼容,下面详解这三种软件安装。

🚀一、Hadoop辅助软件安装

本文以三台机器centos系统安装举例

🔎1.JDK

🦋1.1 查看自带的openjdk并卸载

rpm -qa | grep java
rpm -e java-1.6.0-openjdk-1.6.0.41-1.13.13.1.el6_8.x86_64 tzdata-java-2016j-1.el6.noarch java-1.7.0-openjdk-1.7.0.131-2.6.9.0.el6_8.x86_64 --nodeps

🦋1.2 创建安装目录

mkdir -p /export/softwares  #软件包存放目录
mkdir -p /export/servers    #安装目录

🦋1.3 上传并 解压

#上传jdk到/export/softwares路径下去,并解压
tar -zxvf jdk-8u141-linux-x64.tar.gz -C ../servers/

🦋1.4 配置环境变量

vim /etc/profile

添加如下内容

export JAVA_HOME=/export/servers/jdk1.8.0_141
export PATH=:$JAVA_HOME/bin:$PATH

修改完成之后记得 source /etc/profile生效

source /etc/profile

🔎2.MySQL

第一步:在线安装mysql相关的软件包

yum  install  mysql  mysql-server  mysql-devel

第二步:启动mysql的服务

/etc/init.d/mysqld start

第三步:通过mysql安装自带脚本进行设置

/usr/bin/mysql_secure_installation

第四步:进入mysql的客户端然后进行授权

grant all privileges on *.* to 'root'@'%' identified by '123456' with grant option;
flush privileges;

在这里插入图片描述

🔎3.Zookeeper

🦋3.1 Zookeeper 的概述

Zookeeper 是一个开源的分布式协调服务,它通过提供一组简单的原语来实现分布式应用程序和服务的协调和同步。

1、概述

Zookeeper 是一个分布式协调服务,它的设计目标是提供一个高性能、高可靠性的系统,可以处理大规模的分布式应用程序。

Zookeeper 提供了一个分布式的文件系统,称为 Znode,用于存储和管理数据。Znode 是一个类似于文件和目录的层次结构,每个 Znode 都可以存储数据和子节点。通过对 Znode 的操作,可以实现分布式的协调和同步。

Zookeeper 采用了主从模式的高可用性架构,其中有一个主节点负责处理客户端的请求,而其他节点作为备份节点。当主节点发生故障时,备份节点会自动选举出一个新的主节点,确保系统的可用性和数据的一致性。

2、组成

Zookeeper 由以下几个主要组件组成:

  1. 服务端:Zookeeper 的服务端运行在集群的每个节点上,负责处理客户端的请求,并管理和存储数据。服务端之间通过选举算法来选举出一个主节点,所有的写操作都需要由主节点来处理。

  2. 客户端:Zookeeper 客户端是分布式应用程序和服务与 Zookeeper 集群之间的接口。它可以连接到 Zookeeper 集群,并发送读写请求。客户端可以是任何编程语言实现的,Zookeeper 提供了多种编程语言的客户端库。

  3. Znode:Zookeeper 中的数据单元称为 Znode,它是一个类似于文件和目录的层次结构。每个 Znode 可以存储数据和子节点,Zookeeper 提供了一组 API 来操作 Znode。

  4. Watcher:Watcher 是一种机制,用于监视 Znode 的状态变化。当一个 Znode 发生变化时,Zookeeper 会发送通知给注册了 Watcher 的客户端,客户端可以根据通知来进行相应的处理。

Zookeeper 是一个由服务端、客户端、Znode 和 Watcher 组成的分布式协调服务。它通过提供分布式的文件系统和一组简单的原语,帮助开发人员构建可扩展、高可用性的分布式应用程序和服务。

在这里插入图片描述

🦋3.2 Zookeeper的特点

Zookeeper 本质上是一个分布式文件系统, 适合存放小文件,也可以理解为一个数据库

在这里插入图片描述

  • 在上图左侧, Zookeeper 中存储的其实是一个又一个 Znode, Znode 是 Zookeeper 中的节点
    • Znode 是有路径的, 例如 /data/host1, /data/host2, 这个路径也可以理解为是 Znode 的 Name
    • Znode 也可以携带数据, 例如说某个 Znode 的路径是 /data/host1, 其值是一个字符串 “192.168.0.1”
  • 正因为 Znode 的特性, 所以 Zookeeper 可以对外提供出一个类似于文件系统的试图, 可以通过操作文件系统的方式操作 Zookeeper
    • 使用路径获取 Znode

    • 获取 Znode 携带的数据

    • 修改 Znode 携带的数据

    • 删除 Znode

    • 添加 Znode

🦋3.3 Zookeeper的应用场景

☀️3.3.1 数据发布/订阅

Zookeeper中的数据发布/订阅模式可以通过监听节点来实现。当一个节点的数据发生变化时,订阅该节点的客户端将收到通知并可以获取最新的数据。以下是一个简单的例子:

假设有一个名为/config的Zookeeper节点,存储了某个应用程序的配置信息。多个应用程序实例需要获取这些配置信息,并在配置变化时进行相应的更新。

  1. 一个应用程序实例首先在Zookeeper上创建一个持久节点/config,并设置一个监听器。
zkClient.create("/config", "initial data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
zkClient.getData("/config", true, dataCallback, null);
  1. 其他应用程序实例也可以通过相同的路径来订阅这个节点,并设置监听器。
zkClient.getData("/config", true, dataCallback, null);
  1. 当/config节点的数据发生变化时,所有订阅该节点的应用程序实例都会收到通知。
Watcher dataCallback = new Watcher() {
    @Override
    public void process(WatchedEvent event) {
        if (event.getType() == Watcher.Event.EventType.NodeDataChanged) {
            // 节点数据发生变化,进行相应的更新操作
            byte[] data = zkClient.getData(event.getPath(), true, null);
            // 处理最新的数据
            System.out.println("New data: " + new String(data));
        }
    }
};

当/config节点的数据发生变化时,所有订阅该节点的应用程序实例都会收到通知,并可以获取最新的数据进行相应的处理。这样就实现了简单的数据发布/订阅机制。

☀️3.3.2 命名服务

命名服务是分步实现系统中较为常见的一类场景,分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等,通过命名服务,客户端可以根据指定名字来获取资源的实体,在分布式环境中,上层应用仅仅需要一个全局唯一的名字。Zookeeper可以实现一套分布式全局唯一ID的分配机制。

在这里插入图片描述

通过调用Zookeeper节点创建的API接口就可以创建一个顺序节点,并且在API返回值中会返回这个节点的完整名字,利用此特性,可以生成全局ID,其步骤如下

1. 客户端根据任务类型,在指定类型的任务下通过调用接口创建一个顺序节点,如”job-“。

2. 创建完成后,会返回一个完整的节点名,如”job-00000001″。

3. 客户端拼接type类型和返回值后,就可以作为全局唯一ID了,如”type2-job-00000001″。

☀️3.3.3 分布式协调/通知

Zookeeper中特有的Watcher注册于异步通知机制,能够很好地实现分布式环境下不同机器,甚至不同系统之间的协调与通知,从而实现对数据变更的实时处理。通常的做法是不同的客户端都对Zookeeper上的同一个数据节点进行Watcher注册,监听数据节点的变化(包括节点本身和子节点),若数据节点发生变化,那么所有订阅的客户端都能够接收到相应的Watcher通知,并作出相应处理。

在绝大多数分布式系统中,系统机器间的通信无外乎心跳检测、工作进度汇报和系统调度。

应用场景 描述
心跳检测 不同机器间需要检测到彼此是否在正常运行,可以使用Zookeeper实现机器间的心跳检测。不同的机器可以在Zookeeper的一个指定节点下创建临时子节点,根据子节点的存在与否判断机器是否存活。
工作进度汇报 任务被分发到不同机器后,机器通过在Zookeeper上创建临时子节点来将自己的任务执行进度实时汇报给分发系统。中心系统可以通过监视这些节点来获取任务的执行进度信息。
系统调度 分布式系统由控制台和多个客户端系统组成。控制台通过修改Zookeeper上的节点数据来发送指令信息给所有客户端。Zookeeper会以时间通知的形式将数据变更发送给订阅的客户端,实现系统调度。
☀️3.3.4 分布式锁

分布式锁用于控制分布式系统之间同步访问共享资源的一种方式,可以保证不同系统访问一个或一组资源时的一致性,主要分为排它锁和共享锁。

排它锁又称为写锁或独占锁,若事务T1对数据对象O1加上了排它锁,那么在整个加锁期间,只允许事务T1对O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作,直到T1释放了排它锁。

img

① 获取锁,在需要获取排它锁时,所有客户端通过调用接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。Zookeeper可以保证只有一个客户端能够创建成功,没有成功的客户端需要注册/exclusive_lock节点监听。

② 释放锁,当获取锁的客户端宕机或者正常完成业务逻辑都会导致临时节点的删除,此时,所有在/exclusive_lock节点上注册监听的客户端都会收到通知,可以重新发起分布式锁获取。

共享锁又称为读锁,若事务T1对数据对象O1加上共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁,直到该数据对象上的所有共享锁都被释放。在需要获取共享锁时,所有客户端都会到/shared_lock下面创建一个临时顺序节点

在这里插入图片描述

 

分布式锁是一种在分布式系统中实现互斥访问共享资源的机制,可以避免多个进程或线程同时访问某个共享资源,保证操作的原子性和一致性。

在ZooKeeper中,我们可以使用临时有序节点来实现分布式锁。具体流程如下:

  1. 每个客户端尝试在ZooKeeper中创建一个临时有序节点,例如 /lock/node-。

  2. 客户端调用ZooKeeper的getChildren()方法获取/lock路径下的所有子节点,并对节点进行排序。

  3. 客户端检查自己创建的节点是否是最小的节点。如果是,则表示客户端获取到了锁,可以执行临界区代码。如果不是,则监视前一个节点的删除事件。

  4. 如果前一个节点被删除,客户端会收到一个节点删除的通知,然后再次检查自己创建的节点是否是最小的节点。如果是,则表示客户端获取到了锁,可以执行临界区代码。如果不是,则继续等待。

  5. 当客户端执行完临界区代码后,会删除自己创建的节点,释放锁。

这样就实现了一个基于ZooKeeper的分布式锁机制。举个例子,假设有两个客户端A和B同时需要访问某个共享资源。他们会依次创建临时有序节点/lock/node-0000000001和/lock/node-0000000002。由于node-0000000001小于node-0000000002,客户端A获取到了锁,可以执行临界区代码。当A执行完后,会删除自己创建的节点,此时节点node-0000000002会被删除,客户端B会收到节点删除的通知,然后再次检查自己创建的节点是否是最小的节点,如果是则获取到锁,可以执行临界区代码。

通过ZooKeeper的分布式锁机制,保证了在分布式环境下共享资源的互斥访问。

☀️3.3.5 分布式队列

有一些时候,多个团队需要共同完成一个任务,比如,A团队将Hadoop集群计算的结果交给B团队继续计算,B完成了自己任务再交给C团队继续做。这就有点像业务系统的工作流一样,一环一环地传下 去.

分布式环境下,我们同样需要一个类似单进程队列的组件,用来实现跨进程、跨主机、跨网络的数据共享和数据传递,这就是我们的分布式队列。

🦋3.4 Zookeeper的架构

Zookeeper集群是一个基于主从架构的高可用集群

在这里插入图片描述

​ 每个服务器承担如下三种角色中的一种

  • Leader 一个Zookeeper集群同一时间只会有一个实际工作的Leader,它会发起并维护与各Follwer及Observer间的心跳。所有的写操作必须要通过Leader完成再由Leader将写操作广播给其它服务器。

  • Follower 一个Zookeeper集群可能同时存在多个Follower,它会响应Leader的心跳。Follower可直接处理并返回客户端的读请求,同时会将写请求转发给Leader处理,并且负责在Leader处理写请求时对请求进行投票。

  • Observer 角色与Follower类似,但是无投票权。

🦋3.5 Zookeeper的选举机制

Leader选举是保证分布式数据一致性的关键所在。当Zookeeper集群中的一台服务器出现以下两种情况之一时,需要进入Leader选举。

☀️3.5.1 服务器启动时期的Leader选举

若进行Leader选举,则至少需要两台机器,这里选取3台机器组成的服务器集群为例。在集群初始化阶段,当有一台服务器Server1启动时,其单独无法进行和完成Leader选举,当第二台服务器Server2启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,于是进入Leader选举过程。选举过程如下

(1) 每个Server发出一个投票。由于是初始情况,Server1和Server2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myid和ZXID,使用(myid, ZXID)来表示,此时Server1的投票为(1, 0),Server2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。

(2) 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。

(3) 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下

· 优先检查ZXID。ZXID比较大的服务器优先作为Leader。

· 如果ZXID相同,那么就比较myid。myid较大的服务器作为Leader服务器。

对于Server1而言,它的投票是(1, 0),接收Server2的投票为(2, 0),首先会比较两者的ZXID,均为0,再比较myid,此时Server2的myid最大,于是更新自己的投票为(2, 0),然后重新投票,对于Server2而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。

(4) 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于Server1、Server2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了Leader。

(5) 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。

☀️3.5.2 服务器运行时期的Leader选举

在Zookeeper运行期间,Leader与非Leader服务器各司其职,即便当有非Leader服务器宕机或新加入,此时也不会影响Leader,但是一旦Leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮Leader选举,其过程和启动时期的Leader选举过程基本一致过程相同。

🦋3.6 Zookeeper安装

集群规划

服务器IP 主机名 myid的值
192.168.174.100 node01 1
192.168.174.110 node02 2
192.168.174.120 node03 3
☀️3.6.1 下载zookeeeper的压缩包,下载网址如下

http://archive.apache.org/dist/zookeeper/

我们在这个网址下载我们使用的zk版本为3.4.9

下载完成之后,上传到我们的linux的/export/softwares路径下准备进行安装

☀️3.6.2 解压

解压zookeeper的压缩包到/export/servers路径下去,然后准备进行安装

cd /export/software

tar -zxvf zookeeper-3.4.9.tar.gz -C ../servers/ 
☀️3.6.3 修改配置文件

第一台机器修改配置文件

cd /export/servers/zookeeper-3.4.9/conf/

cp zoo_sample.cfg zoo.cfg

mkdir -p /export/servers/zookeeper-3.4.9/zkdatas/

vim zoo.cfg

dataDir=/export/servers/zookeeper-3.4.9/zkdatas
# 保留多少个快照
autopurge.snapRetainCount=3
# 日志多少小时清理一次
autopurge.purgeInterval=1
# 集群中服务器地址
server.1=node01:2888:3888
server.2=node02:2888:3888
server.3=node03:2888:3888
☀️3.6.4 添加myid配置

在第一台机器的

/export/servers/zookeeper-3.4.9/zkdatas /这个路径下创建一个文件,文件名为myid ,文件内容为1

echo 1 > /export/servers/zookeeper-3.4.9/zkdatas/myid

☀️3.6.5 安装包分发并修改myid的值

安装包分发到其他机器

第一台机器上面执行以下两个命令

scp -r /export/servers/zookeeper-3.4.9/ node02:/export/servers/

scp -r /export/servers/zookeeper-3.4.9/ node03:/export/servers/

第二台机器上修改myid的值为2

echo 2 > /export/servers/zookeeper-3.4.9/zkdatas/myid

第三台机器上修改myid的值为3

echo 3 > /export/servers/zookeeper-3.4.9/zkdatas/myid

☀️3.6.6 三台机器启动zookeeper服务

三台机器启动zookeeper服务

这个命令三台机器都要执行

/export/servers/zookeeper-3.4.9/bin/zkServer.sh start

查看启动状态

/export/servers/zookeeper-3.4.9/bin/zkServer.sh status


🦋3.7 Zookeeper的Shell 客户端操作

命令 说明 参数
create [-s] [-e] path data acl 创建Znode -s 指定是顺序节点
-e 指定是临时节点
ls path [watch] 列出Path下所有子Znode
get path [watch] 获取Path对应的Znode的数据和属性
ls2 path [watch] 查看Path下所有子Znode以及子Znode的属性
set path data [version] 更新节点 version 数据版本
delete path [version] 删除节点, 如果要删除的节点有子Znode则无法删除 version 数据版本
rmr path 删除节点, 如果有子Znode则递归删除
`setquota -n -b val path` 修改Znode配额
history 列出历史记录

1、创建普通节点

create /app1 hello

2、创建顺序节点

create -s /app3 world

3、创建临时节点

create -e /tempnode world

4、创建顺序的临时节点

create -s -e /tempnode2 aaa

5、获取节点数据

get /app1

6、修改节点数据

set /app1 xxx

7、删除节点

delete /app1 删除的节点不能有子节点

rmr /app1 递归删除

🦋3.8 Znode 的特点

  • 文件系统的核心是 Znode
  • 如果想要选取一个 Znode, 需要使用路径的形式, 例如 /test1/test11
  • Znode 本身并不是文件, 也不是文件夹, Znode 因为具有一个类似于 Name 的路径, 所以可以从逻辑上实现一个树状文件系统
  • ZK 保证 Znode 访问的原子性, 不会出现部分 ZK 节点更新成功, 部分 ZK 节点更新失败的问题
  • Znode 中数据是有大小限制的, 最大只能为1M
  • Znode是由三个部分构成
    • stat: 状态, Znode的权限信息, 版本等
    • data: 数据, 每个Znode都是可以携带数据的, 无论是否有子节点
    • children: 子节点列表

🦋3.9 Znode 的类型

  • 每个Znode有两大特性, 可以构成四种不同类型的Znode
    • 持久性
      • 持久 客户端断开时, 不会删除持有的Znode
      • 临时 客户端断开时, 删除所有持有的Znode, 临时Znode不允许有子Znode
    • 顺序性
      • 有序 创建的Znode有先后顺序, 顺序就是在后面追加一个序列号, 序列号是由父节点管理的自增
      • 无序 创建的Znode没有先后顺序
  • Znode的属性
    • dataVersion 数据版本, 每次当Znode中的数据发生变化的时候, dataVersion都会自增一下
    • cversion 节点版本, 每次当Znode的节点发生变化的时候, cversion都会自增
    • aclVersion ACL(Access Control List)的版本号, 当Znode的权限信息发生变化的时候aclVersion会自增
    • zxid 事务ID
    • ctime 创建时间
    • mtime 最近一次更新的时间
    • ephemeralOwner 如果Znode为临时节点, ephemeralOwner表示与该节点关联的SessionId

🦋3.10 通知机制

  • 通知类似于数据库中的触发器, 对某个Znode设置 Watcher, 当Znode发生变化的时候, WatchManager会调用对应的Watcher
  • 当Znode发生删除, 修改, 创建, 子节点修改的时候, 对应的Watcher会得到通知
  • Watcher的特点
    • 一次性触发 一个 Watcher 只会被触发一次, 如果需要继续监听, 则需要再次添加 Watcher
    • 事件封装: Watcher 得到的事件是被封装过的, 包括三个内容 keeperState, eventType, path
KeeperState EventType 触发条件 说明
None 连接成功
SyncConnected NodeCreated Znode被创建 此时处于连接状态
SyncConnected NodeDeleted Znode被删除 此时处于连接状态
SyncConnected NodeDataChanged Znode数据被改变 此时处于连接状态
SyncConnected NodeChildChanged Znode的子Znode数据被改变 此时处于连接状态
Disconnected None 客户端和服务端断开连接 此时客户端和服务器处于断开连接状态
Expired None 会话超时 会收到一个SessionExpiredException
AuthFailed None 权限验证失败 会收到一个AuthFailedException

🦋3.11 会话

  • 在ZK中所有的客户端和服务器的交互都是在某一个Session中的, 客户端和服务器创建一个连接的时候同时也会创建一个Session
  • Session会在不同的状态之间进行切换: CONNECTING, CONNECTED, RECONNECTING, RECONNECTED, CLOSED
  • ZK中的会话两端也需要进行心跳检测, 服务端会检测如果超过超时时间没收到客户端的心跳, 则会关闭连接, 释放资源, 关闭会话

🚀感谢:给读者的一封信

亲爱的读者,

我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。

如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。

我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。

如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。

在这里插入图片描述

再次感谢您的阅读和支持!

最诚挚的问候, “愚公搬代码”

本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://net2asp.com/9a8df8b040.html