关于ZooKeeper的两点思考
群首节点commit消息分两步:1. 发送proposal 2. 收到超半数ack再发送commit广播。
思考1: 场景如下,一个客户端依次执行了两条命令 W1 R1(使用异步),先写后读。是否存在这种情况:W1请求在上述的 proposal和ack未完成或者已完成但客户端连接的服务器未commit,此时读的可能仍然是老数据?
答:是的,ZooKeeper不能保证读的强一致性。
思考2:群首节点依次广播两条proposal P1 P2。假如跟随节点对这两个proposal皆ACK但是由于网络原因导致P2 先一步 到达群首(考虑极端情况,所有P1的ACK都晚到),那么是否使得ZooKeeper不能按照提交顺序P1->P2执行事务了呢?而会是P2->P1?
答:不会。
群首服务器
LeaderZooKeeperServer流水线设置如上图,对应代码
追随者服务器如上图,对应代码如下,FollowerZooKeeperServer
首先,客户端提交请求会通过FollowerRequestProcessor
该处理器接收并处理客户端请求。处理器会执行两个操作:一,转发给下一个处理器CommitProcessor;二,转发给群首节点。
两者的流水线都涉及一个重要的处理器----CommitProcessor,我们看下主要方法
这个方法主要就是不停地往队列里塞数据,这个类继承了Thread类,主进程如下
现在,我们模拟依次执行写操作W1 W2的场景。
1. 跟随节点收到客户端的W1请求,CommitProcessor把请求放入阻塞队列,此时观察run方法,
可以发现,主要分为两种情况: 如果是读请求,放入toProcess列表,然后下次循环直接交给下一个处理器处理
如果是更改数据的请求,会在此处一直阻塞
此时客户端如果向该服务器继续发送请求,可见读请求会通过唤醒的时刻继续运行,而更改数据的操作依然会到上一步阻塞的地方阻塞
假如此时跟随节点的阻塞队列有两条请求 P1->P2, nextPending=P1
2. 对于群首节点,接收到请求后会首先PrepRequestProcessor处理,依次把请求放入阻塞队列,然后交给下一个处理器ProposalRequestProcessor处理,该处理器会准备一个提议,并把提议发送给跟随节点,然后交给下一个处理器CommitProcessor处理
假设此时主节点广播了 P1 P2,接下来我们继续看follower如何处理proposal
Follower.processPacket和FollowerZooKeeperServer#logRequest
FollowerZooKeeperServer依次把P1 P2存储到队列pendingTxns,然后把这个提议交给SyncRequestProcessor,SyncRequestProcessor继续传递给SendAckRequestProcessor向leader发送ACK消息。
这里可以顺便看下对COMMIT消息的处理
可以看到,commit之前会校验消息的zxid是否是之前存储的pendingTxns的事物的zxid,如果不一致则会退出重启。
所以,在这里就有个问题:follower可以保证按照收到的消息的顺序依次commit消息,那么如果群首节点发送proposal时产生了网络问题,后发的P2先一步到达,是否会导致follower节点的commit顺序是P2->P1
不会的,我们可以看下leader收到ACK后如何处理
通过Leader的processAck方法可知,如果获取足够的ACK,会调用下一个处理器CommitPorcessor的commit方法,在这里,假如Leader先收到了P2的足够的ACK,则会广播P2的commit消息,如果一段时间以后收到P1的ACK,会有一个事物id的校验,因为先到的P2的zxid(lastCommited)大于P1的zxid所以会丢弃P1,从而只会有P2成功COMMIT。
而CommitProcessor的comit方法做了一件事:将请求添加到queuedRequests队列,而这个队列的作用是什么呢,正式前面提到的解除CommitProcessor run方法阻塞的条件
所以,不论是由于网络的影响导致follower先收到P2->P1的proposal, 还是正常收到P1->P2的proposal但是leader却先P2->P1的ACK,都会由于leader和follower的校验机制确保顺序的有序性。