最近的項目要依賴於一個分佈式計數器的實現,由於公司使用memcached歷史已久,因此就想到了使用memcached來做爲計數器。以前也用過memcached的incr操做,可是有人封裝好了,也沒有深究,本身測試起來,越到了問題。通過大半天的調試、查閱文檔、查看源碼,解決了問題,如今將收集到的信息整理一下。 git
incr/decr是memcached 1.2.4加入的原子性整數操做(changelog:2006-10-03)。這個功能經常使用於分佈式項目中的計數。 github
1.incr/decr在memcached中的保存方式是:字符串(十進制)表示的無符號64bit整數。 app
memcached的wiki中這樣描述: 分佈式
Increment and Decrement. If an item stored is the string representation of a 64bit integer, you may run incr or decr commands to modify that number. You may only incr by positive values, or decr by positive values. They does not accept negative values. memcached
爲了驗證,在memcached telnet終端中以下操做: 測試
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
seta 0 0 2
12
STORED
#設置key=a的項爲"12"
incr a 1
13
#incr 1 返回13
append a 0 0 1
3
STORED
#在a項後面增長一個字符'3'
get a
VALUE a 0 3
133
END
#a變爲133
incr a 1
134
#a incr 1後變爲134
|
能夠看到,在把a當作字符串進行append操做後,獲得字符串133,此時incr 1,變爲134,證實memcached內部確實incr項將保存爲字符串。 編碼
那麼,若是咱們使用二進制協議,將a寫入數字後,再使用incr會產生什麼結果呢?(使用Java語言,memcached客戶端爲:spymemcached-2.5)。 spa
1
2
3
4
5
6
|
memcachedClient.set(key, exp,51);
Object object = memcachedClient.get(key);
System.out.println("init "+ object);
longincr = memcachedClient.incr(key,1);
object = memcachedClient.get(key);
System.out.println("after incr "+ incr +" "+ (object));
|
輸出爲: 調試
1
2
|
init 51
after incr 4 52
|
是否是沒法理解?按道理,incr的返回值就是memcached中的最終結果,與get結果相同。結果一個返回4,一個返回52? server
等等,4和52,爲何看起來那麼眼熟?
查閱ascii表得知,4的ascii值恰好是52,難道真這麼巧,第二次get的時候,spymemcached客戶端將4的字符值當作整型展開,獲得了52?
不急,先使用telnet終端鏈接memcached,獲得:
1
2
3
|
get a
VALUE a 512 1
4
|
其中512是flag屬性。
這裏簡單提到一下memcached協議中的flag機制。flag是memcached除了key、value、expireTime以外額外保存的一個16bit(1.2.4以後爲32bit)值,它標誌着這個值的」類型「。這個值對於memcached-server是無心義的,它提供給client,來定義對value的處理方式(主要是編碼,也有些客戶端用flag做爲是否壓縮的依據)。
在spymemcached-client中,0爲字符串,512爲整數。因此這裏雖然值保存爲字符串4,可是spymemcached仍然將其當作整數解析,那麼就獲得了52!
因而咱們獲得一個教訓:初始化計數器的時候,請使用字符串memcachedClient.set(key, exp, "0"),或者自行封裝方法。不然可能獲得不可預知的結果!
2. incr/decr操做沒法刷新過時時間。
memcached的協議能夠看這裏。incr/decr操做沒法刷新過時時間,因此過時時間以初始化的時間爲準。
最開始覺得spy的memcacheClient的incr(String key, int by, long def, int exp)能夠刷新過時時間,後來才發現,此方法是封裝了incr/decr和add的組合操做。這個exp指的是,若incr失敗,則將def值add到此key,並使用這個過時時間exp,若是成功,過時時間不變!
所以,若是使用memcached做爲長期的計數器,必須用額外的機制定時刷新item。memcached協議提供了touch方法,只刷新時間,不對值做修改,最新的spymemcached 客戶端中提供了這個功能。
3. 若是對應值不存在,incr/decr會失敗,而不會從0開始計數。
telnet下輸入:
1
2
|
incr b 1
NOT_FOUND
|
返回NOT_FOUND,沒有incr成功。memcachedClient.incr(key,delta)調用以後,若key不存在,則返回-1。