本篇文章仅仅是起一个抛砖迎玉的做用,举一个如何修改源码的例子。文章的灵感来自 ZOOKEEPER-2784。html
以前的文章讲过zxid的设计,咱们先复习下:java
zxid有64位,分红两部分:
高32位是Leader的epoch:选举时钟,每次选出新的Leader,epoch累加1
低32位是在这轮epoch内的事务id:对于用户的每一次更新操做集群都会累加1。git
Zookeeper 的事务 ID 有可能会超过 32 位。github
epoch增加很是慢,超过32位须要很是久的时间,几乎能够忽略这个问题,可是事务 ID 彷佛不行。咱们来算下。apache
若是咱们每秒钟操做1000次 Zookeeper ,即 1k/s ops,那么并发
2^32/(86400∗1000) ≈ 49.7
49.7天后,事务 ID 就将溢出,那溢出会发生什么,看代码:ide
src/java/main/org/apache/zookeeper/server/quorum/Leader.java line1037大数据
/** * create a proposal and send it out to all the members * * @param request * @return the proposal that is queued to send to all the members */ public Proposal propose(Request request) throws XidRolloverException { /** * Address the rollover issue. All lower 32bits set indicate a new leader * election. Force a re-election instead. See ZOOKEEPER-1277 */ if ((request.zxid & 0xffffffffffL) == 0xffffffffffL) { String msg = "zxid lower 32 bits have rolled over, forcing re-election, and therefore new epoch start"; shutdown(msg); throw new XidRolloverException(msg); }
从上面的代码能够看到,atom
Zookeeper 的 Leader 节点会throw new XidRolloverException(msg) 强制进行 re-election从新选举,操作系统
即服务会中止一段时间,在一些场景下,这种状况过于频繁是不能容忍的,那咱们来看看如何解决。
上面说了epoch增加速度慢到能够忽略它溢出的问题,那么能够从新设计 ZXID,
设计成高 24 位用于 epoch,低 40 位用于 事务 ID 增加。
咱们再来算一下:
2^40/(86400∗1000) ≈ 12725.8 即 12725.8/365 ≈ 34.9 年
1k/s ops 的状况下, 34.9 年以后才会进行一次强制选举。
设想不错,能够解决咱们的问题,那咱们继续。
从操做系统的底层来讲,对于32位操做系统,单次操做能处理的最长长度为32bit,而long类型8字节64bit,因此对long的读写都要两条指令才能完成(即每次读写64bit中的32bit)。
为何说这个,由于也许有人会把这个和 ZXID 的设计联想起来,上面的 ZOOKEEPER-2784里面也提到了这个问题。
However, i thought the ZXID is
long
type, reading and writing the long type (anddouble
type the same) in JVM, is divided into high 32bit and low 32bit part of the operation, and because theZXID
variable is not modified withvolatile
and is not boxed for the corresponding reference type (Long
/Double
), so it belongs to [non-atomic operation]
我大概翻译一下:
ZXID 是 long 类型,32 bit 的 JVM 在对 long 读写时(和 double 类型同样),是分为高 32 位和 低 32 位两部分进行操做的,因为 ZXID 变量没有用 volatile 修饰,且也没有装箱为对应的引用类型(Long / Double),属于非原子操做。
这位老哥担忧对 ZXID 从新设计时把高 32 位和 低 32 位改为高 24 位和 低 40 位,可能会存在并发的问题。
会不会有这个问题,咱们先来看看源码:
Iterator<Integer> iterator = servers.iterator(); long zxid = Long.valueOf(m.group(2)); int count = (int)zxid;// & 0xFFFFFFFFL; int epoch = (int)Long.rotateRight(zxid, 32);// >> 32;
注意这个& 0xFFFFFFFFL,实际上后面的代码还有不少这种按位与的操做,就不贴出来了。
翻了这一块的源码就能够知道,这个担忧是多余的,关于ZXID的全部操做都是位操做而不是“=”的赋值操做,它不会形成JVM级别的并发问题。
接下来咱们就用源码中“位与”的方式,把 32 为改为 40 位。
即:zxid按位于(&)0xffffffffffL(40位)得到zxid的后40位。
注意要把count以前的int类型改成long类型,由于int为32bit,long为64bit,此时count有40位因此换成long。
Iterator<Integer> iterator = servers.iterator(); long zxid = Long.valueOf(m.group(2)); // int count = (int)zxid;// & 0xFFFFFFFFL; // int epoch = (int)Long.rotateRight(zxid, 32);// >> 32; long count = zxid & 0xffffffffffL; int epoch = (int)Long.rotateRight(zxid, 40);// >> 40;
后面还有多处相似的地方要修改,就不一一列出来了,有兴趣的能够看这里github
zookeeper篇到这里就完结了,关于zookeeper你们还有什么想知道的能够留言,我以为有价值的话会再更新些新的文章。