本文首發於 泊浮目的簡書: 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典型使用場景數據併發競爭的狀況較少(固然,你可讓它競爭很激烈,只是總體來看過程會變得較爲耗時),且事務操做都是串行執行。