Memcached於1.2.4版本新增CAS(Check and Set)協議類同於Java併發的CAS(Compare and Swap)原子操作,處理同一item被多個線程更改過程的併發問題。
在Memcached中,每個key關聯有一個64-bit長度的long型惟一數值,表示該key對應value的版本號。這個數值由Memcached server產生,從1開始,且同一Memcached server不會重複。在兩種情況下這個版本數值會加1:1、新增一個key-value對;2、對某已有key對應的value值更新成功。刪除item版本值不會減小。
例如
CAS協議解決的問題
模擬多個Memcached client併發set同一個key的場景。如clientA想把當前key的value set爲"x",且操作成功;clientB卻把當前key的value值由"x"覆蓋set爲"y",這時clientA再根據key去取value時得到"y"而不是期望的"x",它使用這個值,但不知道這個值已經被其它線程修改過,就可能會出現問題。
CAS協議解決這種併發修改問題。有線程試圖修改當前key-value對的value時,先由gets方法得到item的版本號,操作完成提交數據時,使用cas方法謹慎變更,如果在本地對item操作過程中這個key-value對在Memcached server端被其它線程更改過,就放棄此次修改(樂觀鎖概念)。
- CASValue casValue = client.gets(key);
- //*****
- //本地的各種處理
- //*****
- CASResponse response = client.cas(key, newValue, casValue);
- //在我取數據時item的版本號是casValue.getCas(),所以提交時我期望item的版本號是沒有改變過的。如果被修改過,不是我取數據時的版本號,那麼Memcached server對這次提交什麼也不做,返回true或false由用戶自己來提出解決方案(什麼也不做或是重新獲取版本號,再次重試提交等)
併發環境下的正確性驗證
用多個Memcached client併發更改同一個key值,將value遞增,如果 操作次數-CAS失敗次數 = value增加的值,表示併發環境下CAS處理沒有問題。
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import net.spy.memcached.CASResponse;
- import net.spy.memcached.CASValue;
- import net.spy.memcached.MemcachedClient;
- public class CASTest {
- private static MemcachedClient client = null;
- static {
- try {
- client = new MemcachedClient(
- new InetSocketAddress("localhost", 11211));
- } catch (IOException o) {
- o.printStackTrace();
- }
- }
- public static void main(String[] args) throws Exception {
- //Firstly, the key should exist.
- //key is "number", value is Integer 1, 7845 is expire time
- client.set("number", 7845, 1);
- CASTest testObj = new CASTest();
- //start the multithread environment
- for (int i = 0; i < 10; i++) {
- testObj.new ThreadTest("Thread-" + (i + 1)).start();
- }
- }
- /**
- * Each thread runs many times
- */
- private class ThreadTest extends Thread {
- private MemcachedClient client = null;
- ThreadTest(String name) throws IOException {
- super(name);
- client = new MemcachedClient(
- new InetSocketAddress("localhost", 11211));
- }
- public void run() {
- int i = 0;
- int success = 0;
- while (i < 10) {
- i++;
- CASValue<Object> uniqueValue =client.gets("number");
- CASResponse response = client.cas("number",
- uniqueValue.getCas(), (Integer)uniqueValue.getValue() + 1);
- if (response.toString().equals("OK")) {
- success++;
- }
- System.out.println(Thread.currentThread().getName() + " " + i
- + " time " + " update oldValue : " + uniqueValue
- + " , result : " + response);
- }
- if (success < 10) {
- System.out.println(Thread.currentThread().getName()
- + " unsuccessful times : " + (10 - success ));
- }
- }
- }
- }
每次執行的結果都會不一樣,如其中某次的執行結果爲: 總共操作100次,衝突47次,且最後value由1漲到53,那麼表示驗證成功。
不足之處,誠請提出!