关于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的校验机制确保顺序的有序性。 

posted @ 2019-05-13 20:47  liuyuchong  阅读(204)  评论(0编辑  收藏  举报