theme: awesome-green
版本 | 日期 | 备注 |
---|---|---|
1.0 | 2020.4.19 | 文章首发 |
1.1 | 2021.6.23 | 标题从 |
最近在开发时偶尔会观测到zk报出BadVersionException
,后在搜索引起上得知了是乐观锁相关的问题,很快就解决了问题。不过学而不思则罔:无论是单体应用还是分布式系统,在运行过程中总要有一种机制来保证数据排他性。接下来,我们就来看看zk是如何实现这种机制的。
在此分析源码之前,我们需要了解zk节点的三种版本属性:
这些属性都可以在
StatPersisted
这个类里找到。
当相关的属性进行变更时,版本号则会+1。刚创建的节点,版本号为0,表示这个节点被更新过0次。
一般如果我们调用setData
,代码会这么写:
//Curator版本
//要求版本对比。当然,填-1服务端在接收时便不会去对比了
client.setData().withVersion(version).forPath(path, payload);
//不要求版本对比
client.setData().forPath(path, payload);
zookeeper的client代码非常简单:
/**
* 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
里的代码:
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典型使用场景数据并发竞争的情况较少(当然,你可以让它竞争很激烈,只是整体来看过程会变得较为耗时),且事务操作都是串行执行。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。