Spring Cloud H (二)服务注册中心 Zookeeper

目录

前言

一、Zookeeper数据结构

二、统一配置管理

三、统一命名服务

四、Zookeeper安装启动

五、zoo.conf参数解读

六、集群搭建

七、Zookeeper Session

为什么要有Session?

Session的创建

会话超时管理(分桶策略+会话激活)

分桶策略

会话激活

八、节点特性

九、权限控制 ACL

ACL命令行操作

ACL构成

十、watcher 机制(尚在研究)

十一、Leader 选举原理

十二、分布式锁

附一、资料——尚硅谷视频配套参考资料

参考


前言

ZooKeeper 是 Apache 软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。

ZooKeeper 的架构通过冗余服务实现高可用性。

Zookeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。

一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现诸如数据发布/订阅负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。(来自菜鸟教程)

zookeeper官网

ZooKeeper的架构图,client跟ZooKeeper集群中的某一台server保持连接,发送读/写请求,读请求直接由当前连接的server处理,写请求由于是事务请求,由当前server转发给leader进行处理。同时,client还能接收来自server端的watcher通知。
 

一、Zookeeper数据结构

Zookeeper是一个树形的目录结构,其数据和Unix的文件系统的目录树很类似,具有一个层次化的结构。并且这里的每一个节点被称为ZNode,能够储存数据和节点信息。节点可以有子节点,并且允许有少量的数据存储在节点。

节点类型

PERSISTENT持久化节点
PERSISTENT_SEQUENTIAL顺序自动编号持久化节点,这种节点会根据当前已存在的节点数自动加 1  -e
EPHEMERAL临时节点, 客户端session超时这类节点就会被自动删除  -s
EPHEMERAL_SEQUENTIAL临时自动编号节点  -es

二、统一配置管理

比如我们现在有A,B,C三个系统,他们每个系统都有一个配置,分别是ASystem.yml、BSystem.yml、CSystem.yml,然后这几个配置又有着很多的相同的配置项并且,改变了配置项的信息很可能就要重启系统。于是我们就会希望抽取配置的公共部分新建新的common.yml。这样我们改变三个系统的公共部分的时候就直接该这个配置文件就行。并且这样也不需要A,B,C系统重启。

我们可以将common.yml这份配置放在ZooKeeper的Znode节点中,系统A、B、C监听着这个Znode节点有无变更,如果变更了,及时响应。

图片

三、统一命名服务

统一命名服务的理解其实跟域名一样,是我们为这某一部分的资源给它取一个名字,别人通过这个名字就可以拿到对应的资源。比如我们现在有一个域名:www.xxx.com,该域名下面有很多机器。

192.168.1.1

192.168.1.2

192.168.1.3

192.168.1.4

 这样别人就可以通过访问域名来访问我的机器,而不是通过IP去访问。

图片

四、Zookeeper安装启动

直接官网下载,在Linux系统解压即可。进入到bin/文件夹

服务端命令:

./zkServer.sh start
./zkServer.sh stop
./zkServer.sh status

 客户端命令:

./zkCli.sh -server localhost:2181
./zkCli.sh

 简单命令:

./zkCli.sh -server ip:port   #连接服务端
quit                         #断开连接
ls 路径
create 路径 数据              #创建节点/数据
set 路径 数据                 #设置数据
get 路径                      #获取数据
delete 路径                   #删除节点
deleteall 节点路径            #删除所有带子节点的节点
stat 路径                     #查看节点状态信息

四字命令: zookeeper 支持某些特定的四字命令与其交互,用户获取 zookeeper 服务的当前状态及相关信息,用户在客户端可以通过 telenet 或者 nc(netcat) 向 zookeeper 提交相应的命令。

echo [command] | nc [ip] [port] #命令格式

         常见的command命令

conf打印出服务相关配置的详细信息。
stat输出关于性能和连接的客户端的列表
ruok测试服务是否处于正确状态。如果确实如此,那么服务返回"imok",否则不做任何相应
cons列出所有连接到这台服务器的客户端全部连接/会话详细信息。包括"接受/发送"的包数量、会话id、操作延迟、最后的操作执行等等信息。
envi打印出服务环境的详细信息。
更多https://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_4lw

五、zoo.conf参数解读

tickTime=2000: 通信心跳数, Zookeeper 服务器与客户端心跳时间,单位毫秒。Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。它用于心跳机制,并且设置最小的session超时时间为两倍心跳时间。 (session的最小超时时间是2*tickTime)


initLimit=10: LF 初始通信时限。集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。


syncLimit=5: LF 同步通信时限。集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime, Leader认为Follwer死掉,从服务器列表中删除Follwer。


dataDir:数据文件目录+数据持久化路径。主要用于保存 Zookeeper 中的数据。


clientPort =2181:客户端连接端口。监听客户端连接的端口。

六、集群搭建

Linux上搭建集群

server.id = ip:port1:port2

id:表示这个是第几号服务器;集群模式下配置一个文件 myid, 这个文件在 dataDir 目录下,这个文件里面有一个数据就是 A 的值, Zookeeper 启动时读取此文件,拿到里面的数据与 zoo.cfg 里面的配置信息比较从而判断到底是哪个 server。

ip:服务器的ip地址

port1:集群内机器通信使用

port2: 选举 leader 使用

七、Zookeeper Session

为什么要有Session?

客户端与服务端之间的连接是基于 TCP 长连接,client 端连接 server 端默认的 2181 端口,也就是 session 会话。从第一次连接建立开始,客户端开始会话的生命周期,客户端向服务端的ping包请求,每个会话都可以设置一个超时时间。通过这个Session会话,客户端能够通过心跳检测和服务器保持有效的会话,也能够向ZooKeeper服务器发送请求并接受响应,同时还能通过该连接接收来自服务器的Watch事件通知。

短连接:

client向server发起连接请求,server接到请求,然后双方建立连接。client向server发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起close操作。为什么呢,一般的server不会回复完client后立即关闭连接的,当然不排除有特殊的情况。从上面的描述看,短连接一般只会在client/server间传递一次读写操作

短连接的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段

长连接:

client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。

首先说一下TCP/IP详解上讲到的TCP保活功能,保活功能主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,从而可以代表客户使用资源。如果客户已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将应远等待客户端的数据,保活功能就是试图在服务器端检测到这种半开放的连接。

如果一个给定的连接在两小时内没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一:

        (1)客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。

        (2)客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。

        (3)客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。

        (4)客户机正常运行,但是服务器不可达,这种情况与2类似,TCP能发现的就是没有收到探查的响应。

从上面可以看出,TCP保活功能主要为探测长连接的存活状况,不过这里存在一个问题,存活功能的探测周期太长,还有就是它只是探测TCP连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。

在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。

Session的创建

SessionID:会话ID,在创建一次会话的时候zk会给其分配一个全局唯一的ID,用来标识此会话。

Timeout:会话超时时间。客户端在构造 Zookeeper 实例时候,向服务端发送配置的超时时间,server 端会根据自己的超时时间限制最终确认会话的超时时间。如果客户端与服务器之间因为网络闪断导致断开连接,并在TimeOut时间内未连上其他server,则此次会话失效,此次会话创建的临时节点将被清理。

TickTime:下次会话超时时间点,默认 2000 毫秒。可在 zoo.cfg 配置文件中配置,便于 server 端对 session 会话实行分桶策略管理

isClosing:该属性标记一个会话是否已经被关闭,当 server 端检测到会话已经超时失效,该会话标记为"已关闭",不再处理该会话的新请求。

zookeeper 创建 sessionID 类 SessionTrackerImpl 中的源码如下。

//是ZooKeeper服务器的会话管理器,负责会话的创建、管理和清理等工作
public class SessionTrackerImpl extends Thread implements SessionTracker {
   
    {...}

	//参数id为当前服务器的myid
    public static long initializeNextSession(long id) {
        long nextSid = 0;
        //此处采用无符号右移,是为了防止出现负数的情况
        nextSid = (System.currentTimeMillis() << 24) >>> 8;
        nextSid =  nextSid | (id <<56);
        return nextSid;
    }
    
	{...}
}

会话超时管理(分桶策略+会话激活)

分桶策略

SessionTrackerImpl通过**“分桶策略”来进行会话的管理,分桶的原则是将每个会话的“下次超时时间点”(ExpirationTime)**相同的会话放在同一区块中进行管理,以便于ZooKeeper对会话进行不同区块的隔离处理,以及同一区块的统一处理,如下图,横坐标是一个个的超时时间点。

分桶管理

 ExpirationTime 的计算方式:

ExpirationTime_ = CurrentTime + SessionTimeOut
ExpirationTime = ( ExpirationTime_/ExpirationInterval + 1 ) * ExpirationInterval

 为什么要采用第二个公式进行计算?

提高会话检查的效率。让创建时间临近的会话,分配在一个桶中,实际生产环境中一个服务端会有很多客户端会话,逐个检查过期时间会非常耗时,把它们放在一个桶中批量处理,可以大大提高效率。比如CurrentTime为1547046000、1547046001这样的会话就会被分配在一个桶中。

其次,Leader每隔ExpirationInterval 毫秒进行会话的清理,而刚好 ExpirationTime 这个时间点是会话的失效时间点,如果发现失效,直接清理掉就OK,避免了检查时未失效,但没过几毫秒又失效了这种情况。比如,ExpirationTime 是1547046000,如果在1547045998的时刻检查,发现还有效,但过了2ms之后就无效了。而如果会话超时检查和会话超时时间在同一个时间节点的话,就会避免这种情况。

会话激活

为了保持client会话的有效性,在ZooKeeper运行过程中,client会在会话超时时间过期范围内向server发送PING请求来保持会话的有效性,俗称“心跳检测”。同时server重新激活client对应的会话。

流程图

激活后进行迁移会话的过程,然后开始新一轮:

源码实现:

//sessionId为发起会话激活的client的sessionId,timeout为会话超时时间
synchronized public boolean touchSession(long sessionId, int timeout) {
        /*
         * sessionsById的结构为 HashMap<Long, SessionImpl>(),每个sessionid都有一个对应的session实现
         * 这里取出对应的session实现
         */
        SessionImpl s = sessionsById.get(sessionId);
        // Return false, if the session doesn't exists or marked as closing
        if (s == null || s.isClosing()) {
            return false;
        }
        //计算当前会话的下一个失效时间,可以理解为ExpirationTime_New
        long expireTime = roundToInterval(System.currentTimeMillis() + timeout);
        //tickTime是上一次计算的超时时间,可以理解为ExpirationTime_Old
        if (s.tickTime >= expireTime) {
            // Nothing needs to be done
            return true;
        }
        //将ExpirationTime_Old对应的桶中的会话取出,SessionSet 是SessionImpl的集合
        SessionSet set = sessionSets.get(s.tickTime);
        if (set != null) {
        	//将旧桶中的会话移除
            set.sessions.remove(s);
        }
        //更新当前会话的下一次超时时间
        s.tickTime = expireTime;
        //从新桶中取出该会话,无则创建,有则更新
        set = sessionSets.get(s.tickTime);
        if (set == null) {
            set = new SessionSet();
            sessionSets.put(expireTime, set);
        }
        set.sessions.add(s);
        return true;
    }

八、节点特性

同一级节点key名称为一

创建节点时必须带上文件名

session关闭,临时节点被清理

自动创建顺序节点

Watch机制监听节点变化

九、权限控制 ACL

zookeeper 的 ACL(Access Control List,访问控制表)权限在生产环境是特别重要的,所以本章节特别介绍一下。ACL 权限可以针对节点设置相关读写等权限,保障数据安全性。

ACL命令行操作

getAcl:获取某个节点的ACL权限信息

setAcl:设置某个节点的Acl权限信息

addauth:输入认证授权信息,注册时输入明文密码,加密形式保存

ACL构成

zookeeper的ACL由 [scheme:id:permissions] 构成权限列表

scheme:代表采用的某种权限机制,包括 world、digest、ip、super 几种

IP通过ip地址粒度进行权限控制模式,例如配置了:192.168.110.135即表示权限控制都是针对这个ip地址的,同时也支持按网段分配,比如:192.168.110.*
digestdigest是最常用的权限控制模式,也更符合我们对权限控制的认识,其类似于"username:password"形式的权限标识进行权限配置。ZK会对形成的权限标识先后进行两次编码处理,粉笔是SHA-1加密算法和Base64编码。
world

最开放的权限控制方式,是一种特殊的 digest 模式,只有一个权限标

识“world:anyone”

super超级用户模式,在超级用户模式下可以对ZK任意进行操作

Ip:即授权对象,允许访问的用户

 permissions:权限组合字符串,由 cdrwa 组成,其中每个字母代表支持不同权限, 创建权限 create(c)、删除权限 delete(d)、读权限 read(r)、写权限 write(w)、管理权限admin(a)。

十、watcher 机制(尚在研究)

概述

zookeeper提供了数据的发布/订阅功能,多个订阅者可同时监听某一特定的主题对象,当主题对象的自身状态发生了变化时(例如节点内容发生了改变、节点下的子节点列表发生改变等),会实时、主动的通知所有订阅者。

机制

Watcher由三部分组成zookeeper服务端zookeeper客户端客户端的watchManager对象

客户端首先将Watcher注册到服务器,同时将Watcher对象保存到客户端的Watcher管理器中。当zookeeper服务器端监听数据状态发生变化时,服务端会主动通知客户端,接着客户端的Watcher管理器会触发相关的Watcher来回调相应的处理逻辑,从而完成整体的数据的发布/订阅流程。

特性

一次性watcher是一次性的,一旦被触发就会被移除,再次使用时需要重新注册。
客户端顺序回调watcher 回调的顺序是串行化执行的,只有回调后客户端才能看到最新的数据状态。
轻量级watcherEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容。
时效性watcher只有在当前的session彻底失效时才会无效,若在session有效内快速重连成功,则watcher依然存在,依然可以接收到通知。

通知状态

SyncConnected客户端和服务端正常连接
Disconnected客户端和服务端断开连接
Expired会话session失效
AuthFailed身份认证失败

事件类型

NodeCreated数据节点创建
NodeDeleted数据节点删除
NodeDataChanged数据节点内容改变
NodeChildrenChanged数据子节点列表发生变更

十一、Leader 选举原理

zookeeper 的 leader 选举存在两个阶段,一个是服务器启动时 leader 选举,另一个是运行过程中 leader 服务器宕机。

在分析选举原理前,先介绍几个重要的参数

服务器ID(myid):编号越大在选举算法中权重越大

事务ID(zxid):值越大说明数据越新,权重越大

逻辑时钟(epoch-logicalclock):同一轮投票过程中的逻辑时钟值是相同的,每投完一次值会增加

选举状态

LOOKING:竞选状态

FOLLOWING: 随从状态,同步 leader 状态,参与投票

OBSERVING: 观察状态,同步 leader 状态,不参与投票

LEADING:领导者状态

服务器启动时Leader选举:

(1)每台 server 发出一个投票,由于是初始情况,server1 和 server2 都将自己作为 leader 服务器进行投票,每次投票包含所推举的服务器myid、zxid、epoch,使用(myid,zxid)表示,此时 server1 投票为(1,0),server2 投票为(2,0),然后将各自投票发送给集群中其他机器。

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

(3)分别处理投票。针对每一次投票,服务器都需要将其他服务器的投票和自己的投票进行对比,对比规则如下:

a. 优先比较 epoch

b. 检查 zxid,zxid 比较大的服务器优先作为 leader

c. 如果 zxid 相同,那么就比较 myid,myid 较大的服务器作为 leader 服务器

(4)统计投票。每次投票后,服务器统计投票信息,判断是都有过半机器接收到相同的投票信息。server1、server2 都统计出集群中有两台机器接受了(2,0)的投票信息,此时已经选出了 server2 为 leader 节点。

(5)改变服务器状态。一旦确定了 leader,每个服务器响应更新自己的状态,如果是 follower,那么就变更为 FOLLOWING,如果是 Leader,变更为 LEADING。此时 server3继续启动,直接加入变更自己为 FOLLOWING。

 运行过程中的 leader 选举

当集群中 leader 服务器出现宕机或者不可用情况时,整个集群无法对外提供服务,进入新一轮的 leader 选举。

(1)变更状态。leader 挂后,其他非 Oberver服务器将自身服务器状态变更为 LOOKING。

(2)每个 server 发出一个投票。在运行期间,每个服务器上 zxid 可能不同。

(3)处理投票。规则同启动过程。

(4)统计投票。与启动过程相同。

(5)改变服务器状态。与启动过程相同。

举例说明:

(1) 服务器 1 启动, 发起一次选举。服务器 1 投自己一票。此时服务器 1 票数一票,不够半数以上(3 票),选举无法完成,服务器 1 状态保持为 LOOKING;
(2)服务器 2 启动, 再发起一次选举。服务器 1 和 2 分别投自己一票并交换选票信息:此时服务器 1 发现服务器 2 的 ID 比自己目前投票推举的(服务器 1)大,更改选票为推举服务器 2。此时服务器 1 票数 0 票,服务器 2 票数 2 票,没有半数以上结果,选举无法完成,服务器 1, 2 状态保持 LOOKING
(3)服务器 3 启动, 发起一次选举。此时服务器 1 和 2 都会更改选票为服务器 3。此次投票结果:服务器 1 为 0 票,服务器 2 为 0 票,服务器 3 为 3 票。此时服务器 3 的票数已经超过半数,服务器 3 当选 Leader。服务器 1, 2 更改状态为 FOLLOWING,服务器 3 更改状态为 LEADING;
(4)服务器 4 启动, 发起一次选举。此时服务器 1, 2, 3 已经不是 LOOKING 状态,不会更改选票信息。交换选票信息结果:服务器 3 为 3 票,服务器 4 为 1 票。此时服务器 4服从多数,更改选票信息为服务器 3,并更改状态为 FOLLOWING;
(5)服务器 5 启动,同 4 一样当小弟。

十二、分布式锁

分布式锁的原理恰巧运用到了临时顺序节点,zookeeper分布式锁的步骤如下:

一个ZooKeeper分布式锁,首先需要创建一个父节点,尽量是持久节点(PERSISTENT类型),然后每个要获得锁的线程,都在这个节点下创建个临时顺序节点。为了确保公平,可以简单的规定:编号最小的那个节点,表示获得了锁。所以,每个线程在尝试占用锁之前,首先判断自己是排号是不是当前最小,如果是,则获取锁。

每个线程抢占锁之前,先尝试创建自己的ZNode。同样,释放锁的时候,就要删除创建的Znode。创建成功后,如果不是排号最小的节点,就处于等待通知的状态。等谁的通知呢?不需要其他人,只需要等前一个Znode的通知就可以了。前一个Znode删除的时候,会触发Znode事件,当前节点能监听到删除事件,就是轮到了自己占有锁的时候。第一个通知第二个、第二个通知第三个。

ZooKeeper的节点监听机制,能够非常完美地实现这种击鼓传花似的信息传递。具体的方法是,每一个等通知的Znode节点,只需要监听(linsten)或者监视(watch)排号在自己前面那个,而且紧挨在自己前面的那个节点,就能收到其删除事件了。只要上一个节点被删除了,就进行再一次判断,看看自己是不是序号最小的那个节点,如果是,自己就获得锁。

 ZooKeeper的内部优越的机制,能保证由于网络异常或者其他原因,集群中占用锁的客户端失联时,临时节点被删除,锁能够被有效释放。一旦占用Znode锁的客户端与ZooKeeper集群服务器失去联系,这个临时Znode也将自动删除。排在它后面的那个节点,也能收到删除事件,从而获得锁。正是由于这个原因,在创建取号节点的时候,尽量创建临时znode

 ZooKeeper这种首尾相接,后面监听前面的方式,可以避免羊群效应。所谓羊群效应就是一个节点挂掉,所有节点都去监听,然后做出反应,这样会给服务器带来巨大压力,所以有了临时顺序节点,当一个节点挂掉,只有它后面的那一个节点才做出反应。

数据同步

在 Zookeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性。ZAB 协议分为两部分:消息广播,崩溃恢复。

消息广播

Zookeeper 使用单一的主进程 Leader 来接收和处理客户端所有事务请求,并采用 ZAB 协议的原子广播协议,将事务请求以 Proposal 提议广播到所有 Follower 节点,当集群中有过半的Follower 服务器进行正确的 ACK 反馈,那么Leader就会再次向所有的 Follower 服务器发送commit 消息,将此次提案进行提交。这个过程可以简称为 2pc 事务提交,整个流程可以参考下图,注意 Observer 节点只负责同步 Leader 数据,不参与 2PC 数据同步过程。

崩溃恢复

在正常情况消息广播情况下能运行良好,但是一旦 Leader 服务器出现崩溃,或者由于网络原理导致 Leader 服务器失去了与过半 Follower 的通信,那么就会进入崩溃恢复模式,需要选举出一个新的 Leader 服务器。在这个过程中可能会出现两种数据不一致性的隐患,需要 ZAB 协议的特性进行避免。

        1、Leader 服务器将消息 commit 发出后,立即崩溃

        2、Leader 服务器刚提出 proposal 后,立即崩溃

ZAB 协议的恢复模式使用了以下策略:

        1、选举 zxid 最大的节点作为新的 leader

        2、新 leader 将事务日志中尚未提交的消息进行处理

 

附一、资料——尚硅谷视频配套参考资料

尚硅谷配套资料 提取码yyds

参考

https://blog.csdn.net/MuErHuoXu/article/details/86218115?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242

https://www.runoob.com/w3cnote/zookeeper-session.html

https://www.cnblogs.com/liuyong/archive/2011/07/01/2095487.html

https://mp.weixin.qq.com/s?src=11&timestamp=1625626409&ver=3175&signature=MFurnk9jBkbGCNGb1KKlI*VH0piRyDDKgZ9WmWIpqb7P-s1pIes*ozh04TIqMpk8M19kr2oMaPZT528AIzl2O0vY**Ux7EeOPkwKMiH-NTc6GdbpfvkSuXORXBH6B9ui&new=1

https://blog.csdn.net/nlcexiyue/article/details/109047470?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162562982316780274191719%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162562982316780274191719&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-2-109047470.pc_search_result_control_group&utm_term=zookeeper%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81&spm=1018.2226.3001.4187

https://blog.csdn.net/crazymakercircle/article/details/85956246?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值