MemCache是一個自由、源碼開放、高性能、分布式的分布式內(nèi)存對象緩存系統(tǒng),用于動態(tài)Web應(yīng)用以減輕數(shù)據(jù)庫的負載。它通過在內(nèi)存中緩存數(shù)據(jù)和對象來減少讀取數(shù)據(jù)庫的次數(shù),從而提高了網(wǎng)站訪問的速度。MemCaChe是一個存儲鍵值對的HashMap,在內(nèi)存中對任意的數(shù)據(jù)(比如字符串、對象等)所使用的key-value存儲,數(shù)據(jù)可以來自數(shù)據(jù)庫調(diào)用、API調(diào)用,或者頁面渲染的結(jié)果。MemCache設(shè)計理念就是小而強大,它簡單的設(shè)計促進了快速部署、易于開發(fā)并解決面對大規(guī)模的數(shù)據(jù)緩存的許多難題,而所開放的API使得MemCache能用于Java、C/C++/C#、Perl、Python、PHP、Ruby等大部分流行的程序語言。
另外,說一下MemCache和MemCached的區(qū)別:
1、MemCache是項目的名稱
2、MemCached是MemCache服務(wù)器端可以執(zhí)行文件的名稱
MemCache的官方網(wǎng)站為http://memcached.org/
MemCache訪問模型
為了加深理解,我模仿著原阿里技術(shù)專家李智慧老師《大型網(wǎng)站技術(shù)架構(gòu) 核心原理與案例分析》一書MemCache部分,自己畫了一張圖:

特別澄清一個問題,MemCache雖然被稱為”分布式緩存”,但是MemCache本身完全不具備分布式的功能,MemCache集群之間不會相互通信(與之形成對比的,比如JBoss
Cache,某臺服務(wù)器有緩存數(shù)據(jù)更新時,會通知集群中其他機器更新緩存或清除緩存數(shù)據(jù)),所謂的”分布式”,完全依賴于客戶端程序的實現(xiàn),就像上面這張圖的流程一樣。
同時基于這張圖,理一下MemCache一次寫緩存的流程:
1、應(yīng)用程序輸入需要寫緩存的數(shù)據(jù)
2、API將Key輸入路由算法模塊,路由算法根據(jù)Key和MemCache集群服務(wù)器列表得到一臺服務(wù)器編號
3、由服務(wù)器編號得到MemCache及其的ip地址和端口號
4、API調(diào)用通信模塊和指定編號的服務(wù)器通信,將數(shù)據(jù)寫入該服務(wù)器,完成一次分布式緩存的寫操作
讀緩存和寫緩存一樣,只要使用相同的路由算法和服務(wù)器列表,只要應(yīng)用程序查詢的是相同的Key,MemCache客戶端總是訪問相同的客戶端去讀取數(shù)據(jù),只要服務(wù)器中還緩存著該數(shù)據(jù),就能保證緩存命中。
這種MemCache集群的方式也是從分區(qū)容錯性的方面考慮的,假如Node2宕機了,那么Node2上面存儲的數(shù)據(jù)都不可用了,此時由于集群中Node0和Node1還存在,下一次請求Node2中存儲的Key值的時候,肯定是沒有命中的,這時先從數(shù)據(jù)庫中拿到要緩存的數(shù)據(jù),然后路由算法模塊根據(jù)Key值在Node0和Node1中選取一個節(jié)點,把對應(yīng)的數(shù)據(jù)放進去,這樣下一次就又可以走緩存了,這種集群的做法很好,但是缺點是成本比較大。
一致性Hash算法
從上面的圖中,可以看出一個很重要的問題,就是對服務(wù)器集群的管理,路由算法至關(guān)重要,就和負載均衡算法一樣,路由算法決定著究竟該訪問集群中的哪臺服務(wù)器,先看一個簡單的路由算法。
1、余數(shù)Hash
比方說,字符串str對應(yīng)的HashCode是50、服務(wù)器的數(shù)目是3,取余數(shù)得到1,str對應(yīng)節(jié)點Node1,所以路由算法把str路由到Node1服務(wù)器上。由于HashCode隨機性比較強,所以使用余數(shù)Hash路由算法就可以保證緩存數(shù)據(jù)在整個MemCache服務(wù)器集群中有比較均衡的分布。
如果不考慮服務(wù)器集群的伸縮性(什么是伸縮性,請參見大型網(wǎng)站架構(gòu)學(xué)習(xí)筆記),那么余數(shù)Hash算法幾乎可以滿足絕大多數(shù)的緩存路由需求,但是當(dāng)分布式緩存集群需要擴容的時候,就難辦了。
就假設(shè)MemCache服務(wù)器集群由3臺變?yōu)?臺吧,更改服務(wù)器列表,仍然使用余數(shù)Hash,50對4的余數(shù)是2,對應(yīng)Node2,但是str原來是存在Node1上的,這就導(dǎo)致了緩存沒有命中。如果這么說不夠明白,那么不妨舉個例子,原來有HashCode為0~19的20個數(shù)據(jù),那么:

現(xiàn)在我擴容到4臺,加粗標(biāo)紅的表示命中:

如果我擴容到20+的臺數(shù),只有前三個HashCode對應(yīng)的Key是命中的,也就是15%。當(dāng)然這只是個簡單例子,現(xiàn)實情況肯定比這個復(fù)雜得多,不過足以說明,使用余數(shù)Hash的路由算法,在擴容的時候會造成大量的數(shù)據(jù)無法正確命中(其實不僅僅是無法命中,那些大量的無法命中的數(shù)據(jù)還在原緩存中在被移除前占據(jù)著內(nèi)存)。這個結(jié)果顯然是無法接受的,在網(wǎng)站業(yè)務(wù)中,大部分的業(yè)務(wù)數(shù)據(jù)度操作請求上事實上是通過緩存獲取的,只有少量讀操作會訪問數(shù)據(jù)庫,因此數(shù)據(jù)庫的負載能力是以有緩存為前提而設(shè)計的。當(dāng)大部分被緩存了的數(shù)據(jù)因為服務(wù)器擴容而不能正確讀取時,這些數(shù)據(jù)訪問的壓力就落在了數(shù)據(jù)庫的身上,這將大大超過數(shù)據(jù)庫的負載能力,嚴重的可能會導(dǎo)致數(shù)據(jù)庫宕機。
這個問題有解決方案,解決步驟為:
(1)在網(wǎng)站訪問量低谷,通常是深夜,技術(shù)團隊加班,擴容、重啟服務(wù)器
(2)通過模擬請求的方式逐漸預(yù)熱緩存,使緩存服務(wù)器中的數(shù)據(jù)重新分布
2、一致性Hash算法
一致性Hash算法通過一個叫做一致性Hash環(huán)的數(shù)據(jù)結(jié)構(gòu)實現(xiàn)Key到緩存服務(wù)器的Hash映射,看一下我自己畫的一張圖:

具體算法過程為:先構(gòu)造一個長度為232的整數(shù)環(huán)(這個環(huán)被稱為一致性Hash環(huán)),根據(jù)節(jié)點名稱的Hash值(其分布為[0, 232-1])將緩存服務(wù)器節(jié)點放置在這個Hash環(huán)上,然后根據(jù)需要緩存的數(shù)據(jù)的Key值計算得到其Hash值(其分布也為[0,
232-1]),然后在Hash環(huán)上順時針查找距離這個Key值的Hash值最近的服務(wù)器節(jié)點,完成Key到服務(wù)器的映射查找。
就如同圖上所示,三個Node點分別位于Hash環(huán)上的三個位置,然后Key值根據(jù)其HashCode,在Hash環(huán)上有一個固定位置,位置固定下之后,Key就會順時針去尋找離它最近的一個Node,把數(shù)據(jù)存儲在這個Node的MemCache服務(wù)器中。使用Hash環(huán)如果加了一個節(jié)點會怎么樣,看一下:
一個節(jié)點會怎么樣,看一下:

看到我加了一個Node4節(jié)點,只影響到了一個Key值的數(shù)據(jù),本來這個Key值應(yīng)該是在Node1服務(wù)器上的,現(xiàn)在要去Node4了。采用一致性Hash算法,的確也會影響到整個集群,但是影響的只是加粗的那一段而已,相比余數(shù)Hash算法影響了遠超一半的影響率,這種影響要小得多。更重要的是,集群中緩存服務(wù)器節(jié)點越多,增加節(jié)點帶來的影響越小,很好理解。換句話說,隨著集群規(guī)模的增大,繼續(xù)命中原有緩存數(shù)據(jù)的概率會越來越大,雖然仍然有小部分數(shù)據(jù)緩存在服務(wù)器中不能被讀到,但是這個比例足夠小,即使訪問數(shù)據(jù)庫,也不會對數(shù)據(jù)庫造成致命的負載壓力。
至于具體應(yīng)用,這個長度為232的一致性Hash環(huán)通常使用二叉查找樹實現(xiàn),至于二叉查找樹,就是算法的問題了,可以自己去查詢相關(guān)資料。
MemCache實現(xiàn)原理
首先要說明一點,MemCache的數(shù)據(jù)存放在內(nèi)存中,存放在內(nèi)存中個人認為意味著幾點:
1、訪問數(shù)據(jù)的速度比傳統(tǒng)的關(guān)系型數(shù)據(jù)庫要快,因為Oracle、MySQL這些傳統(tǒng)的關(guān)系型數(shù)據(jù)庫為了保持數(shù)據(jù)的持久性,數(shù)據(jù)存放在硬盤中,IO操作速度慢
2、MemCache的數(shù)據(jù)存放在內(nèi)存中同時意味著只要MemCache重啟了,數(shù)據(jù)就會消失
3、既然MemCache的數(shù)據(jù)存放在內(nèi)存中,那么勢必受到機器位數(shù)的限制,這個之前的文章寫過很多次了,32位機器最多只能使用2GB的內(nèi)存空間,64位機器可以認為沒有上限
然后我們來看一下MemCache的原理,MemCache最重要的莫不是內(nèi)存分配的內(nèi)容了,MemCache采用的內(nèi)存分配方式是固定空間分配,還是自己畫一張圖說明:

這張圖片里面涉及了slab_class、slab、page、chunk四個概念,它們之間的關(guān)系是:
1、MemCache將內(nèi)存空間分為一組slab
2、每個slab下又有若干個page,每個page默認是1M,如果一個slab占用100M內(nèi)存的話,那么這個slab下應(yīng)該有100個page
3、每個page里面包含一組chunk,chunk是真正存放數(shù)據(jù)的地方,同一個slab里面的chunk的大小是固定的
4、有相同大小chunk的slab被組織在一起,稱為slab_class
MemCache內(nèi)存分配的方式稱為allocator,slab的數(shù)量是有限的,幾個、十幾個或者幾十個,這個和啟動參數(shù)的配置相關(guān)。
MemCache中的value過來存放的地方是由value的大小決定的,value總是會被存放到與chunk大小最接近的一個slab中,比如slab[1]的chunk大小為80字節(jié)、slab[2]的chunk大小為100字節(jié)、slab[3]的chunk大小為128字節(jié)(相鄰slab內(nèi)的chunk基本以1.25為比例進行增長,MemCache啟動時可以用-f指定這個比例),那么過來一個88字節(jié)的value,這個value將被放到2號slab中。放slab的時候,首先slab要申請內(nèi)存,申請內(nèi)存是以page為單位的,所以在放入第一個數(shù)據(jù)的時候,無論大小為多少,都會有1M大小的page被分配給該slab。申請到page后,slab會將這個page的內(nèi)存按chunk的大小進行切分,這樣就變成了一個chunk數(shù)組,最后從這個chunk數(shù)組中選擇一個用于存儲數(shù)據(jù)。
如果這個slab中沒有chunk可以分配了怎么辦,如果MemCache啟動沒有追加-M(禁止LRU,這種情況下內(nèi)存不夠會報Out Of
Memory錯誤),那么MemCache會把這個slab中最近最少使用的chunk中的數(shù)據(jù)清理掉,然后放上最新的數(shù)據(jù)。針對MemCache的內(nèi)存分配及回收算法,總結(jié)三點:
1、MemCache的內(nèi)存分配chunk里面會有內(nèi)存浪費,88字節(jié)的value分配在128字節(jié)(緊接著大的用)的chunk中,就損失了30字節(jié),但是這也避免了管理內(nèi)存碎片的問題
2、MemCache的LRU算法不是針對全局的,是針對slab的
3、應(yīng)該可以理解為什么MemCache存放的value大小是限制的,因為一個新數(shù)據(jù)過來,slab會先以page為單位申請一塊內(nèi)存,申請的內(nèi)存最多就只有1M,所以value大小自然不能大于1M了
再總結(jié)MemCache的特性和限制
上面已經(jīng)對于MemCache做了一個比較詳細的解讀,這里再次總結(jié)MemCache的限制和特性:
1、MemCache中可以保存的item數(shù)據(jù)量是沒有限制的,只要內(nèi)存足夠
2、MemCache單進程在32位機中最大使用內(nèi)存為2G,這個之前的文章提了多次了,64位機則沒有限制
3、Key最大為250個字節(jié),超過該長度無法存儲
4、單個item最大數(shù)據(jù)是1MB,超過1MB的數(shù)據(jù)不予存儲
5、MemCache服務(wù)端是不安全的,比如已知某個MemCache節(jié)點,可以直接telnet過去,并通過flush_all讓已經(jīng)存在的鍵值對立即失效
6、不能夠遍歷MemCache中所有的item,因為這個操作的速度相對緩慢且會阻塞其他的操作
7、MemCache的高性能源自于兩階段哈希結(jié)構(gòu):第一階段在客戶端,通過Hash算法根據(jù)Key值算出一個節(jié)點;第二階段在服務(wù)端,通過一個內(nèi)部的Hash算法,查找真正的item并返回給客戶端。從實現(xiàn)的角度看,MemCache是一個非阻塞的、基于事件的服務(wù)器程序
8、MemCache設(shè)置添加某一個Key值的時候,傳入expiry為0表示這個Key值永久有效,這個Key值也會在30天之后失效,見memcache.c的源代碼:
[js]view plaincopy
#define?REALTIME_MAXDELTA?60*60*24*30
staticrel_time_t?realtime(consttime_t?exptime)?{
if(exptime?==?0)return0;
if(exptime?>?REALTIME_MAXDELTA)?{
if(exptime?<=?process_started)
return(rel_time_t)1;
return(rel_time_t)(exptime?-?process_started);
}else{
return(rel_time_t)(exptime?+?current_time);
}
}
這個失效的時間是memcache源碼里面寫的,開發(fā)者沒有辦法改變MemCache的Key值失效時間為30天這個限制
MemCache指令匯總
上面說過,已知MemCache的某個節(jié)點,直接telnet過去,就可以使用各種命令操作MemCache了,下面看下MemCache有哪幾種命令:
命 ? ?令作 ? ?用
get返回Key對應(yīng)的Value值
add添加一個Key值,沒有則添加成功并提示STORED,有則失敗并提示NOT_STORED
set無條件地設(shè)置一個Key值,沒有就增加,有就覆蓋,操作成功提示STORED
replace按照相應(yīng)的Key值替換數(shù)據(jù),如果Key值不存在則會操作失敗
stats返回MemCache通用統(tǒng)計信息(下面有詳細解讀)
stats items返回各個slab中item的數(shù)目和最老的item的年齡(最后一次訪問距離現(xiàn)在的秒數(shù))
stats slabs返回MemCache運行期間創(chuàng)建的每個slab的信息(下面有詳細解讀)
version返回當(dāng)前MemCache版本號
flush_all清空所有鍵值,但不會刪除items,所以此時MemCache依舊占用內(nèi)存
quit關(guān)閉連接
stats指令解讀
stats是一個比較重要的指令,用于列出當(dāng)前MemCache服務(wù)器的狀態(tài),拿一組數(shù)據(jù)舉個例子:
[js]view plaincopy
STAT?pid?1023
STAT?uptime?21069937
STAT?time?1447235954
STAT?version?1.4.5
STAT?pointer_size?64
STAT?rusage_user?1167.020934
STAT?rusage_system?3346.933170
STAT?curr_connections?29
STAT?total_connections?21
STAT?connection_structures?49
STAT?cmd_get?49
STAT?cmd_set?7458
STAT?cmd_flush?0
STAT?get_hits?7401
STAT?get_misses?57
..(delete、incr、decr、cas的hits和misses數(shù),cas還多一個badval)
STAT?auth_cmds?0
STAT?auth_errors?0
STAT?bytes_read?22026555
STAT?bytes_written?8930466
STAT?limit_maxbytes?4134304000
STAT?accepting_conns?1
STAT?listen_disabled_num?0
STAT?threads?4
STAT?bytes?151255336
STAT?current_items?57146
STAT?total_items?580656
STAT?evicitions?0
這些參數(shù)反映著MemCache服務(wù)器的基本信息,它們的意思是:
參 ?數(shù) ?名作 ? ? ?用
pidMemCache服務(wù)器的進程id
uptime服務(wù)器已經(jīng)運行的秒數(shù)
time服務(wù)器當(dāng)前的UNIX時間戳
versionMemCache版本
pointer_size當(dāng)前操作系統(tǒng)指針大小,反映了操作系統(tǒng)的位數(shù),64意味著MemCache服務(wù)器是64位的
rusage_user進程的累計用戶時間
rusage_system進程的累計系統(tǒng)時間
curr_connections當(dāng)前打開著的連接數(shù)
total_connections當(dāng)服務(wù)器啟動以后曾經(jīng)打開過的連接數(shù)
connection_structures服務(wù)器分配的連接構(gòu)造數(shù)
cmd_getget命令總請求次數(shù)
cmd_setset命令總請求次數(shù)
cmd_flushflush_all命令總請求次數(shù)
get_hits總命中次數(shù),重要,緩存最重要的參數(shù)就是緩存命中率,以get_hits / (get_hits + get_misses)表示,比如這個緩存命中率就是99.2%
get_misses總未命中次數(shù)
auth_cmds認證命令的處理次數(shù)
auth_errors認證失敗的處理次數(shù)
bytes_read總讀取的字節(jié)數(shù)
bytes_written總發(fā)送的字節(jié)數(shù)
limit_maxbytes分配給MemCache的內(nèi)存大?。▎挝粸樽止?jié))
accepting_conns是否已經(jīng)達到連接的最大值,1表示達到,0表示未達到
listen_disabled_num統(tǒng)計當(dāng)前服務(wù)器連接數(shù)曾經(jīng)達到最大連接的次數(shù),這個次數(shù)應(yīng)該為0或者接近于0,如果這個數(shù)字不斷增長, 就要小心我們的服務(wù)了
threads當(dāng)前MemCache總線程數(shù),由于MemCache的線程是基于事件驅(qū)動機制的,因此不會一個線程對應(yīng)一個用戶請求
bytes當(dāng)前服務(wù)器存儲的items總字節(jié)數(shù)
current_items當(dāng)前服務(wù)器存儲的items總數(shù)量
total_items自服務(wù)器啟動以后存儲的items總數(shù)量
stats slab指令解讀
如果對上面的MemCache存儲機制比較理解了,那么我們來看一下各個slab中的信息,還是拿一組數(shù)據(jù)舉個例子:
[js]view plaincopy
1?STAT1:chunk_size?96
2?...
3?STAT?2:chunk_size?144
4?STAT?2:chunks_per_page?7281
5?STAT?2:total_pages?7
6?STAT?2:total_chunks?50967
7?STAT?2:used_chunks?45197
8?STAT?2:free_chunks?1
9?STAT?2:free_chunks_end?5769
10?STAT?2:mem_requested?6084638
11?STAT?2:get_hits?48084
12?STAT?2:cmd_set?59588271
13?STAT?2:delete_hits?0
14?STAT?2:incr_hits?0
15?STAT?2:decr_hits?0
16?STAT?2:cas_hits?0
17?STAT?2:cas_badval?0
18?...
19?STAT?3:chunk_size?216
20?...
首先看到,第二個slab的chunk_size(144)/第一個slab的chunk_size(96)=1.5,第三個slab的chunk_size(216)/第二個slab的chunk_size(144)=1.5,可以確定這個MemCache的增長因子是1.5,chunk_size以1.5倍增長。然后解釋下字段的含義:
參 ?數(shù) ?名作 ? ? ?用
chunk_size當(dāng)前slab每個chunk的大小,單位為字節(jié)
chunks_per_page每個page可以存放的chunk數(shù)目,由于每個page固定為1M即1024*1024字節(jié),所以這個值就是(1024*1024/chunk_size)
total_pages分配給當(dāng)前slab的page總數(shù)
total_chunks當(dāng)前slab最多能夠存放的chunk數(shù),這個值是total_pages*chunks_per_page
used_chunks已經(jīng)被分配給存儲對象的chunks數(shù)目
free_chunks曾經(jīng)被使用過但是因為過期而被回收的chunk數(shù)
free_chunks_end新分配但還沒有被使用的chunk數(shù),這個值不為0則說明當(dāng)前slab從來沒有出現(xiàn)過容量不夠的時候
mem_requested當(dāng)前slab中被請求用來存儲數(shù)據(jù)的內(nèi)存空間字節(jié)總數(shù),(total_chunks*chunk_size)-mem_requested表示有多少內(nèi)存在當(dāng)前slab中是被閑置的,這包括未用的slab+使用的slab中浪費的內(nèi)存
get_hits當(dāng)前slab中命中的get請求數(shù)
cmd_set當(dāng)前slab中接收的所有set命令請求數(shù)
delete_hits當(dāng)前slab中命中的delete請求數(shù)
incr_hits當(dāng)前slab中命中的incr請求數(shù)
decr_hits當(dāng)前slab中命中的decr請求數(shù)
cas_hits當(dāng)前slab中命中的cas請求數(shù)
cas_badval當(dāng)前slab中命中但是更新失敗的cas請求數(shù)
看到這個命令的輸出量很大,所有信息都很有作用。舉個例子吧,比如第一個slab中使用的chunks很少,第二個slab中使用的chunks很多,這時就可以考慮適當(dāng)增大MemCache的增長因子了,讓一部分數(shù)據(jù)落到第一個slab中去,適當(dāng)平衡兩個slab中的內(nèi)存,避免空間浪費。
MemCache的Java實現(xiàn)實例
講了這么多,作為一個Java程序員,怎么能不寫寫MemCache的客戶端的實現(xiàn)呢?MemCache的客戶端有很多第三方j(luò)ar包提供了實現(xiàn),其中比較好的當(dāng)屬XMemCached了,XMemCached具有效率高、IO非阻塞、資源耗費少、支持完整的協(xié)議、允許設(shè)置節(jié)點權(quán)重、允許動態(tài)增刪節(jié)點、支持JMX、支持與Spring框架集成、使用連接池、可擴展性好等諸多優(yōu)點,因而被廣泛使用。這里利用XMemCache寫一個簡單的MemCache客戶單實例,也沒有驗證過,純屬拋磚引玉:
[js]view plaincopy
publicclassMemCacheManager
{
privatestaticMemCacheManager?instance?=newMemCacheManager();
/**?XMemCache允許開發(fā)者通過設(shè)置節(jié)點權(quán)重來調(diào)節(jié)MemCache的負載,設(shè)置的權(quán)重越高,該MemCache節(jié)點存儲的數(shù)據(jù)越多,負載越大?*/
privatestaticMemcachedClientBuilder?mcb?=
newXMemcachedClientBuilder(AddrUtil.getAddresses("127.0.0.1:11211?127.0.0.2:11211?127.0.0.3:11211"),newint[]{1,?3,?5});
privatestaticMemcachedClient?mc?=null;
/**?初始化加載客戶端MemCache信息?*/
static
{
mcb.setCommandFactory(newBinaryCommandFactory());
//?使用二進制文件
mcb.setConnectionPoolSize(10);
//?連接池個數(shù),即客戶端個數(shù)
try
{
mc?=?mcb.build();
}
catch(IOException?e)
{
e.printStackTrace();
}
}
privateMemCacheManager()
{
}
publicMemCacheManager?getInstance()
{
returninstance;
}
/**?向MemCache服務(wù)器設(shè)置數(shù)據(jù)?*/
publicvoidset(String?key,intexpiry,?Object?obj)throwsException
{
mc.set(key,?expiry,?obj);
}
/**?從MemCache服務(wù)器獲取數(shù)據(jù)?*/
publicObject?get(String?key)throwsException
{
returnmc.get(key);
}
/**
*?MemCache通過compare?and?set即cas協(xié)議實現(xiàn)原子更新,類似樂觀鎖,每次請求存儲某個數(shù)據(jù)都要附帶一個cas值,MemCache
*?比對這個cas值與當(dāng)前存儲數(shù)據(jù)的cas值是否相等,如果相等就覆蓋老數(shù)據(jù),如果不相等就認為更新失敗,這在并發(fā)環(huán)境下特別有用
*/
publicbooleanupdate(String?key,?Integer?i)throwsException
{
GetsResponse?result?=?mc.gets(key);
longcas?=?result.getCas();
//?嘗試更新key對應(yīng)的value
if(!mc.cas(key,?0,?i,?cas))
{
returnfalse;
}
returntrue;
}
}
來源:http://www.csdn.net/article/2016-03-16/2826609