zookeeper
2021-01-12 14:29:51 0 举报
AI智能生成
zookeeper原理
作者其他创作
大纲/内容
基础
zookeeper为啥用完整路径访问
底层用的是ConcurrentHashMap<String,DataNode> 用节点的完整路径作为key存储
节点类型
临时
服务器机器监控
持久
有序
每个节点的最大数据量有一个上限是1M
锁
悲观锁
持久节点
服务器A获取锁创建持久节点成功,随后A异常未释放锁,
其他服务不能再获取到锁,造成死锁
其他服务不能再获取到锁,造成死锁
临时节点+watch通知
惊群效应,事件大量通知服务争抢锁,只有一个服务能获取锁,
其他又进入等待,资源消耗
其他又进入等待,资源消耗
临时有序节点+watch 通知下一个节点获取锁 (推荐)
乐观锁
自带乐观锁 版本号-1不启用
发布订阅模式
客户端发送
标记会话是一个带有Watch事件的请求
将Watch事件存储到ZKWatchManager
服务端接收
解析收到请求是否待会有Watch注册事件
将对应的Watch事件存储到WatchManager
服务端事件触发
节点内容变更等触发事件
查询该节点注册的watch事件
如果存在watch事件则添加到定义的watchers集合,并从WatchManager管理中删除
调用process方法向客户端发送通知
客户端处理回调
反序列化服务端发送的请求头信息,xid为-1表示该请求为通知类型
将受到的请求反序列化为WatchEvent对象
调用eventThread.queueEvent()方法将接受到的事件交给EventThread线程处理
根据通知的事件类型,从ZKWatchManager中查询注册过的客户端Watch信息
从ZKWatchManager管理中删除事件。客户端的Watcher机制也是一次性的
将拿到的Watcher存储到waitingEvents队列中,调用EventThread中的run()循环取出在队列中等待的Watcher事件进行处理
最后调用processEvent(event)方法最终执行实现了watcher接口的process()方法
watch具有一次性,获取服务器通知之后要再次注册watch事件
序列化
Jute实现序列化
实现Record接口后,重写serialize和deserialize方法
serialize
OutputArchive
BinaryOutputArchive 对象转二进制 (默认)
CsvOutputArchive 转csv格式
XmlOutputArchive 转xml格式
deserialize
InputArchive
BinaryInputArchive
CsvInputArchive
XmlInputArchive
网络通信协议
请求协议
请求头
RequestHeader
xid 记录客户端请求的发起顺序
type 请求操作的类型
请求体
会话创建
ConnectRequest
protocolVersion 请求协议的版本信息
lastZxidSeen 最后一次接收到服务器的zxid序号
timeOut 会话超时时间
seesionId 会话标识符
passwd 会话的密码
节点查询
GetDataRequest
path 要请求的数据节点路径
watch 该节点是否注册了Watch监控
节点更新
SetDataRequest
path 节点的路径
byte[] data 节点的数据信息
version 节点期望的版本号用于锁的验证
响应协议
请求头
ReplyHeader
xid 对应RequstHeader的xid
zxid 服务端最大的zxid
err 错误状态
请求体
响应会话创建
ConnectResponse
protocolVersion 请求协议的版本信息
timeOut 会话超时时间
sessionId 会话标识符
passwd 会话密码
响应节点查询
GetDataResponse
byte[] data 节点数据的内容
stat 节点的状态信息
响应节点更新
SetDataResponse
stat 节点数据更新后的最新状态信息
超时时间
客户端的超时时间,服务端的最小和最大会话超时时间,
当客户端超时时间大于服务端最大超时时间,超时时间为服务端最大会话超时时间
当客户端超时时间大于服务端最大超时时间,超时时间为服务端最大会话超时时间
进阶
单机模式
启动准备实现
启动程序入口
QuorumPeerMain是zk的服务启动接口
解析配置文件
解析配置文件zoo.cfg
autopurge.snapRetainCount 需要保留的快照文件数量 默认3
sutopurge.purgeInterval 清理频率,以小时为单位,默认0,不开启自动清理
需要填一个大于0的整数
需要填一个大于0的整数
dataDir 数据快照目录
dataLogDir 事务日志目录
创建文件清理器
大流量的网络访问,zk会产生海量的数据,
如果磁盘数据过多或者磁盘空间不足,会导致zk服务不能正常运行
如果磁盘数据过多或者磁盘空间不足,会导致zk服务不能正常运行
DataDirCleanUpManager 历史文件的清理工具
File snapDir 数据快照地址
File dataLogDir 日志数据的存放地址
int snapRetainCount 需要保留的快照文件数量
int purgeInterval 清理频率
Timer timer
服务初始化
统计工具,服务运行信息统计 ServerStats
long packetsSent 服务端向客户端发送的响应包次数
long packetsReceived 接收到的客户端的请求包次数
long maxLatency
long minLatency
long totalLatency
服务端处理请求的延迟情况
long minLatency
long totalLatency
服务端处理请求的延迟情况
count 处理客户端的请求次数
持久化工具 FileTxnSnapLog
File dataDir
File snapDir
TxnLog txnLog
SnapShot snapLog
boolean autoCreateDB
会话管理 ServerCnxnFactory
设置服务器的TickTime和会话超时时间、创建会话管理器
设置服务器的TickTime和会话超时时间、创建会话管理器
初始化请求处理链
PrepRequestProcessor
SyncRequestProcessor
FinalRequestProcessor
集群模式
角色
leader
负责管理集群中其他的服务器,是集群中的分配和调度者
处理客户端发送的数据变更等事务性请求和读请求操作
负责管理集群中其他的服务器,是集群中的分配和调度者
处理客户端发送的数据变更等事务性请求和读请求操作
事务性请求,leader服务器会向集群中各个机器针对该请求发起投票询问
follower
选举出leader,多数投票原则
处理客户端的获取数据等非实物性的请求操作
选举出leader,多数投票原则
处理客户端的获取数据等非实物性的请求操作
observer
负责处理来自客户端的请求,不参与leader的选举,也不会被选举为leader
处理客户端的获取数据等非实物性的请求操作
负责处理来自客户端的请求,不参与leader的选举,也不会被选举为leader
处理客户端的获取数据等非实物性的请求操作
早期zk版本中,只有leader和follow服务器
随着集群规模的变大,集群处理的写入性能反而下降
对事务请求发起投票,超过半数的follow投票一致,才执行写操作
写数据时需要 leader 将写操作同步给半数以上的 Follower 节点
增加observer服务器不会影响zk的写入性能
ObserverZooKeeperServer
只接收INFORM 类型的信息包
程序启动
QuorumPeerMain类中initizeAndRun根据zoo.cfg配置,
判断服务是单机还是集群模式
判断服务是单机还是集群模式
runFromConfig 集群模式初始化
QuorumPeer 每个QuorumPeer类的实例都看作集群的一台服务器
参与leader的选举
作为follow节点同步leader、observer节点的数据
作为leader节点管理集群中的follow、observer节点
启动时,判断当前服务的状态,如果是looking,则发起选举
QuorumPeer.startLeaderElection()
选举使用udp
建立udp连接之后,开启一个线程单独处理接包发包业务
leader服务器的启动过程
集群选举
服务器自身的服务器id(SID)
最新的ZXID 事务id 标识数据的新旧程度(版本)
其中低32位针对客户端每一个事务请求,Leader服务器在产生一个新的事务proposal时进行+1,
高32位代表本轮Leader周期的epoch标号。节点最新的zxid越大代表这个节点的数据越新
高32位代表本轮Leader周期的epoch标号。节点最新的zxid越大代表这个节点的数据越新
epoch(高 32 位)
自增计数器(低 32 位)
当前服务器的epoch
投票的次数,同一轮投票过程中的逻辑时钟值是相同的,每投完一次票这个数据就会增加
选举算法
LeaderElection
AuthFastLeaderElection
FastLeaderElection
3.4.0版本后,只支持该选举算法
选举工具类QuorumCnxManager
没有被选举为leader的节点机器作为follow或observer存在
follow服务器启动过程
org.apache.zookeeper.server.quorum.Follower#followLeader
不断读取接受的包同时处理请求
当leader机器启动成功之后,follow节点会受到来自leader节点的启动通知
LearnerCnxAcceptor 接收器
专门用来收集来自集群中leader节点的通知信息
接收到来自leader服务器的通知后,follow会创建LearnerHandler实例
用来处理与leader服务器的数据同步等操作
org.apache.zookeeper.server.quorum.Follower#followLeader
不断读取接受的包同时处理请求
Leader.PROPOSAL请求
fzk.logRequest(hdr, txn)
提案记录在pendingTxns.add(request);
最小的集群配置应该是3个节点
不要用偶数节点,投票不满足大多数原则,产生 脑裂 问题
查看当前集群的节点状态
zkserver.sh status
创建会话
会话 session
sessionId 会话id
timeOut 会话超时时间
isClosing 会话关闭状态
服务器检查到一个会话已经因为超时等原因失效时,
zk会在该会话的isClosing属性值标记为关闭,再之后不对该会话进行操作了
zk会在该会话的isClosing属性值标记为关闭,再之后不对该会话进行操作了
SessionTracker
实现会话管理和维护
实现会话管理和维护
SessionTrackerImpl
sessionsById 用于根据会话id来管理具体的会话实体
sessionsWithTimeout 根据不同会话id管理每个会话的超时时间
客户端核心工作类原理解析
ClientCnxn
负责维护客户端与服务端的网络连接和信息交互
Packet
网络通信的数据结构
RequestHeader 请求头信息
ReplyHeader 响应头信息
Request 请求信息体
Response 响应信息体
clientPath serverPath 节点路径
watch 监控信息
createBB()方法
将Packet对象的数据进行序列化
只对requestHeader request readOnly属性字段序列化,其他字段存储在客户端,用于之后的相关操作
SendThread
对请求信息进行封装和序列化后,zk不会立即将请求信息通过网络发送给服务端,
而是通过将请求信息添加到队列,之后通过sendThread线程类来处理相关的请求发送等操作
而是通过将请求信息添加到队列,之后通过sendThread线程类来处理相关的请求发送等操作
定期向服务端发送ping包来实现心跳检查
服务端收到ping请求时,会根据服务端的当前时间重置与客户端的session事件,
更新该会话的请求延迟时间,保持客户端服务端的连接状态
更新该会话的请求延迟时间,保持客户端服务端的连接状态
向服务端发送请求
队列
outgoingQueue
客户端发送给服务端的发送队列
pendingQueue
服务端响应客户端操作的响应队列
EventThread
接收服务端的请求
将要触发的事件对象存放在waitingEvents队列中,之后再接收到相应的事件通知时,
会从该队列取出对应的事件,然后调用process函数处理
会从该队列取出对应的事件,然后调用process函数处理
会话管理
分桶策略
会话按照不同的时间间隔进行划分,超时时间相近的会话被放在同一个间隔区间
避免了对每一个会话进行检查,分批次的会话管理,降低管理难度,提高处理会话过期的效率
一个会话过期队列由若干个bucket组成
按照时间划分的区间
expirationInterval为单位进行时间区间的划分单位
服务端开启一个线程专门用来检索过期队列,取出过期会话执行过期操作
服务端处理会话请求
责任链模式
leader处理请求链
PrepRequestProcessor
负责请求处理的准备工作
判断请求是否是事务性相关的请求操作
事务请求,会针对该请求创建请求事务头、事务体、会话检查、ACL检查和版本检查等预处理操作
ProposalRequestProcessor
对会话请求是否执行询问zk服务中的所有服务器之后,
执行相关的会话请求操作,变更zk数据库数据
执行相关的会话请求操作,变更zk数据库数据
事务性请求,zk会在服务端发起一次投票流程,通知各机器进行事务性操作。
事务性请求,zk需要取得集群中过半数机器的投票,才真正的将数据改变。
事务性请求,zk需要取得集群中过半数机器的投票,才真正的将数据改变。
Proposal流程
Sync流程
SyncRequestProcess
处理事务性请求时,zk每台机器将该请求的操作日志记录下来,
然后向zk服务中的leader机器发送事务日志记录完成的通知(转发leader处理)
然后向zk服务中的leader机器发送事务日志记录完成的通知(转发leader处理)
processRequest()->leader.propose()->follower将自己的投票结果写入提案Proposal.addQuorumVerifier()
follower启动之后线程followLeader()不断接受leader的请求->processPacket()->将选举结果放回提案中
投票和统计投票结果,过半原则
AckRequestProcessor
processAck
tryToCommit(p, zxid, followerAddr)
Commit流程
完成Proposal后,zk服务器上的数据不会进行任何变更,经过commit之后才会
CommitProcessor
LinkedList queuedRequests队列,收到请求后不会立即处理,
而是将其放在queuedRequests队列
而是将其放在queuedRequests队列
leader本地提交之后,同步消息到其他follower节点,
放入队列,另有线程从队列取出发送
放入队列,另有线程从队列取出发送
FinalRequestProcessor
踢出重复会话的操作,避免重复处理会话请求,造成资源浪费
follower处理请求链
Follow 服务器内部首先调用的是 FollowerZooKeeperServer
继承了 LearnerZooKeeperServer
类内部ConcurrentLinkedQueue 类型的队列字段,用于存放接收到的会话请求
起始处理器 FollowerRequestProcessor
筛选该条会话请求是否是事务性的会话请求。如果是事务性的会话请求,则转发给 Leader 服务器进行操作。
如果不是事务性的会话请求,则交由 Follow 服务器处理链上的下一个处理器进行处理。
如果不是事务性的会话请求,则交由 Follow 服务器处理链上的下一个处理器进行处理。
提交处理器 CommitProcessor
对来自集群中其他服务器的事务性请求和本地服务器的提交请求操作进行匹配
同步处理器 SendAckRequestProcessor
最终处理器 FinalProcessor
follower节点请求处理链构建
observer处理链
ObserverRequestProcessor
判断客户端请求的会话类型,将所有事务性的会话请求交给 Leader 服务器处理
CommitProcessor
将该条会话放入到 queuedRequests 请求等待队列中。并唤醒相关线程进行会话处理
FinalRequestProcessor
高级篇
leader选举
最终一致性
经过一段时间后,zk集群服务器上的数据最终保持一致
数据不一致情况
集群初始化启动,需要同步集群各服务器上的数据
leader服务器崩溃,重新选举出新leader过程中会出现各服务器上数据不一致,
leader选举成功后,需要进行数据同步
leader选举成功后,需要进行数据同步
事务请求导致服务器上的数据变更时,zk只要保证集群上多数机器数据的正确变更,
就可以保证系统数据的一致性
就可以保证系统数据的一致性
每个follower可以看做是leader服务的数据副本,只要保证集群中大多数数据都是一致的,
当个别机器出现故障时,依然能保证稳定运行
当个别机器出现故障时,依然能保证稳定运行
广播模式
hashset forwardingFollowers
加锁获取所有的follower
遍历follower的状态,根据服务器的zxid,以及数据同步状态等条件判断服务器执行逻辑是否成功
统计follower服务器的sid并返回是否超过半数服务器影响
QuorumMaj
超过半数
QuorumHierarchical
权重
leader服务器会通过request.setTxn方法向集群中的follower发送数据变更的请求
新的follower机器加入集群,找到集群的leader服务器,同leader服务器进行数据同步
恢复模式
当zk集群中的一个leader服务器失效,会重新在follower中选举出一个新的leader服务器
如果在此时并发有新的事物请求时,zk集群因为没有leader服务器,
客户端会将该会话挂起,挂起的会话不会计算会话的超时时间,之后leader服务器产生之后。系统会同步执行这些会话操作
客户端会将该会话挂起,挂起的会话不会计算会话的超时时间,之后leader服务器产生之后。系统会同步执行这些会话操作
选举出的leader服务器会对系统所有服务器进行数据同步保证集群数据一致性
LearnerHandler
负责的工作
进行follower、observer服务器与leader服务器的数据同步
事务性会话请求的转发
proposal提议投票
多线程的类
在zk集群中,一个follower或Observer对应一个LearnerHandler
leader服务器会与每一个learner服务器维持一个长连接,并启动一个单独的LearnerHandler线程处理
run()
syncFollower判断数据同步的方式是否是快照方式
快照方式
将leader服务器上的数据操作日志dump出来发送给follower等
follower接收到数据操作日志后,在本地执行该日志,完成数据同步操作
mysql和redis也是采用操作日志同步
follower在选举中的作用
判断leader失效
follower与leader会有心跳,定期请求leader服务的状态
leader运行正常,follower数据同步和服务转发
反之触发leader重新选举
重新选举
集群中个别follow服务器发现返回错误,并不会立即触发重新选举
首先将follow服务状态变更为looking状态,并向网络中发起投票
当zk有更多的机器发生投票,且投票机器过半,则重新选举leader
follow角色变更
集群同步数据
选举过程非常快速,期间不会造成大量事务性请求的积压,影响集群性能
zk如何选中leader
集群初始启动
首先服务器间进行通信检查
寻找集群中的leader并进行数据同步,此时集群初始化,没有leader服务
选举
发起投票
投票时,发送服务器的myid和ZXID等选票信息字段都指向本机服务器
接收投票
各服务器发起投票的同时叶接收来自集群中其他服务器的投票信息
检查投票信息的有效性
检查投票信息的失效性,是否是本轮最新的投票
检查投票信息是否处于looking状态的服务器发出的
统计投票
将每条收到的投票信息与自己的投票信息对比
ZXID大的优先作为leader服务器
myid大的优先作为leader服务器
统计投票信息,判断是否有过半数机器投出一样的信息,如果有,即leader产生
没有过半数投票,对比之后更新自己的投票信息,重新发给其他服务器
除了被选举成leader的服务器,集群中其他服务器角色变更为follower
服务运行时的leader选举
与初始化启动阶段,变更状态和发起投票两个阶段实现不同
变更状态
leader崩溃后,集群中其他的服务器将自身状态变更为looking状态,表示做好选举leader的准备
发起投票
过程基本和集群初始化相同
QuorumCnxManager
ConcurrentHashMap<Long, SendWorker> senderWorkerMap
管理每一个服务器的通信
内部类RecvWorker,继承ZookeeperThread,负责消息接收
不断通过queueSendMap队列读取信息
内部类SendWorker,向集群其他机器发送投票信息
先将投票信息插入pollSendQueue
在通过send进行发送
投票结果处理
FastLeaderElection
最大通信间隔maxNotificationInterval
服务器等待时间finalizeWait
getVote函数设置本机投票内容
proposedLeader服务器信息
proposedZxid服务器ZXID
proposedEpoch投票轮次
之前崩溃的leader服务器是否会参与本次投票
旧leader服务器运行状态正常且可以和集群其他服务通信,那么可以参加新的leader选举
leader和follower数据同步策略
同步条件
zk集群中是否存在用来进行数据同步的learning服务器。
当选举出leader节点之后,除了被选举的leader服务,
其他服务器都作为learnning服务器并向leader服务器注册,然后进入数据同步过程
当选举出leader节点之后,除了被选举的leader服务,
其他服务器都作为learnning服务器并向leader服务器注册,然后进入数据同步过程
同步过程
事务性的请求会被同步
四种同步方式
DIFF同步
差异化同步
leader探测到learnning服务器存在时,向learnning服务器发送DIFF不同指令,
收到该指令后,learnning服务器会进行差异化的方式数据同步操作
收到该指令后,learnning服务器会进行差异化的方式数据同步操作
TRUNC+DIFF同步
先回滚再执行差异化同步
learnning服务器上存在一条事务性的操作日志,但是在集群leader中并不存在
leader服务器已经将事务记录写到本地事务日志,但没有成功发起proposal流程
该learnning上的数据先回滚到与leader一直的状态后在DIFF数据同步操作
TRUNC同步
仅回滚操作
将learnning服务器上的操作日志数据回滚到leader操作日志一致的状态,之后并不进行DIFF操作
SNAP同步
全量同步,将leader上的数据全部同步到learnning服务器上
leader服务器会向learnning服务发送SNAP命令
learnning接收到SNAP命令后全量同步
leader服务器会从内存数据库中获取全量的数据节点和会话超时时间记录器
序列化之后传输给learnning服务器
learnning服务器接收到之后反序列化载入内存数据库
本质是将leader的数据增加到learnning上,再将learnning上多余的数据回滚
底层实现
[] learnner 数组
确定数据同步方式之后,将事务操作同步到处理队列中packetsCommitted.add(qp.getZxid()),之后调用事务线程进行处理
事务请求处理和调度分析
事务性请求处理
zk集群收到请求
1.判断是否是事务性请求
2.如果非事务性请求,会 分配给observer,follower处理读请求
3.如果是事务性请求,判断当前是否是leader服务器,不是则转发给leader服务器
REQUEST 消息类型 转发给leader
当一个业务场景在查询操作多而创建删除等事务性操作少的情况下,ZooKeeper 集群的性能表现的就会很好。
而如果是在极端情况下,ZooKeeper 集群只有事务性的会话请求而没有查询操作,那么 Follow 和 Observer 服务器就只能充当一个请求转发服务器的角色, 所有的会话的处理压力都在 Leader 服务器。在处理性能上整个集群服务器的瓶颈取决于 Leader 服务器的性能。
zk的数据和文件
内存数据模型DataTree
内存数据就是创建数据节点、添加监控等请求时直接操作的数据
事务日志
dataLogDir配置 存放zk事务日志的位置
FileTxnLog 底层实现
preAllocSize
可存储的日志文件大小,默认65536*1024 字节
如果ZooKeeper产生快照频率较大,可以考虑减小这个参数,
因为每次快照后都会切换到新的事务日志,但前面的64M根本就没写完
因为每次快照后都会切换到新的事务日志,但前面的64M根本就没写完
TXNLOG_MAGIC
设置日志文件的魔数信息为ZKLG
VERSION
设置日志文件的版本信息
lastZxidSeen
最后一次更新日志得到的 ZXID
snapCount
当事务日志记录的次数达到一定数量后(默认10W次),就会将内存数据库序列化一次,使其持久化保存到磁盘上,
序列化后的文件称为"快照文件"。每次拍快照都会生成新的事务日志
序列化后的文件称为"快照文件"。每次拍快照都会生成新的事务日志
append 末尾追加的方式维护新的事务日志数据到日志文件中
记录本地事务性会话操作,用于 ZooKeeper 集群服务器之间的数据同步
命名
log.100000001,这里的100000001是这次会话的事务id(zxid)。之后的事务都将写入到这个文件中,直到拍下一个快照
查看事务日志
java -cp /usr/local/zookeeper/zookeeper-3.4.12.jar:/usr/local/zookeeper/lib/slf4j-api-1.7.25.jar org.apache.zookeeper.server.LogFormatter /usr/local/zookeeper/data/version-2/log.100000001
事务快照
数据快照的作用是将内存数据结构存储到本地磁盘中。
因此,从设计的角度说,数据快照与内存数据的逻辑结构一样,都使用 DataTree 结构
因此,从设计的角度说,数据快照与内存数据的逻辑结构一样,都使用 DataTree 结构
每隔一段时间,会把内存中的数据存储到磁盘中
FileTxnSnapLog
save()
首先会创建数据快照文件,之后调用 FileSnap 类对内存数据进行序列化,并写入到快照文件中
事务快照则是将内存数据持久化到本地磁盘
实战篇
分布式锁
分布式死锁
超时方法
创建分布式线程的时候,对每个线程设置一个超时时间
缺点:很难设置一个合适的超时时间。
超时时间过短会导致线程未执行完相关处理,就因超时时间到期被迫关闭
超时时间过短会导致线程未执行完相关处理,就因超时时间到期被迫关闭
死锁检测
运行在各服务系统上的线程或方法,探索发现应用服务器上的线程是否发生了死锁
锁的实现
排他锁
具有该锁的事务线程可以访问该条数据对象,直到该事务主动释放锁
临时节点+watch
1.对/exclusive_lock添加watch
2.创建/exclusive_lock/lock子节点
3.创建成功获取锁,处理任务,完成或者超时后释放锁
4.创建失败,watch监听事件,当/exclusive_lock/lock子节点删除时,重复1流程
问题:多个线程添加watch获取锁,只有一个线程成功拿到锁,释放锁时通知唤醒其他的所有线程重新获取锁,
然而只有一个线程能获取到锁,惊群效应
然而只有一个线程能获取到锁,惊群效应
临时有序节点+watch
创建/exclusive_lock/lock_{序号} 子节点
未获取到锁时watch比自己序号小的那个子节点
当没有比自己序号小的节点时,代表获取锁成功
释放锁时,通知只监听到watch自己节点的线程
共享锁
写入排他,读取操作没有限制
依然以一个临时节点来表示一个锁,但是不同点在于共享锁由一个格式为/shared_lock/[Hostname]-请求类型-序号
(/shared_lock/192.168.0.1-R-0000000001)的临时顺序节点表示。
(/shared_lock/192.168.0.1-R-0000000001)的临时顺序节点表示。
读写顺序
不同的事务可以同时对同一个数据进行读取操作,而更新操作必须在当前没有任何读写操作的时候进行
成功获取锁的条件
读请求
没有比自己序号小的节点
所有比自己序号小的子节点全是读请求
写请求
没有比自己序号小的节点
watch
读请求
只需要关心比自己序号小的最后一个写请求节点
写请求
只需要关心比自己序号小的最后一个节点
分布式id生成器
UUID方式
根据运行应用的计算机网卡MAC地址、时间戳、命令空间等元素,通过一定的随机算法产生
本地应用生成,速度快。无序
数据库序列方式
数据库自增主键
TDDL
作为id生成器的机器,数据库会存在一张sequence序列化表,记录当前已经被占用ID的最大值,
客户端需要id时,编码服务器会返回一段ID地址区间,并更新sequence表。客户端将收到的id区间存放内存中,
使用id时先从内存中取,内存没有去TDDL id生成器服务器获取
客户端需要id时,编码服务器会返回一段ID地址区间,并更新sequence表。客户端将收到的id区间存放内存中,
使用id时先从内存中取,内存没有去TDDL id生成器服务器获取
zk
顺序节点
服务器成功创建节点后会将节点信息响应客户端,客户端使用数据节点名称作为id
snowflake算法
64位长整形值
第1位不用
前41位,机器的毫秒数
中间10,机器的工作id
剩余12,毫秒内的流水号和表示位符号值0
每秒可以理论生成400w id编码
zk的工业级应用
dubbo
zk注册中心
服务提供者会在启动是向/dubbo/com.foo.BarService/providers目录写入自己的url地址
即zk客户端在zk服务器数据模型上创建一个数据节点
服务消费者会在启动时订阅/dubbo/com.foo.BarService/providers目录下的提供者url地址,
并在/comsumers目录写入自己的url
并在/comsumers目录写入自己的url
即在zk服务器/consumers节点路径下创建一个子节点,
然后在请求会话中发起对/providers节点的watch监控
然后在请求会话中发起对/providers节点的watch监控
kafka
监控broker机器的运行情况
将网络中的机器的运行统计存储在数据模型中的brokers节点下
topic
逻辑分区可以存在一台或多台broker上
将服务分区数量存储在/brokers/topics/[topic]数据节点下
用途
统一命名服务、统一配置管理、注册中心(分布式集群管理)、分布式锁服务、Leader 选举服务等
原理
分布式事务
原子提交协议特性
安全性
如果任意一方 commit, 所有人必须都 commit
如果任意一方中断,则没有任何一个人进行 commit
存活性
没有宕机或失败发生时, A 和 B 都能提交, 则提交
如果发生失败时,最终能达成一个一致性结果(成功/失败), 予以响应, 不能一直等待
二阶段提交
核心
引入了一个事务协调者(TC)
在真正的提交操作前, 增加了一个准备阶段, 收集业务结点是否有能力进行提交
关键点产生误解
准备阶段就是开启一个事务, 执行所有的业务代码, 都不报错,
不执行事务的 commit 操作, 然后向 TC 回复 “Yes”, 表示我已准备好提交 (错误)
不执行事务的 commit 操作, 然后向 TC 回复 “Yes”, 表示我已准备好提交 (错误)
准备阶段
二阶段提交协议中, 业务结点回复 “Yes” , 代表它做好了提交操作的所有准备, 只要结点回复了 “Yes”,
即使突然发生宕机, 只要结点重新启动, 收到了 TC 发送的 commit 指令, 必须依旧能正确提交
即使突然发生宕机, 只要结点重新启动, 收到了 TC 发送的 commit 指令, 必须依旧能正确提交
普通数据库如果在在一个事务中间发生了宕机(比如数据库所在机器直接停电), 重启以后,
数据库的默认行为是对处于中间状态的事务进行回滚操作, 并不具备继续等待并接受 commit 指令的能力
数据库的默认行为是对处于中间状态的事务进行回滚操作, 并不具备继续等待并接受 commit 指令的能力
如何满足特性
满足安全性(Safety)
事务协调者 TC,作为一个中心, 统一收集了 A 和 B 是否有意愿(有能力)进行 commit
事务协调者 TC 强制保证了, A, B 双方必须都有意愿提交时, 才进行 commit
无法满足存活性
响应超时
结点正常运行, 但是没有正常收到它所期待的响应
其他的结点故障了
网络情况不好, 数据包丢失了或网络干脆中断了
重启
结点宕机, 重启以后, 如何恢复被中断的操作
如何应对超时
事务协调者 TC 需要等待 A 和 B 返回 “yes”/“no”
此时 , TC 还没有发送过任何 “commit” 指令
TC 此时可以安全地发起终止 “abort” 指令, 放弃 commit
上面这种做法, 保证了安全性, 放弃了存活性
因为 A, B 可能都做好了准备进行提交, 只是 “yes” 信息没有成功被 TC 收到, 就导致了整个事务无法提交
这种情况属于本可以提交而未提交, 也就是说 TC 采取了非常保守的方案
A 和 B 需要等待 TC 发送 “commit”/ “abort” 指令, 才能进行下一步操作
以 B 为例进行考虑( A 的情形完全对称)
如果 B 之前回复的是 “no” , 那此时, B 可以无需等待 TC 回复就放弃 commit 操作,
因为 TC 即使收到了响应, 也会回复 “abort”, 这个行为是统一的
因为 TC 即使收到了响应, 也会回复 “abort”, 这个行为是统一的
如果 B 之前回复了 “Yes”,那此时 B 能单方面地直接进行 aboort 操作吗?
不行! 因为, TC 此时可能已经成功收到了 A, B 返回的 “Yes”, 并且已经向 A 发送了 “Commit”,
然后再向 B 发送 “commit” 前宕机了
然后再向 B 发送 “commit” 前宕机了
如果 B 放弃了 commit 操作, 就会出现 A 执行了 commit, B 未执行 Commit 的情形, 显然违背了安全性(Safety)
那 B 能单方面地直接进行 commit 操作吗?
不行” ! 因为 A 可能返回给TC 的响应是 “No”
方案一: B 一直等待 TC 的 “commit”/ “abort” 指令
方案二(更好): B 针对这种情形发起一轮终止协议操作(Termination Protocol)
超时终止协议
B 向 A 发送状态查询请求, 询问 A 是否知道事务已经提交
如果 B没有收到 A 的响应, B 无法进行后续操作, 只能继续等待
如果 B 收到了 A 的响应, 则分如下几种情况:
A 回复说 , 它已经收到了来自 TC 的 “commit”/ “abort” 指令
此时 B 可以执行 “commit”/ “abort”, 应为 TC 发给 B 的指令肯定和 A 一样
A 回复说, 它还没有向 TC 回复 “yes”/“no”
此时 B 和 A 都直接执行 abort 操作
不必担心 TC, 因为 TC 尚未收到 A 的回复, 最终会根据 A 和B 的状态回复 client
A 回复说, 它向 TC 回复了 “no”
此时 B 和 A 都直接执行 abort 操作
A 回复说, 它向 TC 回复了 “yes”
此时 B 不能进行后续操作
因为 TC 可能已经收到了 A 和 B 的 “Yes” 响应, 并且决定执行 “commit”,
向 A 和 B 发送了“commit” 指令, 只是没被 A 和 B 收到, 但是 TC 发送 “commit” 之后就会直接向客户端返回了 “ok”
向 A 和 B 发送了“commit” 指令, 只是没被 A 和 B 收到, 但是 TC 发送 “commit” 之后就会直接向客户端返回了 “ok”
TC 也有可能在等待 A 和 B 的响应过程中超时了, 直接进行了 “abort” 决定, 向 A 和 B 发送了 “abort” 指令,
只是没被 A 和 B 收到, 但是 TC 发送 “abort” 之后就会直接向客户端返回了 “fail”
只是没被 A 和 B 收到, 但是 TC 发送 “abort” 之后就会直接向客户端返回了 “fail”
以转账操作为例
如果 A 银行需要对转账客户的账户执行 -100 元操作, 当它向 TC 回复了 “Yes” 前, 应该完成以下操作
确定账户上有 100 待扣减, 将这100 元冻结, 其他的操作无法解冻, 转移这100元
留下必要的持久化记录, 确保即使宕机重启, 收到 “abort” 指令也有能力回滚到100 元被冻结前的状态
undo
留下必要的持久化记录, 确保即使宕机重启, 收到 “commit” 指令也有能力正确提交, 完成 -100 元操作
redo
留下必要的持久化记录, 标识自己已经完成了准备阶段的所有操作, 要向 TC 回复 “Yes” 指令
事务协调者是第三方
并不是
客户端 client 发起一个转账操作请求给 TC, 这个 TC 完全可以就属于银行 A。
只要银行 A 实现的 TC 在银行 A 数据库发起 -100 元的操作前,依旧先按照协议要求;
即可以根据银行 B 的准备阶段应答结果, 进行后续的操作。
只要银行 A 实现的 TC 在银行 A 数据库发起 -100 元的操作前,依旧先按照协议要求;
即可以根据银行 B 的准备阶段应答结果, 进行后续的操作。
模拟 prepare 阶段, 进行持久化记录
做好 -100 元随时提交或回退的准备
问题
同步阻塞问题
执行过程中,所有参与节点都是事务阻塞型的
当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
zk解决同步阻塞是要么半数follower同意执行,要么放弃提案
单点故障
由于协调者的重要性,一旦协调者发生故障,参与者会一直阻塞下去
尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。
尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。
zk解决二阶段提交的单点故障是用崩溃恢复重新选举的方式
数据不一致
在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这会导致只有一部分参与者接受到了commit请求。
而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据一致性的现象。
而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据一致性的现象。
zk解决数据不一致方案是,leader写数据,同步给其他节点
三阶段提交
不同点
相比二阶段提交,增加了预提交阶段
2PC
只有协调者拥有超时机制,即如果在一定时间内没有收到cohort的消息则默认失败
3PC
对于协调者(Coordinator)和参与者(Cohort)都设置了超时机制
1.参与者(Cohort)增加设置了超时机制。在doCommit阶段,如果Cohort无法及时接收到来自Coordinator的doCommit或者abort请求时,
会在等待超时之后,会继续进行事务的提交。
会在等待超时之后,会继续进行事务的提交。
2.在2PC的准备阶段和提交阶段之间,插入预提交阶段,使3PC拥有CanCommit、PreCommit、DoCommit三个阶段。
说白了,PreCommit是一个缓冲,保证了在最后提交阶段之前各参与节点的状态是一致的。
说白了,PreCommit是一个缓冲,保证了在最后提交阶段之前各参与节点的状态是一致的。
相对于2PC解决了什么问题
主要解决的单点故障问题,并减少阻塞
因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。
问题
Coordinator发出的是abort请求,假设只有一个Cohort收到并进行了abort操作,
而其他对于系统状态未知的Cohort会根据3PC选择继续Commit,此时系统状态发生不一致性。
而其他对于系统状态未知的Cohort会根据3PC选择继续Commit,此时系统状态发生不一致性。
阶段
canCommit
协调者协议流程
写本地日志“BEGIN_COMMIT”,并进入WAIT状态;
向所有参与者发送“VOTE_REQUEST”消息;
等待并接收参与者发送的对“VOTE_REQUEST”的响应。
参与者响应“VOTE_ABORT”或“VOTE_COMMIT”消息给协调者。
参与者响应“VOTE_ABORT”或“VOTE_COMMIT”消息给协调者。
PreCommit
协调者协议流程
若收到任何一个参与者发送的“VOTE_ABORT”消息
写本地“GLOBAL_ABORT”日志,进入ABORT状态;
向所有的参与者发送“GLOBAL_ABORT”消息;
若收到所有参与者发送的“VOTE_COMMIT”消息
写本地“PREPARE_COMMIT”日志,进入PRECOMMIT状态;
向所有的参与者发送“PREPARE _COMMIT”消息;
等待并接收参与者发送的对“GLOBAL_ABORT”消息或“PREPARE_COMMIT”消息的确认响应消息。
一旦收到所有参与者的“GLOBAL_ABORT”确认消息或者超时没有收到,
写本地“END_TRANSACTION”日志流程结束,则不再进入DoCommit阶段
写本地“END_TRANSACTION”日志流程结束,则不再进入DoCommit阶段
如果收到所有参与者的“PREPARE_COMMIT”确认消息,则进入DoCommit阶段。
DoCommit
协调者协议流程
向所有参与者发送的“GLOBAL _COMMIT”消息
等待并接收参与者发送的对 “GLOBAL_COMMIT”消息的确认响应消息,
一旦收到所有参与者的确认消息,写本地“END_TRANSACTION”日志流程结束
一旦收到所有参与者的确认消息,写本地“END_TRANSACTION”日志流程结束
在DoCommit阶段,如果参与者无法及时接收到来自协调者的GLOBAL_COMMIT请求时,会在等待超时之后,会继续进行事务的提交。
Paxos 算法
三个阶段
提案准备阶段
在处理事务性会话请求的时候,会针对该会话操作在集群中通过提议者(Proposer)服务器发起询问操作,
事务处理阶段
由决策者(Acceptor)服务器决定是否执行。在集群中多数服务器都正确执行会话操作
数据同步阶段
决策学习者(Learner)会同步(Acceptor)服务器上的数据,并完成最终的操作。
与ZAB协议的区别
1.投票发起
ZAB 协议的 ZooKeeper 集群中发起投票的机器是在集群中运行的一台 Leader 角色服务器
而 Paxos 算法则采用多副本的处理方式,即存在多个副本,每个副本分别包含提案者、决策者以及学习者
Paxos 算法的发起者可以是一个或多个proposer
2. paxos的事务是在各个决策者上的,而提议者只负责发送指令,zab的事务主要发生在leader上。
3. 数据同步阶段paxos是决策者作为数据源同步给学习者服务器,zk中的zab是将单台的leader作为数据源同步给其它服务器。
0 条评论
下一页