在使用分布式存储时我们要考虑如何将数据均匀分布在多个节点库中?并且也要方便后期的数据查找。
hash取模
假设有N个存储节点,根据用户传入的key(key具有唯一性),index = hash(key) % N计算出将数据存储在哪一个节点上。
一致性hash
为什么不直接使用hash取模的方式,主要原因是:hash取模在容错性和扩展性上较差,如果新增一个节点,或者删除一个节点,那么所有的几点都要重新计算一遍。显然不符合实际实际生产环境。
什么是一致性hash?
通过一致性hash算法可以把key的hash值均匀分布在一个hash环上,hash环的范围是 0 ~ 2^32-1。通过节点的ip,port、hostname等信息hash后分布到hash环上。同时为了均匀分布,可为每个节点设置m个虚拟节点。
如上图所示:有节点N1,N2,N3。并且给这三个节点都设置2个虚拟节点。 数据的存储按照hash环顺时针,将hash运算后的key存储在下一个临近节点上。
如果需要删除或者新增一个节点,只会有一小部分key需要重新计算或者转移,一致性hash无论是扩展性还是容错性上都是要优于hash取模的。
php实现一致性hash算法
class ConsistentHash
{
public $serverList = []; //服务器列列表
public $virtualPos = []; //虚拟节点的位置
public $virtualPosNum = 2; //每个节点对应2个虚节点
/**
* 使用循环冗余算法计算出十进制校验值
* @param $str
* @return int
*/
public function cHash($str)
{
$str = md5($str);
return sprintf('%u', crc32($str));
}
/**
* 在当前的服务器列表中找到合适的服务器存放数据
* @param $key 键名
* @return mixed 返回服务器IP地址
*/
public function lookup($key)
{
$point = $this->cHash($key);//落点的hash值
$finalServer = current($this->virtualPos);//先取圆环上最小的一个节点当成结果
foreach ($this->virtualPos as $pos => $server) {
if ($point <= $pos) {
$finalServer = $server;
break;
}
}
reset($this->virtualPos);//重置圆环的指针为第一个
return $finalServer;
}
/**
* 添加一台服务器到服务器列表中
* @param $server 服务器IP地址
* @return bool
*/
public function addServer($server)
{
if (!isset($this->serverList[$server])) {
for ($i = 0; $i < $this->virtualPosNum; $i++) {
$pos = $this->cHash($server . '-' . $i);
$this->virtualPos[$pos] = $server;
$this->serverList[$server][] = $pos;
}
ksort($this->virtualPos, SORT_NUMERIC);
}
return true;
}
/**
* 移除一台服务器(循环所有的虚节点,删除值为该服务器地址的虚节点)
* @param $key
* @return bool
*/
public function removeServer($key)
{
if (isset($this->serverList[$key])) {
//删除对应虚节点
foreach ($this->serverList[$key] as $pos) {
unset($this->virtualPos[$pos]);
}
//删除对应服务器
unset($this->serverList[$key]);
}
return true;
}
}
#测试
array(4) {
'192.168.1.1' =>
array(2) {
[0] =>
string(9) "998838913"
[1] =>
string(10) "2388609030"
}
'192.168.1.2' =>
array(2) {
[0] =>
string(9) "336885862"
[1] =>
string(10) "3749174810"
}
'192.168.1.3' =>
array(2) {
[0] =>
string(10) "1624092728"
[1] =>
string(8) "65155740"
}
'192.168.1.4' =>
array(2) {
[0] =>
string(10) "3733784813"
[1] =>
string(10) "1716255089"
}
}
/home/www/test/ConsistentHash.php:97:
array(8) {
[65155740] =>
string(11) "192.168.1.3"
[336885862] =>
string(11) "192.168.1.2"
[998838913] =>
string(11) "192.168.1.1"
[1624092728] =>
string(11) "192.168.1.3"
[1716255089] =>
string(11) "192.168.1.4"
[2388609030] =>
string(11) "192.168.1.1"
[3733784813] =>
string(11) "192.168.1.4"
[3749174810] =>
string(11) "192.168.1.2"
}
/home/www/test/ConsistentHash.php:98:
string(11) "192.168.1.4"
/home/www/test/ConsistentHash.php:99:
string(11) "192.168.1.3"