下面天涯 [PHPHA.COM]
介绍下 Memcached
的最常规的应用及分布式部署方案。相关内容参考了凹凸曼写的 Memcached
使用与实践章节。至于 Memcached
的安装及基本使用,这里不做介绍,有需要可以借助 Google
。
1、Memcached
常规应用
使用 Memcached
缓存 MySQL
查询结果减轻数据库压力,下面直接上代码,后面做简单说明。
/**
* Memcached常规应用演示
* 天涯PHP博客
* http://blog.phpha.com
*/
$mc = new Memcache();
$mc->conncet('127.0.0.1', 11211);
$sql = sprintf("SELECT * FROM users WHERE uid = %d", $_GET['uid']);
$key = md5($sql);
//检测结果是否已经被缓存
if( ! $data = $mc->get($key)){
//没有缓存则直接从数据库读取
mysql_conncet('localhost', 'test', 'test');
mysql_select_db('test');
while($row = mysql_fetch_object(mysql_query($sql))){
$data[] = $row;
}
//并将查询结果缓存
$mc->add($key, $data);
}
var_dump($data);
说明:首先通过 md5()
将 SQL
语句转化成一个唯一的 KEY
,并用此 KEY
查询Memcached
检测是否已经缓存,是的话在直接返回结果,否则先查询数据库再缓存,并返回结果。这样,下次使用此 KEY
就可以直接返回结果了。另外值得一提的是,看代码中 SQL
语句的组合部分,用到了 sprintf()
函数,简单高效,来自白菜指南推荐。
2、Memcached
分布式部署方案
通常较小的应用一台 Memcached
服务器就可以满足需求,但是大中型项目可能就需要多台 Memcached
服务器了,这就牵涉到一个分布式部署的问题。
对于多台 Memcached
服务器,怎么确定一个数据应该保存到哪台服务器呢?有两种方案,一是普通 Hash
分布,二是一致性 Hash
分布。下面详细说明。
2.1、Memcached
分布式部署之普通 Hash
分布
普通 Hash
分布对于 Memcached
服务器数量固定的情况推荐使用,比较简单,但是可想而知扩展性不好。
/**
* 普通Hash分布
* 天涯PHP博客
* http://blog.phpha.com
*/
//Hash函数
function mHash($key){
$md5 = substr(md5($key), 0, 8);
$seed = 31;
$hash = 0;
for($i = 0; $i < 8; $i++){
$hash = $hash * $seed + ord($md5{$i});
$i++;
}
return $hash & 0x7FFFFFFF;
}
//假设有2台Memcached服务器
$servers = array(
array('host' => '192.168.1.1', 'port' => 11211),
array('host' => '192.168.1.1', 'port' => 11211)
);
$key = 'MyBlog';
$value = 'http://blog.phpha.com';
$sc = $servers[mHash($key) % 2];
$memcached = new Memcached($sc);
$memcached->set($key, $value);
?>
说明:首先通过 MD5
函数把 KEY
处理成 32
位字符串,然后截取前 8
位,再经过 Hash
算法处理成一个整数并返回。利用这个整数与 Memcached
服务器数量取模,决定当前 KEY
存储于哪台 Memcached
服务器,就完成了 Memcached
的分布式部署。可想而知,当要读取 KEY
的值时,依然是先要通过 Hash
算法判断存储于哪台服务器。这种方案整体来说比较简单容易理解。
2.2、Memcached
分布式部署之一致性 Hash
分布
当 Memcached
服务器数量固定时,普通 Hash
分布可以很好的运作。但是当服务器数量发生改变时,问题就出来了。因为同一个 KEY
经 Hash
算法处理后,与服务器数量取模,会导致结果与服务器数量未变化时不同,这就导致之前保存的数据丢失。采取一致性 Hash
分布可以有效的解决这个问题,把丢失的数据减到最小(注意这里并没有说完全不丢失)。
一致性 Hash
分布算法分 4
个步骤:
步骤1
:将一个 32
位整数 [0 ~ (2^32-1)]
想象成一个环,0
作为开头,(2^32-1)
作为结尾,当然这只是想象。
步骤2
:通过 Hash
函数把 KEY
处理成整数。这样就可以在环上找到一个位置与之对应。
步骤3
:把 Memcached
服务器群映射到环上,使用 Hash
函数处理服务器对应的 IP
地址即可。
步骤4
:把数据映射到 Memcached
服务器上。
查找一个 KEY
对应的 Memcached
服务器位置的方法如下:从当前 KEY
的位置,沿着圆环顺时针方向出发,查找位置离得最近的一台 Memcached
服务器,并将 KEY
对应的数据保存在此服务器上。这样一来,当添加或移除某一台服务器时,受影响的数据范围变的更小了。
2.3、一致性Hash分布算法实例
/**
* 一致性Hash分布
* 天涯PHP博客
* http://blog.phpha.com
*/
class FlexiHash{
//服务器列表
private $serverList = array();
//记录是否已经排序
private $isSorted = FALSE;
//添加一台服务器
public function addServer($server){
$hash = $this->mHash($server);
if(!isset($this->serverList[$hash])){
$this->serverList[$hash] = $server;
}
//需要重新排序
$this->isSorted = FALSE;
return TRUE;
}
//移除一台服务器
public function removeServer($server){
$hash = $this->mHash($server);
if(isset($this->serverList[$hash])){
unset($this->serverList[$hash]);
}
//需要重新排序
$this->isSorted = FALSE;
return TRUE;
}
//在当前服务器列表查找合适的服务器
public function lookup($key){
$hash = $this->mHash($key);
//先进行倒序排序操作
if(!$this->isSorted){
krsort($this->serverList, SORT_NUMERIC);
$this->isSorted = TRUE;
}
//圆环上顺时针方向查找当前KEY紧邻的一台服务器
foreach($this->serverList as $pos => $server){
if($hash >= $pos) return $server;
}
//没有找到则返回顺时针方向最后一台服务器
return $this->serverList[count($this->serverList) - 1];
}
//Hash函数
private function mHash($key){
$md5 = substr(md5($key), 0, 8);
$seed = 31;
$hash = 0;
for($i = 0; $i < 8; $i++){
$hash = $hash * $seed + ord($md5{$i});
$i++;
}
return $hash & 0x7FFFFFFF;
}
}
说明:其整体查找思路,已经在前面的一致性 Hash
分布部分进行了介绍,需要补充的是每次添加或移除服务器后需要对服务器列表这个序列就行一次排序。
下面是对上面的一致性 Hash
分布实例的相关测试代码:
/**
* 一致性Hash分布测试代码
* 天涯PHP博客
* http://blog.phpha.com
*/
$hserver = new FlexiHash();
//初始5台服务器
$hserver->addServer("192.168.1.1");
$hserver->addServer("192.168.1.2");
$hserver->addServer("192.168.1.3");
$hserver->addServer("192.168.1.4");
$hserver->addServer("192.168.1.5");
echo "save key1 in server: ", $hserver->lookup('key1'), "<br/>";
echo "save key2 in server: ", $hserver->lookup('key2'), "<br/>";
echo '===============================================<br/>';
//移除1台服务器
$hserver->removeServer("192.168.1.4");
echo "save key1 in server: ", $hserver->lookup('key1'), "<br/>";
echo "save key2 in server: ", $hserver->lookup('key2'), "<br/>";
echo '===============================================<br/>';
//添加1台服务器
$hserver->addServer('192.168.1.6');
echo "save key1 in server: ", $hserver->lookup('key1'), "<br/>";
echo "save key2 in server: ", $hserver->lookup('key2');
测试结果如下:
save key1 in server: 192.168.1.4
save key2 in server: 192.168.1.2
==================================
save key1 in server: 192.168.1.3
save key2 in server: 192.168.1.2
==================================
save key1 in server: 192.168.1.3
save key2 in server: 192.168.1.2