php扩展之memcache vs memcached

2019-08-26 10:48:47 浏览数 (1)

背景

我们在业务中广泛使用memcache做缓存,我们都了解memcached本身不支持分布式,业务上会使用客户端分布式算法(一致性hash)保证分布式缓存集群性能和可用性。客户端将多个mc实例维护成一个缓存池,根据缓存key值进行一致性hash计算,写入具体指定的hash节点。由于单机可用性无法保证,若要体现此mc集群的高可用,对于单点故障机器的剔除就十分重要。但目前框架内使用的memcached扩展(也是行业内一致推荐的扩展)却没能很好的完成这一功能(或者是设置上没有正确设置),当集群中一个实例挂掉之后,hash到这台机器上的set、get方法全部失效,没有保证集群的可用。而memcache扩展在出现单节点挂掉的情况下,会把key路由到新的alive节点上,保证集群的可用性。那么问题就来了,为什么在更“新”,更“先进”的memcached扩展内却没有完成如此好特性?是有坑?还是实现上会有问题?值得深究一番。

参考资料

  • 《memcached - PHPClientComparison.wiki》
  • 《Memcache 和 Memcached 客户端的区别》
  • 《Memcached集群/分布式的单点故障》

正文

几篇博文里都说memcache有缺陷,总结一下核心缺陷:

  1. 高并发下TS不好,不稳定
  2. 协议支持不完整: memcached扩展基于memcached项目的lib库,能够以极低的成本跟进memcache的更新;并且因为此特点,也支持了更多的mc协议。
  3. 将数字存储为字符串: 对于强类型,或者是php中"==="这种比较会造成困扰,如set一个test:1, get test会返回"1",与1去做"==="会返回false,造成开发者的困惑;

memcached还有一些功能上优化的点:

  1. 提供了setOption api 可以统一设置flag
  2. 支持二进制协议,提供了更高的性能,低内存、线程安全
  3. 功能更多:cas 检查并设置

memcache多出的功能点(09年的2.2.0开始支持一致性hash):

  1. 支持OO和过程两组接口,而memcached只支持OO
  2. 支持获取or设置key时的failover

其中功能点1不够吸引人,PHP5版本之后,全线切OO编程,因此OO方法足够实现用户的直接使用,关键是功能点2。查阅资料可以得知,当网络抖动or部分服务临时不可用时,memcache扩展会主动的进行rehash,造成数据一致性问题,以一个简单的计数器(限流用)举例:

代码语言:php复制
<?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 endn");
	            sleep(1);
	            exit(1);
	        }
	        printf("set counter...n");
	        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,是取舍上收益更大的选择。

解法应该是:

代码语言:txt复制
1. 本地缓存(临时方案)
2. 利用缓存代理(magent)

总结memcache扩展与memcached扩展对比表格:

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

0 人点赞