下面天涯[PHPHA.COM]介绍下Memcached的最常规的应用及分布式部署方案。相关内容参考了凹凸曼写的Memcached使用与实践章节。至于Memcached的安装及基本使用,这里不做介绍,有需要可以借助Google或Baidu。

1、Memcached常规应用
使用Memcached缓存MySQL查询结果减轻数据库压力,下面直接上代码,后面做简单说明。

<?php
/**
 * 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分布。下面详细说明。

[1]Memcached分布式部署之普通Hash分布
普通Hash分布对于Memcached服务器数量固定的情况推荐使用,比较简单,但是可想而知扩展性不好。

<?php
/**
 * 普通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]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对应的数据保存在此服务器上。
说明:这样一来,当添加或移除某一台服务器时,受影响的数据范围变的更小了。具体可以画个图更便于理解,这里我就不画了。

[3]一致性Hash分布算法实例

<?php
/**
 * 一致性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分布实例的相关测试代码:

<?php
/**
 * 一致性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

标签:分布式 / php / memcached