Memcached常规应用与分布式部署方案

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

1、Memcached 常规应用

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * 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 服务器数量固定的情况推荐使用,比较简单,但是可想而知扩展性不好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
 * 普通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 分布可以很好的运作。但是当服务器数量发生改变时,问题就出来了。因为同一个 KEYHash 算法处理后,与服务器数量取模,会导致结果与服务器数量未变化时不同,这就导致之前保存的数据丢失。采取一致性 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分布算法实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
 * 一致性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 分布实例的相关测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * 一致性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');

测试结果如下:

1
2
3
4
5
6
7
8
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