關鍵詞:multiget hole,memcache
適用於:java,php
基礎知識背景:
1)multiget 是什么:
multiget 指的是從 memcache(或其他分布式緩存) 一次性獲得多個鍵值,一般由 memcached client 自行實現。如 PHP-memcache-client 提供了
Memcached::getMulti 函數。調用示范如下:
<?php
$items = array(
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3'
);
$m->setMulti($items);
$result = $m->getMulti(array('key1', 'key3', 'badkey'), $cas);
var_dump($result, $cas);
?>
2)”multiget hole“詳解:
『讓我們來模擬一下案發經過,看看到底發生了什么:我們使用 Multiget 一次性獲取100個鍵對應的數據。
系統最初只有一台 Memcached 服務器,隨着訪問量的增加,系統負載捉襟見肘,於是我們又增加了一台 Memcached 服務器,數據散列到兩台服務器上。
開始那100個鍵在兩台服務器上各有50個。
問題就在這里:原本只要訪問一台服務器就能獲取的數據,現在要訪問兩台服務器才能獲取;服務器加的越多,需要訪問的服務器就越多,所以問題不會改善,甚至還會惡化。
不過,作為被告方,Memcached官方開發人員對此進行了辯護:
請求多台服務器並不是問題的症結,真正的原因在於客戶端在請求多台服務器時是並行的還是串行的!問題是很多客戶端,包括Libmemcached在內,在處理Multiget多服務器請求時,使用的是串行的方式!也就是說,先請求一台服務器,然后等待響應結果,接着請求另一台,結果導致客戶端操作時間累加,請求堆積,性能下降。
如何解決這個棘手的問題呢?只要保證 Multiget 中的鍵只出現在一台服務器上即可!(注:事實上這可不容易做到。)
』
3)以前鄭昀在文章里說過,spymemcached 某版本又是如何實現 Multiget(即getBulk)的
- 給一組 key,[1,2,3,4,5]。
- 先算一下這些key都落在哪些節點上(通過 KetamaNodeLocator 的 public Iterator<MemcachedNode> getSequence(String k)。Now that we know how many servers it breaks down into.);
- 此時,得到一個map:<Node1,[1,3]>;<Node2,[2,4]>;<Node3,[5]>;
- 遍歷這個map,從每一個 mc node 讀出對應的 keys(即單節點的 multiget 操作);一個Node一個Node串行的;
- 拼成一個大map<key,value>返回。
這樣就是一個 node 復一個 node 串行檢索的,雖然做了優化,但是如果涉及的 mc nodes 數量多,線程勢必長時間阻塞在等待網絡資源返回上。(注:spymemcached 后來的版本不再按 node 串行輪詢,而是並行:第一步,將本次操作構造成一個針對每個 node 的 Operation 對象,加入連接對象中;第二步,在連接對象中,將所有的 node 操作放入 addedQueue 隊列,然后觸發 Selector 方式異步非阻塞的執行。)
現象:
某中心每天很多個讀取 memcache 鍵值超時,報錯如下:
Caused by: java.util.concurrent.ExecutionException: net.spy.memcached.internal. CheckedOperationTimeoutException: Operation timed out. - failing node: mcN.domain.nameat net.spy.memcached.internal.OperationFuture.get(OperationFuture.java:172)
at net.spy.memcached.internal.GetFuture.get(GetFuture.java:62)
分析:
在 memcache 集群節點較多情況下,
特別是在一次性獲取成百上千鍵值的極端場景面前,
服務端輕則請求超時,重則宕機。
無論是先計算 keys 都散列到哪些 mc nodes 上了,還是直接輪詢 memcached::get ,或者說並行提交給各個 mc nodes 然后異步等待,
假設每個 mc get 耗時2~3毫秒,一次性取 2000 個keys,都將阻塞線程長達2~6秒之久,這是身為服務所不能容忍的。
所以,必須約定,適度使用批量獲取鍵值功能,100個鍵值就到頂了,別因小失大。
當然,也有業務場景繞不開 multiget,那么,一是按照 facebook 所說,此時需要的是更多的 CPU,把緩存數據復制一份到另一個 memcache 集群上,一個集群負責讀一半的 keys;二是按照火丁所說,最好保證批量查的這批鍵值都在同一個 mc node 上。
參考資源:
1)火丁,2012,
memcache 二三事兒;
2)鄭昀,2013,
關於 Multiget hole:spymemcached對此的實現方法;
3)iteye,2012,
通過NIO實現Memcached multi get;
4)facebook,2009,
Facebook's Memcached Multiget Hole: More machines != More Capacity ;
贈圖幾枚:
死鎖分析