背景
此文是之前开发过程中,出于对版本更新的疑问,以及新版本解决了什么问题的一个回答,简单总结交流。 我们在业务中广泛使用memcache做缓存,我们都了解memcached本身不支持分布式,业务上会使用客户端分布式算法(一致性hash)保证分布式缓存集群性能和可用性。客户端将多个mc实例维护成一个缓存池,根据缓存key值进行一致性hash计算,写入具体指定的hash节点。由于单机可用性无法保证,若要体现此mc集群的高可用,对于单点故障机器的剔除就十分重要。但目前框架内使用的memcached扩展(也是行业内一致推荐的扩展)却没能很好的完成这一功能(或者是设置上没有正确设置),当集群中一个实例挂掉之后,hash到这台机器上的set、get方法全部失效,没有保证集群的可用。而memcache扩展在出现单节点挂掉的情况下,会把key路由到新的alive节点上,保证集群的可用性。那么问题就来了,为什么在更“新”,更“先进”的memcached扩展内却没有完成如此好特性?是有坑?还是实现上会有问题?值得深究一番。
参考资料
正文
几篇博文里都说memcache有缺陷,总结一下核心缺陷:
- 高并发下TS不好,不稳定
- 协议支持不完整: memcached扩展基于memcached项目的lib库,能够以极低的成本跟进memcache的更新;并且因为此特点,也支持了更多的mc协议
- 将数字存储为字符串: 对于强类型,或者是php中"="这种比较会造成困扰,如set一个test:1, get test会返回"1",与1去做"="会返回false,造成开发者的困惑
memcached还有一些功能上优化的点:
- 提供了setOption api 可以统一设置flag
- 支持二进制协议,提供了更高的性能,低内存、线程安全
- 功能更多:cas 检查并设置
memcache多出的功能点(09年的2.2.0开始支持一致性hash):
- 支持OO和过程两组接口,而memcached只支持OO
- 支持获取or设置key时的failover
其中功能点1不够吸引人,PHP5版本之后,全线切OO编程,因此OO方法足够实现用户的直接使用,关键是功能点2。查阅资料可以得知,当网络抖动or部分服务临时不可用时,memcache扩展会主动的进行rehash,造成数据一致性问题,以一个简单的计数器(限流用)举例:
<?php
error_reporting(-1);
//$client = new memcached;
$client = new memcache;
$arr = array(
array("host"=>"127.0.0.1","port"=>11211),
array("host"=>"127.0.0.1","port"=>11212),
);
foreach ($arr as $ele)
{
$client->addServer($ele["host"],$ele["port"],true);
}
$counter = $client->get('counter');
var_dump($counter);
if (empty($counter))
$client->set('counter', 100, 0);
for ($i=0; $i < 100; $i++)
{
try
{
printf("get counter...");
sleep(2);
$counter = $client->get('counter');
printf($counter);
if (false === $counter)
{
printf("connect error");
sleep(1);
continue;
}
if (0 >= $counter)
{
printf("loop end
");
sleep(1);
exit(1);
}
printf("set counter...
");
sleep(2);
$client->set('counter', $counter - 1);
}
catch (Exception $e)
{
echo "*";
var_dump($e->getMessage());
continue;
}
}
exit(0);
steps:
a. php连接11211和11212集群,counter作为key存在11211实例上;
b. 循环继续,eg:当计数器到90的循环内,在set counter阶段,mcd进程11211失效(以kill来仿真),则将会把counter作为key写入11212节点中(报一个notice) ;
c. 计数器继续递减,eg:当counter为80时,在get counter阶段 11211又启动,所以从11211中拿数据,此时数据为false;在set counter阶段,则将counter=>80写到11211中;
d. 计数器继续递减,eg:当counter为70时,在get counter阶段 11211又失效,则获取counter会拿到上一次切换的点80;
e. 如果使用memcached扩展,则一旦对应的节点失效就会报错,保证通知到运维方,对mc集群进行处理。
由于集群的网络环境不可控,单次操作超时 or 单节点短时间不可用的场景会频繁出现,因此不会使用随机节点rehash的方式来保证系统可用,对数据一致性造成的负面影响过大,因此在memcached扩展中,选择直接返回false,是取舍上收益更大的选择。
解法应该是:
1. 本地缓存(临时方案)
2. 利用缓存代理(magent)
总结memcache扩展与memcached扩展对比表格(盗用google wiki上的比对):
PECL/MEMCACHE | PECL/MEMCACHED | |
---|---|---|
FIRST RELEASE DATE | 2004-06-08 | 2009-01-29 (beta) |
ACTIVELY DEVELOPED? | Yes | Yes |
EXTERNAL DEPENDENCY | None | libmemcached |
Features | ||
AUTOMATIC KEY FIXUP | Yes | No |
APPEND/PREPEND | No | Yes |
AUTOMATIC SERIALZATION | Yes | Yes |
BINARY PROTOCOL | No | Optional |
CAS | No | Yes |
COMPRESSION | Yes | Yes |
COMMUNICATION TIMEOUT | Connect Only | Various Options |
CONSISTENT HASHING | Yes | Yes |
DELAYED GET | No | Yes |
MULTI-GET | Yes | Yes |
SESSION SUPPORT | Yes | Yes |
SET/GET TO A SPECIFIC SERVER | No | Yes |
STORES NUMERICS | Converted to Strings | Yes |