memcached一致性哈希及php客户端实现
memcached分布式算法
memcached的分布式是依靠客户端的算法来实现,假设键名为$key,服务器数量为N,常规的实现方式有两种:
- 取模哈希
- crc32($key)%N,通过这个算法将键名映射到某一台服务器,比如需要存取一个键名为myname的缓存,服务器数量为3,那么通过算法计算:crc32('myname')%3=0,那么这个缓存就落到第1台服务器上面
- 这种方式虽然简单可行,但是增减服务器的时候,缓存将面临大量的重建,比如上面的例子中,新增了1台服务器,服务器数量变为4台,通过算法计算:crc32('myname')%4=3,从第1台变成第3台了,导致缓存重建;又比如第1台服务器挂了,缓存的存取都会失败,导致短时间内大量的请求涌入mysql
- 一致性哈希
- 一致性哈希就是为了解决上面的缓存重建而设计的,取模法不理想的原因就是算法的本质就是根据服务器数量来计算的,缓存跟服务器是一一对应,要想灵活一点就不能是一对一的关系,一致性哈希算法首先创建出一个首( 0 )尾( 2^32-1 )相接的环形的哈希空间,如下图的圆环,然后把服务器通过hash算法映射到环形的某一点,如下图中node1、node2、node3、node4,然后再把缓存的键映射到环形的某一点,获取某个键的内容是从这个键的节点按顺时针方向开始查找服务器节点,找到的第一台服务器就是这个缓存要进行存取的服务器,如此一来当node1服务器挂了,影响到的只是从node3到node1节点之间的缓存数据,这些数据将会去node2中存取,这样可以把缓存重建的代价降低
安装依赖软件
[root@localhost ~]# yum install gcc [root@localhost ~]# yum install gcc-c++ [root@localhost ~]# yum install autoconf
安装libmemcached
memcached扩展依赖于libmemcached,因此先安装libmemcached
[root@localhost ~]# cd /usr/local/src [root@localhost src]# wget https://launchpad.net/libmemcached/1.0/1.0.17/+download/libmemcached-1.0.17.tar.gz [root@localhost src]# tar -zxvf libmemcached-1.0.18.tar.gz [root@localhost src]# cd libmemcached-1.0.18 [root@localhost libmemcached-1.0.18]# ./configure --prefix=/usr/local/libmemcached --with-memcached [root@localhost libmemcached-1.0.18]# make && make install
安装php的memcached扩展
[root@localhost ~]# cd /usr/local/src [root@localhost src]# wget http://pecl.php.net/get/memcached-3.0.3.tgz [root@localhost src]# cd memcached-3.0.3 [root@localhost memcached-3.0.3]# tar -zxvf memcached-3.0.3.tgz [root@localhost memcached-3.0.3]# ./configure --prefix=/usr/local/pecl-memcached --with-php-config=/usr/local/php/bin/php-config --with-libmemcached-dir=/usr/local/libmemcached error: no, sasl.h is not available [root@localhost memcached-3.0.3]# yum install cyrus-sasl-devel [root@localhost memcached-3.0.3]# make && make install #修改php配置,加入memcache扩展 [root@localhost memcached-3.0.3]# vi /usr/local/php/lib/php.ini extension=memcached.so #重启apache [root@localhost memcached-3.0.3]# apachectl restart #启动memcached服务器并记录日志 [root@localhost src]# memcached -d -u nobody -p 32054 -vv >> /tmp/memcached.32054.log 2>&1 [root@localhost src]# memcached -d -u nobody -p 32055 -vv >> /tmp/memcached.32055.log 2>&1 [root@localhost src]# memcached -d -u nobody -p 32056 -vv >> /tmp/memcached.32056.log 2>&1
测试一致性哈希
$m = new Memcached();
$m->setOptions(array(
Memcached::OPT_DISTRIBUTION=>Memcached::DISTRIBUTION_CONSISTENT,
Memcached::OPT_LIBKETAMA_COMPATIBLE=>true,
Memcached::OPT_REMOVE_FAILED_SERVERS=>true,
));
$m->addServers(array(
array('localhost', 32054),
array('localhost', 32055),
array('localhost', 32056),
//array('localhost', 32057),
));
$m->set('key1', 1);
$m->set('key2', 'abc');
$m->set('key3', array('foo', 'bar'));
$m->set('key4', new stdClass);
$m->set('key5', 'pigfly');
$m->set('key6', 999);
var_dump($m->get('key1'), $m->get('key2'), $m->get('key3'), $m->get('key4'), $m->get('key5'), $m->get('key6'));
运行这段php代码,我们再查看日志:
#memcached.32054.log
36 STORED
36 STORED
36 sending key key2
>36 END
36 sending key key3
>36 END
#memcached.32055.log
<36 new auto-negotiating client connection
36: Client using the ascii protocol
<36 set key1 1 0 1
>36 STORED
<36 set key4 4 0 19
>36 STORED
<36 get key1
>36 sending key key1
>36 END
<36 get key4
>36 sending key key4
>36 END
<36 quit
<36 connection closed.
#memcached.32056.log
<36 new auto-negotiating client connection
36: Client using the ascii protocol
<36 set key5 0 0 6
>36 STORED
<36 set key6 1 0 3
>36 STORED
<36 get key5
>36 sending key key5
>36 END
<36 get key6
>36 sending key key6
>36 END
<36 quit
<36 connection closed.
缓存的分布情况:
host | key |
---|---|
32054 | key2,key3 |
32055 | key1,key4 |
32056 | key5,key6 |
现在我们加一台32057的memcached试试:
[root@localhost src]# memcached -d -u nobody -p 32057 -vv >> /tmp/memcached.32057.log 2>&1
取消php代码第13行的注释,运行php,查看日志文件:
#memcached.32054.log
<36 new auto-negotiating client connection
36: Client using the ascii protocol
<36 set key3 4 0 34
>36 STORED
<36 get key3
>36 sending key key3
>36 END
<36 quit
#memcached.32055.log
<36 new auto-negotiating client connection
36: Client using the ascii protocol
<36 set key1 1 0 1
>36 STORED
<36 set key4 4 0 19
>36 STORED
<36 get key1
>36 sending key key1
>36 END
<36 get key4
>36 sending key key4
>36 END
<36 quit
<36 connection closed.
#memcached.32056.log
<36 new auto-negotiating client connection
36: Client using the ascii protocol
<36 set key5 0 0 6
>36 STORED
<36 set key6 1 0 3
>36 STORED
<36 get key5
>36 sending key key5
>36 END
<36 get key6
>36 sending key key6
>36 END
<36 quit
<36 connection closed.
#memcached.32057.log
<36 new auto-negotiating client connection
36: Client using the ascii protocol
<36 set key2 0 0 3
>36 STORED
<36 get key2
>36 sending key key2
>36 END
<36 quit
<36 connection closed.
缓存的分布情况:
host | key |
---|---|
32054 | key3 |
32055 | key1,key4 |
32056 | key5,key6 |
32057 | key2 |
我们发现32054的key2跑到32057去了,32055和32056的key都没有受到影响,说明一致性哈希起作用了,我们再来模拟一下有一台memcached服务器宕机的情况
[root@local htdocs]# ps -aux | grep memcached nobody 7621 0.0 0.3 415860 3440 ? Ssl 16:16 0:00 memcached -d -u nobody -p 32054 -vv nobody 7632 0.0 0.4 414832 4432 ? Ssl 16:16 0:00 memcached -d -u nobody -p 32055 -vv nobody 7643 0.0 0.8 414832 8532 ? Ssl 16:17 0:00 memcached -d -u nobody -p 32056 -vv nobody 7659 0.0 0.4 414832 4432 ? Ssl 16:26 0:00 memcached -d -u nobody -p 32057 -vv [root@local htdocs]# kill 7621 #杀掉32054
运行php代码,查看日志:
#memcached.32055.log
<36 new auto-negotiating client connection
36: Client using the ascii protocol
<36 set key1 1 0 1
>36 STORED
<36 set key3 4 0 34
>36 STORED
<36 set key4 4 0 19
>36 STORED
<36 get key1
>36 sending key key1
>36 END
<36 get key3
>36 sending key key3
>36 END
<36 get key4
>36 sending key key4
>36 END
<36 quit
<36 connection closed.
#memcached.32056.log
<36 new auto-negotiating client connection
36: Client using the ascii protocol
<36 set key5 0 0 6
>36 STORED
<36 set key6 1 0 3
>36 STORED
<36 get key5
>36 sending key key5
>36 END
<36 get key6
>36 sending key key6
>36 END
<36 quit
<36 connection closed.
#memcached.32057.log
<36 new auto-negotiating client connection
36: Client using the ascii protocol
<36 set key2 0 0 3
>36 STORED
<36 get key2
>36 sending key key2
>36 END
<36 quit
<36 connection closed.
缓存的分布情况:
host | key |
---|---|
32055 | key1,key3,key4 |
32056 | key5,key6 |
32057 | key2 |
由于32054挂了,我们看到32054的key3跑到32055去了,其他端口的缓存没有受到影响,这样就把缓存的重建代价降低了