[TOC]
PHP实现一致性hash
bash命令
因为下面PHP代码的模拟用户用的是随机数,所以统计结果达不到绝对的均衡.
php ./hash.php | sort | uniq -c | sort
PHP代码
这是之前学的时候留下来的测试代码,原理方面就不赘述了.
<?php
/**
* @class ConsistentHash
* @desc 无符号的一致性hash
* @author liuhao
*/
class ConsistentHash
{
protected $nodeList = []; //服务器列表
protected $virtualNodeSite = []; //虚拟节点的位置
protected $virtualNodeCount = 20; //每个节点对应20个虚节点
/**
* @desc 在服务器列表中, 为用户匹配合适的服务器
* @action getNodeAction
* @param string $key 键名
* @return string 服务器IP地址
* @author liuhao
*/
public function getNode($key)
{
//获取当前下标的hash值
$keyHash = $this->crc32Hash($key);
//先把hash环上上最小的一个节点临时当作是我们需要的节点
$resultHost = current($this->virtualNodeSite);
foreach ($this->virtualNodeSite as $nodeHash => $host) {
if ($keyHash <= $nodeHash) {
$resultHost = $host;
break;
}
}
//重置圆环的指针为第一个
reset($this->virtualNodeSite);
return $resultHost;
}
/**
* @desc 将字符串转换成32位无符号整数hash值
* @action crc32HashAction
* @param string $str
* @return string
* @author liuhao
*/
protected function crc32Hash($str)
{
//由于 PHP 的整数是带符号的,所以在 32 位系统上许多 crc32 校验码将返回负整数。 尽管在 64 位上所有 crc32() 的结果将都是正整数。
//因此你需要使用 sprintf() 或 printf() 的“%u”格式符来获取表示无符号 crc32 校验码的字符串。
$str = crc32(md5($str));
return sprintf('%u', $str);
}
/**
* @desc 向服务器列表,添加一个新的服务器
* @action addNodeAction
* @param string $host 服务器IP地址
* @return bool
* @author liuhao
*/
public function addNode($host)
{
if (!isset($this->nodeList[$host])) {
for ($i = 0; $i < $this->virtualNodeCount; $i++) {
$hostHash = $this->crc32Hash($host.'-'.$i);
$this->virtualNodeSite[$hostHash] = $host;
$this->nodeList[$host][] = $hostHash;
}
ksort($this->virtualNodeSite, SORT_NUMERIC);
}
return true;
}
/**
* @desc 循环所有的虚节点,删除值为该服务器地址的虚节点
* @action delNodeAction
* @param string $host
* @return bool
* @author liuhao
*/
public function delNode($host)
{
if (isset($this->nodeList[$host])) {
//删除对应虚节点
foreach ($this->nodeList[$host] as $pos) {
unset($this->virtualNodeSite[$pos]);
}
//删除对应服务器
unset($this->nodeList[$host]);
}
return true;
}
}
//节点服务器列表
$ipArr = [
"192.168.1.100",
"192.168.1.102",
"192.168.1.103",
"192.168.1.104",
"192.168.1.105",
"192.168.1.106",
"192.168.1.107",
"192.168.1.108",
"192.168.1.109",
"192.168.1.110",
"192.168.1.111",
"192.168.1.112",
"192.168.1.113",
"192.168.1.114",
"192.168.1.115",
"192.168.1.116",
"192.168.1.117",
"192.168.1.118",
"192.168.1.119",
"192.168.1.120",
];
$hashServer = new ConsistentHash();
//添加节点
foreach ($ipArr as $key => $value) {
$hashServer->addNode($value);
// echo "节点服务器{$key}: ======> {$value}".PHP_EOL;
}
echo PHP_EOL;
//模拟访问函数
$fakeVisit = function () use (&$hashServer) {
//用户访问总次数
$visitsTotal = 100;
for ($i = 0; $i < $visitsTotal; $i++) {
//假定5个用户随机访问
$rand = mt_rand(0, 5);
echo "用户{$rand} 连接的节点是: ".$hashServer->getNode($rand).PHP_EOL;
}
};
//模拟访问
$fakeVisit();
exit;
//随机删除一台服务器
$randIPFunc = function () use ($ipArr) {
return $ip = $ipArr[array_rand($ipArr, 1)];
};
echo PHP_EOL;
$randIP = $randIPFunc();
echo "用户删除的节点服务器是:{$randIP}".PHP_EOL;
echo PHP_EOL;
$hashServer->delNode($randIP);
//模拟访问
$fakeVisit();
echo PHP_EOL;
$randIP = $randIPFunc();
echo "用户删除的节点服务器是:{$randIP}".PHP_EOL;
echo PHP_EOL;
$hashServer->delNode($randIP);
//模拟访问
$fakeVisit();
来源:oschina
链接:https://my.oschina.net/chinaliuhan/blog/3169142