微服务一般使用的是dubbo、springcloud,我会慢慢分篇总结
- ZooKeeper
- Dubbo
- SpringCloud
本篇主要总结了一下zookeeper的相关技术点
文章目录
Zookeeper
1、zookeeper 是什么?
zookeeper 是一个开源分布式应用程序协调服务。
可以实现:数据发布/订阅、负载均衡、命名服务、集群管理、分布式协调/通知、分布式锁、Master 选举、配置维护,名字服务、分布式同步和分布式队列等功能。
zookeeper提供了:文件系统、通知机制
文件系统:
通知机制:
- 客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。
- 使用watcher注册与异步通知机制
二、工作原理
1、原子广播协议(ZAB)
- ZAB协议是ZooKeeper用来实现数据一致性的算法,这个机制保证了各个Server之间的同步
- Zab协议有两种模式,它们分别是崩溃恢复模式(选主)和消息广播模式(同步)。
- 基于一致性算法实现
ZAB协议规定:
- 所有的写操作(事物请求)都必须通过Leader完成,Leader写入本地日志后再复制到所有的Follower节点。保证了集群之间的数据一致性
- 一旦Leader节点无法工作,ZAB协议能够自动从Follower节点中重新选出一个合适的替代者,即新的Leader(该过程即为领导选举)。
- ZAB协议保证了在Leader选举的过程中,已经被Commit的数据不会丢失,未被Commit的数据对客户端不可见。
Leader写操作:
Follower/Observer均可接受写请求,但不能直接处理,而需要将写请求转发给Leader处理
- 客户端向Leader发起写请求
- Leader将写请求以Proposal(提议)形式发给所有Follower并等待ACK (反馈)
- Follower收到Leader的Proposal后返回ACK
- Leader得到过半数的ACK(Leader对自己默认有一个ACK)后向所有的Follower和Observer发送Commmit ,要求其将前一个Proposal(提议)进行提交
- Leader将处理结果返回给客户端
follower读操作
- Leader/Follower/Observer都可直接处理读请求,从本地内存中读取数据并返回给客户端即可。
Follower主要工作有以下三个。
- 处理客户端非事务请求,转发事务请求给Leader服务器。
- 参与事务请求Proposal(建议)的投票。
- 参与Leader选举投票。
Zookeeper节点状态:
**LOOKING: 不确定Leader状态。**该状态下的服务器认为当前集群中没有Leader,会发起Leader选举
**FOLLOWING: 跟随者状态。**表明当前服务器角色是Follower,并且它知道Leader是谁
**LEADING: 领导者状态。**表明当前服务器角色是Leader,它会维护与Follower间的心跳
**OBSERVING: 观察者状态。**表明当前服务器角色是Observer,与Folower唯一的不同在于不参与选举,也不参与集群写操作时的投票
2、消息广播模式(同步)
- 消息广播模式是基于具有FIFO特性的TCP协议进行网络通信,保证了消息广播过程中的消息接收和发送顺序性。
- 广播过程:
- leader服务器会为每个事务请求生成对应的Proposal(提议)来进行广播,并在广播前、leader会为这个事务分配一个全局单调递增的唯一ID(事务ID:ZXID)。由于ZAB协议需要保证每个消息严格的因果关系,因此要求每个事务Proposal必须按照ZXID的顺序进行处理
- 广播过程中,leader会为每一个follower 都分配一个单独的队列来依次发送事务Proposal(提议),
- 每一个follower 接收到事务后,会先以事务日志写入到本地磁盘,并且在写入成功后,反馈ack
- leader 收到半数的follower的ack,就会广播Commit给所有的follower服务器进行事务提交。并提交自己的事务
3、崩溃恢复模式(选举)
- 当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,ZAB协议就会进行leader选举;当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
4、Leader选举
Leader选举是保证分布式数据一致性的关键所在
选举算法:FastLeaderELection
选举过程:
- 每个Server发出一个投票(包含服务器的唯一标识myid和事务zxid)
- 接受来自各个服务器的投票(会验证有效性,是否来自LOOKING状态的服务器)
- 处理投票,根据规则决定是否变更自己的投票,如果变更,投票再发送出去
- 规则如下 :
- 优先检查事务zxid。事务zxid比较大的服务器优先作为Leader
- 事务zxid相同,那么就比较myid。myid较大的服务器优先作为Leader
- 统计投票,收到超过半数的相同投票,便认为已经选出作为Leader。
- 改变服务器状态
选具有最大的ZXID作为leader的意义
- 是为了保证这个leader具有所有已经提交的提案,
- 省去了leader检查Proposal的提交和丢弃工作的这步操作
myid:即配置中 server.id=host:port:port 的这个ID ,范围在1-255
三、经典应用场景
1、数据发布/订阅
发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。
例如:全局的配置信息,服务式服务框架的服务地址列表等就非常适合使用。
采用了一种推拉结合的模式
2、软负载均衡
在分布式环境中,为了保证高可用性,通常同一个应用或服务的提供方都会部署多份,达到对等服务。而消费者就须要在这些对等的服务器中选择一个来执行相关的业务逻辑
比较典型的是消息中间件中的生产者,消费者负载均衡。
KafkaMQ和阿里开源的metaq都是通过zookeeper来做到生产者、消费者的负载均衡
KafkaMQ:
- 生产者在发送消息的时候必须选择一台broker上的一个分区来发送消息
- 把所有broker和对应的分区信息全部注册到ZK指定节点上
3、命名服务
通过命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。
被命名的实体通常可以是集群中的机器,提供的服务地址,远程对象等等
分布式服务框架Dubbo中使用ZooKeeper来作为其命名服务
4、集群管理
所谓集群管理无在乎两点:是否有机器退出和加入、选举master。
集群机器监控:利用zk 的临时节点特性+监视器(Watch)来实现
1> 服务器角色
Zookeeper集群是一个基于主从复制的高可用集群,每个服务器承担如下三种角色中的一种
- Leader:
- 一个集群同一时间只会有一个实际工作的Leader,它会发起并维护与各Follwer及Observer间的心跳。
- Leader进行所有的写操作,完成后再由Leader将写操作广播给其它服务器。
- Follower:
- 一个集群可能同时存在多个Follower,它会响应Leader的心跳。
- Follower可直接处理并返回客户端的读请求
- 将写请求转发给Leader处理,并且负责在Leader处理写请求时对请求进行投票。
- Observer: 角色与Follower类似,但是无投票权。
2> Master选举
在分布式环境中,相同的业务应用分布在不同的机器上,有些业务逻辑(例如一些耗时的计算,网络I/O处理),往往只需要让整个集群中的某一台机器进行执行,其余机器可以共享这个结果,这样可以大大减少重复劳动,提高性能,于是这个master选举便是这种场景下的碰到的主要问题。
选举过程:
- 如果多个客户端请求创建同一个临时节点,成功创建该节点的客户端(只有一台)所在的机器就成了Master。
- 同时其他没有成功的客户端,都会在该节点上注册一个子节点变更的watch,用于监控当前master机器是否存活,
- 一旦发现当前的master挂了,那么其他客户端将会重新进行master选举。
注意:当zk全部宕机了,不会影响现有consumer和provider之间的调用,但是新的provider想要注册到注册中心上是不行的
3> 数据复制
集群数据复制的好处
1、容错:一个节点出错,不致于让整个系统停止工作,别的节点可以接管它的工作;
2、提高系统的扩展能力 :把负载分布到多个节点上,或者增加节点来提高系统的负载能力;
3、提高性能:让客户端本地访问就近的节点,提高用户访问速度。
集群数据复制种类
1、写主(WriteMaster) :对数据的修改提交给指定的节点。读无此限制,可以读取任何一个节点。这种情况下客户端需要对读与写进行区别,俗称读写分离;
2、写任意(Write Any):对数据的修改可提交给任意的节点,跟读一样。这种情况下,客户端对集群节点的角色与变化透明。
对zookeeper来说,它采用的方式是写任意
4> watch的工作机制
watch 机制总的来说可以分三个过程,
- 客户端注册watch:客户端在向 ZK 服务器注册 Watcher,同时将 Watcher 对象存储在自己的的 WatchManager 中
- 服务器处理watch:当 ZooKeeper 服务器端触发 Watcher 事件后,会向客户端发送通知
- 客户端回调watch:收到通知,客户端线程从 WatchManager 中取出对应的 Watcher 对象来执行回调
watcher 特性:一次性、顺序回调执行、轻量级(在客户端没有收到关注数据的变化事件通知)
5、分布式协调与通知
zk中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理
6、分布式锁
分布式锁,这个主要得益于ZooKeeper为我们保证了数据的强一致性
zookeeper分布式锁:保证了数据的强一致性。
zookeeper锁服务主要分两类:一个是保持独占,一个是控制时序。
-
保持独占(谁成功谁得锁)
将zookeeper上的一个机器节点定义为一个锁,那么获得锁就是通过创建节点的方式来实现。zookeeper会保证所有客户端都去创建/distribute_lock节点,最终只有一个客户端能创建成功(谁成功谁得锁),那么就认为这个客户端获得了锁。
-
控制时序(编号谁小锁谁得原则)
所有试图来获取锁的客户端,都会被安排执行,只是有个全局的时序。不过这里的/distribute_lock 已经预先存在,所有的客户端在它下面创建临时有序节点,当自己创建的节点编号是所有节点中最小的时便获得锁(编号谁小锁谁得原则),用完删除。
扩展:
释放锁分两种方式: 被迫释放或正常释放
1、被迫释放:当前获得锁的客户端机器发生宕机或重启,那么该节点就会被删除,释放锁。
2、正常释放:正常执行完业务逻辑后,该客户端就会主动将自己创建的临时节点删除,释放锁。
实现分布式锁的3种方式:
-
基于Zookeeper实现分布式锁
-
基于数据库分布式锁:排它锁(增加for update)和乐观锁(加一个时间戳,验证后再修改)
-
基于Redis的分布式锁:set成功表示获取锁,set失败表示获取失败,失败后需要重试。
1、setNx一个锁key,相应的value为当前时间加上过期时间的时钟;
2、如果setNx成功,或者当前时钟大于此时key对应的时钟则加锁成功,否则加锁失败退出;
3、释放锁时判断当前时钟是否小于锁key的value,如果是则执行删除锁key的操作。
3种方式优缺点:
基于数据库:排它锁 :安全,容易死锁,乐观锁不易死锁,但只能对一张表的数据进行加锁
基于redis :实现比较简单,性能高,同时定期失效的机制可以解决因网络抖动锁删除失败的问题
基于zookeeper:实现复杂,需要集群,
7、分布式队列
队列方面,简单地讲有两种,
- 常规的先进先出队列:和分布式锁服务中的控制时序场景基本原理一致
- 统一时序队列:是指等到队列成员聚齐之后的才统一按序执行。
第二种队列其实是在FIFO队列的基础上作了一个增强。通常可以在 /queue 这个znode下预先建立一个/queue/num 节点,并且赋值为n(或者直接给/queue赋值n),表示队列大小,之后每次有队列成员加入后,就判断下是否已经到达队列大小,决定是否可以开始执行了。
这种用法的典型场景是,分布式环境中,一个大任务Task A,需要在很多子任务完成(或条件就绪)情况下才能进行。这个时候,凡是其中一个子任务完成(就绪),那么就去 /taskList 下建立自己的临时时序节点(CreateMode.EPHEMERAL_SEQUENTIAL),当 /taskList 发现自己下面的子节点满足指定个数,就可以进行下一步按序进行处理了。