PHP实现的一致性Hash算法详解【分布式算法】


Posted in PHP onMarch 31, 2018

本文实例讲述了PHP实现的一致性Hash算法。分享给大家供大家参考,具体如下:

一致性哈希算法是分布式系统中常用的算法,为什么要用这个算法?

比如:一个分布式存储系统,要将数据存储到具体的节点(服务器)上, 在服务器数量不发生改变的情况下,如果采用普通的hash再对服务器总数量取模的方法(如key%服务器总数量),如果期间有服务器宕机了或者需要增加服务器,问题就出来了。 同一个key经过hash之后,再与服务器总数量取模的结果跟之前的结果会不一样,这就导致了之前保存数据的丢失。因此,引入了一致性Hash(Consistent Hashing)分布算法

PHP实现的一致性Hash算法详解【分布式算法】

把数据用hash函数(如md5,sha1),映射到一个圆环上,如上图所示,数据在存储时,先根据hash算法算出key的hash值,对应到这个环中的位置,如k1对应图中所示的位置同,然后沿着顺时针方向找到服务器节点B,然后把k1在存到B这个节点中。

如果B节点宕机了,则B上的数据就会落到C节点上,如下图所示

PHP实现的一致性Hash算法详解【分布式算法】

这样,只会影响C节点,对于其他节点A、D的数据不会造成影响。但是问题来了,这样会造成C节点负载过重的情况,因为C节点承担了B节点的数据,所以C节点容易宕机,这样造成了分布不均匀。

为了解决这个问题,引入了“虚拟节点“的概念:即想象空上环上有很多”虚拟节点“,一个真实的服务器节点对应多个虚拟节点,数据存储的时候沿着环的顺时针方向找到虚拟节点,就找到了对应的真实服务器节点。如下图

PHP实现的一致性Hash算法详解【分布式算法】

图中的A1、A2、B1、B2、C1、C2、D1、D2都是虚拟节点,机器A负载存储A1、A2的数据,机器B负载存储B1、B2的数据,机器C负载存储C1、C2的数据。由于这些虚拟节点数量很多,均匀分布,因此不会造成“雪崩”现象。

一致性哈希算法的PHP实现

下面给出一个接口

/**
 * 一致性哈希实现接口
 * Interface ConsistentHash
 */
interface ConsistentHash
{
 //将字符串转为hash值
 public function cHash($str);
 //添加一台服务器到服务器列表中
 public function addServer($server);
 //从服务器删除一台服务器
 public function removeServer($server);
 //在当前的服务器列表中找到合适的服务器存放数据
 public function lookup($key);
}

这个接口分别定义了4个方法,cHash(将字符串处理为hash值)、addServer(增加一台服务器)、removeServer(移除一台服务器)、lookup(找到一台服务器来存储数据)

下面给出一个该接口的具体实现

/**
 * 具体一致性哈希实现
 * author chenqionghe
 * Class MyConsistentHash
 */
class MyConsistentHash implements ConsistentHash
{
 public $serverList = array(); //服务器列列表
 public $virtualPos = array(); //虚拟节点的位置
 public $virtualPosNum = 5;  //每个节点对应5个虚节点
 /**
  * 将字符串转换成32位无符号整数hash值
  * @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;
 }
}

然后, 我们来测试一下该算法

$hashServer = new MyConsistentHash();
$hashServer->addServer('192.168.1.1');
$hashServer->addServer('192.168.1.2');
$hashServer->addServer('192.168.1.3');
$hashServer->addServer('192.168.1.4');
$hashServer->addServer('192.168.1.5');
$hashServer->addServer('192.168.1.6');
$hashServer->addServer('192.168.1.7');
$hashServer->addServer('192.168.1.8');
$hashServer->addServer('192.168.1.9');
$hashServer->addServer('192.168.1.10');
echo "增加十台服务器192.168.1.1~192.168.1.10<br />";
echo "保存 key1 到 server :".$hashServer->lookup('key1') . '<br />';
echo "保存 key2 到 server :".$hashServer->lookup('key2') . '<br />';
echo "保存 key3 到 server :".$hashServer->lookup('key3') . '<br />';
echo "保存 key4 到 server :".$hashServer->lookup('key4') . '<br />';
echo "保存 key5 到 server :".$hashServer->lookup('key5') . '<br />';
echo "保存 key6 到 server :".$hashServer->lookup('key6') . '<br />';
echo "保存 key7 到 server :".$hashServer->lookup('key7') . '<br />';
echo "保存 key8 到 server :".$hashServer->lookup('key8') . '<br />';
echo "保存 key9 到 server :".$hashServer->lookup('key9') . '<br />';
echo "保存 key10 到 server :".$hashServer->lookup('key10') . '<br />';
echo '<hr />';
echo "移除一台服务器192.168.1.2<br />";
$hashServer->removeServer('192.168.1.2');
echo "保存 key1 到 server :".$hashServer->lookup('key1') . '<br />';
echo "保存 key2 到 server :".$hashServer->lookup('key2') . '<br />';
echo "保存 key3 到 server :".$hashServer->lookup('key3') . '<br />';
echo "保存 key4 到 server :".$hashServer->lookup('key4') . '<br />';
echo "保存 key5 到 server :".$hashServer->lookup('key5') . '<br />';
echo "保存 key6 到 server :".$hashServer->lookup('key6') . '<br />';
echo "保存 key7 到 server :".$hashServer->lookup('key7') . '<br />';
echo "保存 key8 到 server :".$hashServer->lookup('key8') . '<br />';
echo "保存 key9 到 server :".$hashServer->lookup('key9') . '<br />';
echo "保存 key10 到 server :".$hashServer->lookup('key10') . '<br />';
echo '<hr />';
echo "移除一台服务器192.168.1.6<br />";
$hashServer->removeServer('192.168.1.6');
echo "保存 key1 到 server :".$hashServer->lookup('key1') . '<br />';
echo "保存 key2 到 server :".$hashServer->lookup('key2') . '<br />';
echo "保存 key3 到 server :".$hashServer->lookup('key3') . '<br />';
echo "保存 key4 到 server :".$hashServer->lookup('key4') . '<br />';
echo "保存 key5 到 server :".$hashServer->lookup('key5') . '<br />';
echo "保存 key6 到 server :".$hashServer->lookup('key6') . '<br />';
echo "保存 key7 到 server :".$hashServer->lookup('key7') . '<br />';
echo "保存 key8 到 server :".$hashServer->lookup('key8') . '<br />';
echo "保存 key9 到 server :".$hashServer->lookup('key9') . '<br />';
echo "保存 key10 到 server :".$hashServer->lookup('key10') . '<br />';
echo '<hr />';
echo "移除一台服务器192.168.1.8<br />";
$hashServer->removeServer('192.168.1.8');
echo "保存 key1 到 server :".$hashServer->lookup('key1') . '<br />';
echo "保存 key2 到 server :".$hashServer->lookup('key2') . '<br />';
echo "保存 key3 到 server :".$hashServer->lookup('key3') . '<br />';
echo "保存 key4 到 server :".$hashServer->lookup('key4') . '<br />';
echo "保存 key5 到 server :".$hashServer->lookup('key5') . '<br />';
echo "保存 key6 到 server :".$hashServer->lookup('key6') . '<br />';
echo "保存 key7 到 server :".$hashServer->lookup('key7') . '<br />';
echo "保存 key8 到 server :".$hashServer->lookup('key8') . '<br />';
echo "保存 key9 到 server :".$hashServer->lookup('key9') . '<br />';
echo "保存 key10 到 server :".$hashServer->lookup('key10') . '<br />';
echo '<hr />';
echo "移除一台服务器192.168.1.2<br />";
$hashServer->removeServer('192.168.1.2');
echo "保存 key1 到 server :".$hashServer->lookup('key1') . '<br />';
echo "保存 key2 到 server :".$hashServer->lookup('key2') . '<br />';
echo "保存 key3 到 server :".$hashServer->lookup('key3') . '<br />';
echo "保存 key4 到 server :".$hashServer->lookup('key4') . '<br />';
echo "保存 key5 到 server :".$hashServer->lookup('key5') . '<br />';
echo "保存 key6 到 server :".$hashServer->lookup('key6') . '<br />';
echo "保存 key7 到 server :".$hashServer->lookup('key7') . '<br />';
echo "保存 key8 到 server :".$hashServer->lookup('key8') . '<br />';
echo "保存 key9 到 server :".$hashServer->lookup('key9') . '<br />';
echo "保存 key10 到 server :".$hashServer->lookup('key10') . '<br />';
echo '<hr />';
echo "增加一台服务器192.168.1.11<br />";
$hashServer->addServer('192.168.1.11');
echo "保存 key1 到 server :".$hashServer->lookup('key1') . '<br />';
echo "保存 key2 到 server :".$hashServer->lookup('key2') . '<br />';
echo "保存 key3 到 server :".$hashServer->lookup('key3') . '<br />';
echo "保存 key4 到 server :".$hashServer->lookup('key4') . '<br />';
echo "保存 key5 到 server :".$hashServer->lookup('key5') . '<br />';
echo "保存 key6 到 server :".$hashServer->lookup('key6') . '<br />';
echo "保存 key7 到 server :".$hashServer->lookup('key7') . '<br />';
echo "保存 key8 到 server :".$hashServer->lookup('key8') . '<br />';
echo "保存 key9 到 server :".$hashServer->lookup('key9') . '<br />';
echo "保存 key10 到 server :".$hashServer->lookup('key10') . '<br />';
echo '<hr />';

运行结果如下

增加十台服务器192.168.1.1~192.168.1.10
保存 key1 到 server :192.168.1.2
保存 key2 到 server :192.168.1.1
保存 key3 到 server :192.168.1.6
保存 key4 到 server :192.168.1.8
保存 key5 到 server :192.168.1.9
保存 key6 到 server :192.168.1.10
保存 key7 到 server :192.168.1.7
保存 key8 到 server :192.168.1.4
保存 key9 到 server :192.168.1.7
保存 key10 到 server :192.168.1.4
移除一台服务器192.168.1.2
保存 key1 到 server :192.168.1.7
保存 key2 到 server :192.168.1.1
保存 key3 到 server :192.168.1.6
保存 key4 到 server :192.168.1.8
保存 key5 到 server :192.168.1.9
保存 key6 到 server :192.168.1.10
保存 key7 到 server :192.168.1.7
保存 key8 到 server :192.168.1.4
保存 key9 到 server :192.168.1.7
保存 key10 到 server :192.168.1.4
移除一台服务器192.168.1.6
保存 key1 到 server :192.168.1.7
保存 key2 到 server :192.168.1.1
保存 key3 到 server :192.168.1.3
保存 key4 到 server :192.168.1.8
保存 key5 到 server :192.168.1.9
保存 key6 到 server :192.168.1.10
保存 key7 到 server :192.168.1.7
保存 key8 到 server :192.168.1.4
保存 key9 到 server :192.168.1.7
保存 key10 到 server :192.168.1.4
移除一台服务器192.168.1.8
保存 key1 到 server :192.168.1.7
保存 key2 到 server :192.168.1.1
保存 key3 到 server :192.168.1.3
保存 key4 到 server :192.168.1.10
保存 key5 到 server :192.168.1.9
保存 key6 到 server :192.168.1.10
保存 key7 到 server :192.168.1.7
保存 key8 到 server :192.168.1.4
保存 key9 到 server :192.168.1.7
保存 key10 到 server :192.168.1.4
移除一台服务器192.168.1.2
保存 key1 到 server :192.168.1.7
保存 key2 到 server :192.168.1.1
保存 key3 到 server :192.168.1.3
保存 key4 到 server :192.168.1.10
保存 key5 到 server :192.168.1.9
保存 key6 到 server :192.168.1.10
保存 key7 到 server :192.168.1.7
保存 key8 到 server :192.168.1.4
保存 key9 到 server :192.168.1.7
保存 key10 到 server :192.168.1.4
增加一台服务器192.168.1.11
保存 key1 到 server :192.168.1.7
保存 key2 到 server :192.168.1.1
保存 key3 到 server :192.168.1.11
保存 key4 到 server :192.168.1.10
保存 key5 到 server :192.168.1.9
保存 key6 到 server :192.168.1.10
保存 key7 到 server :192.168.1.7
保存 key8 到 server :192.168.1.4
保存 key9 到 server :192.168.1.7
保存 key10 到 server :192.168.1.4

可以,看到,使用一致性哈希后,无认是增加服务器还是减少服务器都最大程度的保证了数据的完整性、均匀性.

PHP 相关文章推荐
4.与数据库的连接
Oct 09 PHP
让你同时上传 1000 个文件 (一)
Oct 09 PHP
一些常用的php函数
Dec 06 PHP
js下函数般调用正则的方法附代码
Jun 22 PHP
Google Voice 短信发送接口PHP开源版(2010.5更新)
Jul 22 PHP
PHP批量采集下载美女图片的实现代码
Jun 03 PHP
php5.5中类级别的常量使用介绍
Oct 02 PHP
Drupal读取Excel并导入数据库实例
Mar 02 PHP
编写PHP脚本来实现WordPress中评论分页的功能
Dec 10 PHP
PHP入门教程之面向对象基本概念实例分析
Sep 11 PHP
Redis使用Eval多个键值自增的操作实例
Nov 04 PHP
PHP中overload与override的区别
Feb 13 PHP
PHP实现基于PDO扩展连接PostgreSQL对象关系数据库示例
Mar 31 #PHP
ThinkPHP框架中使用Memcached缓存数据的方法
Mar 31 #PHP
PHPTree――php快速生成无限级分类
Mar 30 #PHP
CMSPRESS 10行代码搞定 PHP无限级分类2
Mar 30 #PHP
PHP实现动态删除XML数据的方法示例
Mar 30 #PHP
PHP实现动态添加XML中数据的方法
Mar 30 #PHP
PHP实现动态创建XML文档的方法
Mar 30 #PHP
You might like
dedecms中显示数字验证码的修改方法
2007/03/21 PHP
PHP学习 运算符与运算符优先级
2008/06/15 PHP
PHP实现链式操作的核心思想
2015/06/23 PHP
实例讲解yii2.0在php命令行中运行的步骤
2015/12/01 PHP
cakephp2.X多表联合查询join及使用分页查询的方法
2017/02/23 PHP
PHP实现的操作数组类库定义与用法示例
2019/05/24 PHP
Laravel5.1 框架表单验证操作实例详解
2020/01/07 PHP
YII2框架中behavior行为的理解与使用方法示例
2020/03/13 PHP
Javascript的一种模块模式
2008/03/22 Javascript
jQuery实现动画效果的简单实例
2014/01/27 Javascript
jquery实现用户信息修改验证输入方法汇总
2015/07/18 Javascript
浅析JavaScript中作用域和作用域链
2016/12/06 Javascript
jquery获取下拉框中的循环值
2017/02/08 Javascript
jQuery Pagination分页插件使用方法详解
2017/02/28 Javascript
ReactNative之键盘Keyboard的弹出与消失示例
2017/07/11 Javascript
Node.js学习之TCP/IP数据通讯(实例讲解)
2017/10/11 Javascript
初探Vue3.0 中的一大亮点Proxy的使用
2018/12/06 Javascript
解决父组件将子组件作为弹窗调用只执行一次created的问题
2020/07/24 Javascript
利用Python获取赶集网招聘信息前篇
2016/04/18 Python
python输出100以内的质数与合数实例代码
2018/07/08 Python
Python3随机漫步生成数据并绘制
2018/08/27 Python
Python3实现获取图片文字里中文的方法分析
2018/12/13 Python
Django 解决distinct无法去除重复数据的问题
2020/05/20 Python
最简单的matplotlib安装教程(小白)
2020/07/28 Python
python用tkinter实现一个gui的翻译工具
2020/10/26 Python
.net软件工程师面试题
2015/03/31 面试题
母亲节演讲稿范文
2014/01/02 职场文书
新闻编辑自荐书范文
2014/02/12 职场文书
信息服务专业毕业生求职信
2014/03/02 职场文书
《晚上的太阳》教学反思
2014/04/23 职场文书
数学教师求职信范文
2015/03/20 职场文书
如何书写邀请函?
2019/06/24 职场文书
MySQL修改默认引擎和字符集详情
2021/09/25 MySQL
记一次Mysql不走日期字段索引的原因小结
2021/10/24 MySQL
Python自动化工具之实现Excel转Markdown表格
2022/04/08 Python
SQL解决未能删除约束问题drop constraint
2022/05/30 SQL Server