本文首发于 泊浮目的简书: https://www.jianshu.com/u/204...
版本 | 日期 | 备注 |
---|---|---|
1.0 | 2020.4.19 | 文章首发 |
最近在开发时偶尔会观测到zk报出BadVersionException
,后在搜索引发上得知了是乐观锁相关的问题,很快就解决了问题。不过学而不思则罔:不管是单体应用仍是分布式系统,在运行过程当中总要有一种机制来保证数据排他性。接下来,咱们就来看看zk是如何实现这种机制的。java
在此分析源码以前,咱们须要了解zk节点的三种版本属性:node
这些属性均可以在
StatPersisted
这个类里找到。
当相关的属性进行变动时,版本号则会+1。刚建立的节点,版本号为0,表示这个节点被更新过0次。数据库
通常若是咱们调用setData
,代码会这么写:服务器
//Curator版本 //要求版本对比。固然,填-1服务端在接收时便不会去对比了 client.setData().withVersion(version).forPath(path, payload); //不要求版本对比 client.setData().forPath(path, payload);
zookeeper的client代码很是简单:session
/** * The asynchronous version of setData. * * @see #setData(String, byte[], int) */ public void setData(final String path, byte data[], int version, StatCallback cb, Object ctx) { final String clientPath = path; PathUtils.validatePath(clientPath); final String serverPath = prependChroot(clientPath); RequestHeader h = new RequestHeader(); h.setType(ZooDefs.OpCode.setData); SetDataRequest request = new SetDataRequest(); request.setPath(serverPath); request.setData(data); request.setVersion(version); SetDataResponse response = new SetDataResponse(); cnxn.queuePacket(h, new ReplyHeader(), request, response, cb, clientPath, serverPath, ctx, null); }
以后version这个属性会被序列化到请求中,发送给服务端。并发
看下服务端的代码。从异常的名字,咱们能够很轻易的找到代码PrepRequestProcessor.pRequest2Txn
里的代码:async
case OpCode.setData: zks.sessionTracker.checkSession(request.sessionId, request.getOwner()); SetDataRequest setDataRequest = (SetDataRequest)record; if(deserialize) ByteBufferInputStream.byteBuffer2Record(request.request, setDataRequest); path = setDataRequest.getPath(); validatePath(path, request.sessionId); nodeRecord = getRecordForPath(path); checkACL(zks, nodeRecord.acl, ZooDefs.Perms.WRITE, request.authInfo); int newVersion = checkAndIncVersion(nodeRecord.stat.getVersion(), setDataRequest.getVersion(), path); request.setTxn(new SetDataTxn(path, setDataRequest.getData(), newVersion)); nodeRecord = nodeRecord.duplicate(request.getHdr().getZxid()); nodeRecord.stat.setVersion(newVersion); addChangeRecord(nodeRecord); break;
咱们看一下checkAndIncVersion
的逻辑:分布式
private static int checkAndIncVersion(int currentVersion, int expectedVersion, String path) throws KeeperException.BadVersionException { if (expectedVersion != -1 && expectedVersion != currentVersion) { throw new KeeperException.BadVersionException(path); } return currentVersion + 1; }
代码简单易懂:从zk里取出该节点的版本,若是请求须要对比(由客户端设置不为-1)则与节点目前的版本进行对比。源码分析
若是没有抛出异常,则这个版本号会被+1,并更新提交到队列里去,最后会更新到zk的内存数据库中去。设计
很显然,这是CAS技术的一种实现。那么为何要基于CAS实现锁呢?在此以前,咱们须要回顾乐观锁和悲观锁的适用场景:
咱们都知道,zk通常用于配置管理、DNS服务、分布式协同和组成员管理
,这意味着较少的数据并发竞争,而事务其实也是由leader服务器串行处理。显然,这符合乐观锁的使用场景,故此zk没有采用“笨重”的悲观锁来实现分布式数据的原子性操做。
在本文中,咱们得知zk的数据排他性机制实现是乐观锁。这么设计的缘由是zk典型使用场景数据并发竞争的状况较少(固然,你可让它竞争很激烈,只是总体来看过程会变得较为耗时),且事务操做都是串行执行。