memcached是一個分布式的緩存系統,且其分布式是一種“輕量級”的分布式,完全依賴用戶端庫來實作,libmemcached就是一個開源的C/C++庫。
使用libmemcached的C/C++ API用戶端庫資料及官方資料都很少,且網絡上存在的C/C++ libmemcached執行個體都是采用的MOD的分布式算法,其缺點顯而易見,當存在失效的memcached server或重新加入新的server時,容易造成系統的“震蕩”。libmemcached本身已經支援一緻性hash算法,一緻性算法在處理“加入”或“删除”server方面具有優良的特性,這裡就不具體分析了,請查閱一緻性算法相關資料。
今天對libmemcached-1.0.2版本的使用進行一個簡單的測試,以使應用支援dead server的自動隔離和自動連接配接。
libmemcached的C/C++ API使用及測試執行個體如下:
[cpp] view plaincopy
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <libmemcached/memcached.h>
int main(int argc, char *argv[])
{
memcached_st *memc;
memcached_return rc;
memcached_server_st *servers;
//connect multi server
memc = memcached_create(NULL);
servers = memcached_server_list_append(NULL, (char*)"localhost", 11211, &rc);
servers = memcached_server_list_append(servers, (char*)"localhost", 30000, &rc);
rc = memcached_server_push(memc, servers);
memcached_server_free(servers);
memcached_behavior_set(memc,MEMCACHED_BEHAVIOR_DISTRIBUTION,MEMCACHED_DISTRIBUTION_CONSISTENT);
memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, 20) ;
// memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_REMOVE_FAILED_SERVERS, 1) ; // 同時設定MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT 和 MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS
memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, 5) ;
memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS, true) ;
int time_sl = 0 ;
int times = 0 ;
while(times++<100000)
{
//save data
const char *keys[]= {"key1", "key2", "key3","key4"};
const size_t key_length[]= {4, 4, 4, 4};
char *values[] = {"This is 1 first value", "This is 2 second value", "This is 3 third value"," this is 4 forth value"};
size_t val_length[]= {21, 22, 21, 22};
int i = 0;
for (; i < 4; i++)
{
rc = memcached_set(memc, keys[i], key_length[i], values[i], val_length[i], (time_t)180,(uint32_t)0); printf("key: %s rc:%s\n", keys[i], memcached_strerror(memc, rc)); // 輸出狀态
}
printf("time: %d\n", time_sl++) ;
sleep(1) ;
}
//free
memcached_free(memc);
return 0;
}
執行個體非常簡單,但包含了使用libmemcached的基本流程:
1)建立memcached_st結構;
2)添加memcached server;
3)設定libmemcached庫的一些屬性(hash算法,重試次數及重試時間等);
4)調用基本的API(如get,set等)...;
5)釋放memcached_st結構;
本執行個體的關鍵在于3)中設定libmemcached庫的屬性。
隻有一緻性算法支援dead server的自動隔離和自動連接配接,參看run_distribution函數中隻有分布式算法為MEMCACHED_DISTRIBUTION_CONSISTENT*類的才調用update_continuum進行更新,其它都沒有任何操作,是以這裡設定了memcached_behavior_set(memc,MEMCACHED_BEHAVIOR_DISTRIBUTION,MEMCACHED_DISTRIBUTION_CONSISTENT);
run_distribution函數為自動隔離dead server的代碼,其主要調用在backoff_handling函數中:
調用條件同時滿足:server->server_failure_counter >= server->root->server_failure_limit 和memcached_st結構的flag設定了MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS标志,如果不設定MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS标志,則每次set/get等操作都試圖連接配接dead server。
在函數update_continuum中,
next_retry小于目前時間時,則表示标志目前server有效,如果不設定預設為0,在backoff_handling函數中設定為1,永遠小于目前時間,是以即使失效也不會自動剔除。MEMCACHED_BEHAVIOR_DEAD_TIMEOUT标志就是設定它的重試時間。
實驗過程:
一、初始階段:在同一虛拟機開啟2個memcached server(端口分别為11211和30000)
# memcached -vv -u root -p 11211
# memcached -vv -u root -p 30000
二、啟動測試程式
其中下面兩行,表示目前servers包括2個:分别為:localhost:11211和localhost:30000,1和160分别表示weight和在一緻性算法中每個server在圓上的點數。
ketama_weighted:localhost|11211|1|160
ketama_weighted:localhost|30000|1|160
同樣可以看出:key1、key2和key3映射到localhost:11211上,key4映射到localhost:30000上。
正常情況下,會一直處于此狀态下,下面模拟存在server down掉。
三、memcached server 失效和恢複
起始是使用libmemcached-1.0.2進行的測試,發現存在明顯的錯誤(至少2處),如果還沒有更新或使用1.0.2版本的,那麼可以直接使用1.0.3版本即可(寫此文時的最新版本),否則需要盡快更新,很快官方釋出了libmemcached-1.0.3版本,測試基本正常。
1、libmemcached-1.0.2版本測試圖:
1)關閉memcached server,測試set失敗情況
在time:1339時刻停止了memcached server localhost:30000伺服器,memcached_set結果傳回SERVER HAS FAILED AND IS DISABLED UNTIL TIMED RETRY(使用memcached_strerror轉換的錯誤資訊),此時set、get等并不能實作重新映射到新的server,是以對于之前映射到localhost:11211上的資料,如果此server不恢複,将一直會set失敗,get同樣失敗,不能實作dead server的自動剔除(這是個問題)。
2)重新啟動memcached server,測試重新連接配接情況:
在time:1372時刻啟動memcached server,在大緻time:1381或1382時刻重新set key4成功,時間大緻為20s,即MEMCACHED_BEHAVIOR_RETRY_TIMEOUT設定時間,也就是它的逾時重試時間,當到達這個時間後會重新連接配接dead的server。
3)重新關閉memcached server,測試永久失效
在time:1386時刻,關閉localhost:30000,此時key4設定失敗,直到time:1471時刻,ketama_weighted:localhost|11211|1|160表示目前隻有一個memcached server有效,且localhost:30000被标記為DEAD,之後key4映射到localhost:11211上。經過時間大緻為:1471-1386=85s。
經測試,之後即使重新啟動localhost:30000伺服器,永遠也不能自動連接配接進來了(這是個問題)。
2、源碼修改之後測試圖:
1)關閉memcached server,測試重新映射情況
在time:10時刻,關閉localhost:30000,則key4設定失敗,從失敗次數可以看出失敗5次後,localhost:30000被自動剔除,key4重新映射到localhost:11211上,這裡的5就是測試用例中設定的:
memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, 5) ;
在time:561時刻啟動localhost:30000,在time:580時刻,localhost:30000自動連接配接,key4重新映射到其上,時間大概20s,即為設定的 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, 20) ;
經測試,即使localhost:30000失效更長的時候,每個MEMCACHED_BEHAVIOR_RETRY_TIMEOUT時間都會重新嘗試連接配接它,如果連接配接成功則自動恢複其映射。
3)在重試次數(這裡為5次)内,如果memcached server恢複,則直接set/get資料成功
上圖:time:548時刻,關閉localhost:30000; 中圖:time:552時刻,重試4次時,重新開機啟動localhost:30000; 下圖:time:552時刻,成功連接配接到localhost:30000,set成功;
基于上面1.0.2版本提到的2個問題(或許有更多,或許稱為不足),經過簡單修改:
第一個問題:backoff_handling函數中 server->server_failure_counter隻增不減,導緻一旦到達重試次數,它狀态将不能恢複。這裡将其重置為0:server->server_failure_counter= 0;
是以這裡的重試次數與通常我們了解的重試次數是不同的,這裡是重試時間的次數,1.0.2版本中在”重試時間*重試次數“時間段内,任何映射到dead server上面的資料都失敗,經過”重試時間*重試次數“的時間後,server自動隔離,此時設定成功的時候為”重試時間“,是以多數情況下,這些資料都處于不可用狀态,導緻性能很低。
對第二個問題修改的總的原則是:快隔離,慢恢複!快隔離:如果存在server失效,則迅速将其自動剔除;避免set及get等操作失敗持續時間較長(通常memcached應用于密集型操作set/get,是以快隔離還算合理)。慢恢複:失效的server啟動後,并不是立即進行連接配接,需要經過MEMCACHED_BEHAVIOR_RETRY_TIMEOUT時間,以避免剛剛重新映射的資料立即失效(從理論來講也不一定好,需要具體問題具體分析)。然後将”重試次數“了解為set、get等操作失敗的次數,我的修改方案并不優雅,隻是功能實作而已,這裡就不貼圖了,如果需要請自行查閱修改後的源碼。
上述版本在源碼1.0.3版本中并不能獲得預期的效果,在上述代碼基礎上,需要設定如下參數:
memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_DEAD_TIMEOUT, 20) ;
具體的測試類似于1.0.2版本。