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使用及测试实例如下:
#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) ;
2)重新启动memcached server,测试重新连接情况:
在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版本。