轉自:Simba_cheng
更新節點數據的方法:
- 同步方法:Stat setData(final String path, byte data[], int version)
- 異步方法:void setData(final String path, byte data[], int version, StatCallback cb, Object ctx)
參數說明:
- path:指定數據節點路徑
- data[]:一個字節數組,即需要使用該數據來覆蓋節點現在的數據內容
- version:指定節點的數據版本
- cb:注冊一個異步回調函數
- ctx:用於傳遞上下文信息的對象
其中:
version參數用於指定節點的數據版本,表名本次更新操作是針對指定的數據版本進行的。
指定數據版本更新的意義何在呢?
通俗的講"CAS":對於值V,每次更新前都會比對其值是否是預期值A,只有符合預期,才會將V原子化的更新到新值B
CAS具體解釋
zookeeper的setData接口中的version參數是CAS衍化來的
zookeeper每個節點都有數據版本的概念,在調用更新操作的時候,就可以添加version這個參數,該參數可以對應於CAS
原理中對的"預期值",表明是針對該數據版本進行更新。
形象一些說:
假如有一個客戶端試圖進行更新操作,它會攜帶上次獲取到的version值進行更新。
而如果在這段時間內,ZooKeeper服務器上該節點的數值恰好已經被其他客戶端更新了,那么其數據版本一定也會發生變化,
因此肯定與客戶端攜帶對的version無法匹配,於是便無法成功更新 -- 因此可以有效地避免一些分布式更新的並發問題。
Demo代碼:
1 public class TestSetData implements Watcher { 2 3 // 屏障,計數器 4 private static CountDownLatch downLatch = new CountDownLatch(1); 5 6 private static ZooKeeper zookeeper = null; 7 8 public static void main(String[] args) throws Exception { 9 10 zookeeper = new ZooKeeper("10.0.227.66:2181", 5000, new TestSetData()); 11 12 System.out.println("zookeeper.getState()1 : " + zookeeper.getState()); 13 14 try { 15 downLatch.await();// 在計數器未歸零之前,所有線程等待 16 } catch (Exception e) { 17 e.printStackTrace(); 18 } 19 20 System.out.println("zookeeper.getState()2 : " + zookeeper.getState()); 21 22 zookeeper.create("/cyx", "ccc".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 23 24 zookeeper.getData("/cyx", true, null); 25 26 // 第一次設置 27 Stat stat = zookeeper.setData("/cyx", "456".getBytes(), -1); 28 System.out.println(stat.getCzxid() + " , " + stat.getMzxid() + " , " + stat.getVersion()); 29 30 // 第二次設置 31 Stat stat2 = zookeeper.setData("/cyx", "789".getBytes(), -1); 32 System.out.println(stat2.getCzxid() + " , " + stat2.getMzxid() + " , " + stat2.getVersion()); 33 34 // 獲取第一次設置得到version,進行更新 35 try { 36 37 zookeeper.setData("/cyx", "123".getBytes(), stat.getVersion()); 38 39 } catch (Exception e) { 40 e.printStackTrace(); 41 } 42 43 } 44 45 @Override 46 public void process(WatchedEvent event) { 47 48 System.out.println("receive watched event : " + event); 49 50 if (KeeperState.SyncConnected == event.getState()) { 51 52 if (EventType.None == event.getType() && null == event.getPath()) { 53 downLatch.countDown();// 計數器-1 54 } 55 } 56 } 57 }
輸出結果:
zookeeper.getState()1 : CONNECTING receive watched event : WatchedEvent state:SyncConnected type:None path:null zookeeper.getState()2 : CONNECTED receive watched event : WatchedEvent state:SyncConnected type:NodeDataChanged path:/cyx 197568501464 , 197568501465 , 1 197568501464 , 197568501466 , 2 org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for /cyx
代碼編寫思路:
- 首先創建節點"/cyx"節點,設置節點參數"ccc"
- 接着我們第一次setData,更新節點參數為"456",同時獲取stat,此時version已經改變
- 然后我們再次setData,更新節點參數為"789",同時獲取stat,version也改變了
- 接着我們使用第一次獲取的version版本號,去setData。
- 然后就拋異常,因為第二次setData的時候,版本號已經更新,這時候,我們拿第一次的更新的版本號去更新,是沒法成功的。
解釋下setData時的"-1":
在ZooKeeper中,數據版本都是從0開始計數額,所以嚴格的講,"-1"不是一個合法得到數據版本,它僅僅是一個標示符。
如果客戶端傳入的版本參數是"-1",就是告訴zookeeper服務器,客戶端需要基於數據的最新版本進行更新操作。