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 相关文章推荐
MVC模式的PHP实现
Oct 09 PHP
adodb与adodb_lite之比较
Dec 31 PHP
使用PHP获取网络文件的实现代码
Jan 01 PHP
ThinkPHP自动验证失败的解决方法
Jun 09 PHP
设置php页面编码的两种方法示例介绍
Mar 03 PHP
php计算函数执行时间的方法
Mar 20 PHP
Centos下升级php5.2到php5.4全记录(编译安装)
Apr 03 PHP
使用php实现从身份证中提取生日
May 09 PHP
php获取flash尺寸详细数据的方法
Nov 12 PHP
PHP面向对象程序设计OOP继承用法入门示例
Dec 27 PHP
PHP实现会员账号单唯一登录的方法分析
Mar 07 PHP
PHP常用字符串输出方法分析(echo,print,printf及sprintf)
Mar 09 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
让你同时上传 1000 个文件 (二)
2006/10/09 PHP
关于php mvc开发模式的感想
2011/06/28 PHP
分享一个PHP数据流应用的简单例子
2012/06/01 PHP
PHP依赖倒置(Dependency Injection)代码实例
2014/10/11 PHP
php实现压缩合并js的方法【附demo源码下载】
2016/09/22 PHP
php下的原生ajax请求用法实例分析
2020/02/28 PHP
jQuery Ajax中的事件详细介绍
2015/04/16 Javascript
jquery心形点赞关注效果的简单实现
2016/11/14 Javascript
使用原生的javascript来实现轮播图
2017/02/24 Javascript
ES6新特性一: let和const命令详解
2017/04/20 Javascript
Vue2.x中的父组件传递数据至子组件的方法
2017/05/01 Javascript
BootStrap 表单控件之单选按钮水平排列
2017/05/23 Javascript
vue的基本用法与常见指令
2017/08/15 Javascript
轻量级富文本编辑器wangEditor结合vue使用方法示例
2018/10/10 Javascript
微信小程序中的video视频实现 自定义播放按钮、封面图、视频封面上文案
2020/01/02 Javascript
使用Node.js实现base64和png文件相互转换的方法
2020/03/11 Javascript
Node.js API详解之 string_decoder用法实例分析
2020/04/29 Javascript
vscode 使用Prettier插件格式化配置使用代码详解
2020/08/10 Javascript
Python request设置HTTPS代理代码解析
2018/02/12 Python
Python小程序之在图片上加入数字的代码
2019/11/26 Python
python3.7.3版本和django2.2.3版本是否可以兼容
2020/09/01 Python
使用python tkinter开发一个爬取B站直播弹幕工具的实现代码
2021/02/07 Python
python 获取计算机的网卡信息
2021/02/18 Python
纯CSS3实现表单验证效果(非常不错)
2017/01/18 HTML / CSS
美国高端医师级美容产品电商:BeautifiedYou.com
2017/04/17 全球购物
意大利简约的休闲品牌:Aspesi
2018/02/08 全球购物
如何在.net Winform里面显示PDF文档
2012/09/11 面试题
万户网络JAVA程序员岗位招聘笔试试卷
2013/01/08 面试题
管理建议书范文
2014/05/13 职场文书
导游词范文
2015/02/13 职场文书
团组织推荐意见
2015/06/05 职场文书
2019新员工心得体会
2019/06/25 职场文书
MySQL 重写查询语句的三种策略
2021/05/10 MySQL
浅谈Python数学建模之固定费用问题
2021/06/23 Python
Python获取字典中某个key的value
2022/04/13 Python
JS函数式编程实现XDM一
2022/06/16 Javascript