PHP 实现一致性哈希算法
发布于: 2020 年 07 月 04 日
一致性哈希算法类:
class Hash{ private $serverList; private $virtualNodeNum; private $nodeList = []; private $nodeServerMap = []; public function __construct($serverList, $virtualNodeNum) { $this->serverList = $serverList; $this->virtualNodeNum = $virtualNodeNum; $this->_initNodeList(); } //通过key值获取服务器信息(遍历算法) public function getServerName($key) { $keyPosition = $this->_getHashName($key); $resNode = $this->nodeList[0]; foreach ($this->nodeList as $key => $node) { if ($keyPosition < $node) { $resNode = $node; break; }; } return $this->nodeServerMap[$resNode]; } //通过key值获取服务器信息(二分算法) public function getServerName2($key) { $keyPosition = $this->_getHashName($key); $resNode = $this->nodeList[0]; $start = 0; $end = count($this->nodeList) - 1; if ($keyPosition > $this->nodeList[$start] && $keyPosition < $this->nodeList[$end]) { while ($start <= $end) { $middle = intval(($start + $end) / 2); if ($middle < 1) { break; } if ($keyPosition <= $this->nodeList[$middle] && $keyPosition > $this->nodeList[$middle - 1]) { $resNode = $this->nodeList[$middle]; break; } if ($keyPosition < $this->nodeList[$middle]) { $end = $middle - 1; } else { $start = $middle + 1; } } } if ($keyPosition == $this->nodeList[$end]) { $resNode = $this->nodeList[$end]; } return $this->nodeServerMap[$resNode]; } //初始化虚拟节点 private function _initNodeList() { foreach ($this->serverList as $serverName) { $this->_addNode($serverName); } asort($this->nodeList, SORT_NUMERIC); $this->nodeList = array_merge($this->nodeList, []); } //生成哈希值 private function _getHashName($key) { return sprintf('%u', crc32($key)); } //为单服务器加虚拟节点 private function _addNode($serverName) { for ($i = 0; $i < $this->virtualNodeNum; $i++) { $key = $serverName . '_' . $i; $node = $this->_getHashName($key); $this->nodeList[] = $node; $this->nodeServerMap[$node] = $serverName; } }}
计算标准差函数
function getVariance($avg, $list, $isSwatch = false){ $arrayCount = count($list); if ($arrayCount == 1 && $isSwatch == TRUE) { return FALSE; } elseif ($arrayCount > 0) { $total_var = 0; foreach ($list as $lv) $total_var += pow(($lv - $avg), 2); return $isSwatch ? sqrt($total_var / (count($list) - 1)) : sqrt($total_var / count($list)); } else return false;}
测试函数
function test($serverNum){ echo $serverNum . '个服务节点测试' . PHP_EOL; $minNum = 0; $minVariance = null; $key = 'RenXiaoLong'; for ($n = 0; $n < $serverNum; $n++) { $serverList[] = 'server_' . ($n+1); } $keyNum = 1000000;//100万KV值 for ($virtualNodeNum = 50; $virtualNodeNum <= 250; $virtualNodeNum = $virtualNodeNum + 10) { $bucketList = []; $serverHash = new Hash($serverList, $virtualNodeNum); for ($i = 0; $i < $keyNum; $i++) { $serverName = $serverHash->getServerName2($key . '_' . $i); if (!isset($bucketList[$serverName])) { $bucketList[$serverName] = 1; } else { $bucketList[$serverName]++; } } $variance = getVariance($keyNum / count($serverList), $bucketList); if (is_null($minVariance) || $minVariance > $variance) { $minVariance = $variance; $minNum = $virtualNodeNum; } echo '虚拟节点数:' . $virtualNodeNum . ' 标准差:' . $variance . PHP_EOL; } echo PHP_EOL . ' 最小标准差:' . $minVariance . ' 此时虚拟节点数:' . $minNum;}
10个服务节点运行结果
10个服务节点测试虚拟节点数:50 标准差:23823.18714614虚拟节点数:60 标准差:13750.255706713虚拟节点数:70 标准差:10457.388259025虚拟节点数:80 标准差:12140.558043187虚拟节点数:90 标准差:10525.395707526虚拟节点数:100 标准差:9295.6409999526虚拟节点数:110 标准差:10793.353955097虚拟节点数:120 标准差:12310.234067637虚拟节点数:130 标准差:12050.280851499虚拟节点数:140 标准差:12502.381789083虚拟节点数:150 标准差:11116.714442676虚拟节点数:160 标准差:11321.901059451虚拟节点数:170 标准差:11846.528622343虚拟节点数:180 标准差:11664.198489395虚拟节点数:190 标准差:12261.751139213虚拟节点数:200 标准差:13443.812428028虚拟节点数:210 标准差:13487.275551423虚拟节点数:220 标准差:13023.136265892虚拟节点数:230 标准差:13377.86814855虚拟节点数:240 标准差:13833.141826787虚拟节点数:250 标准差:13468.785216195最小标准差:9295.6409999526 此时虚拟节点数:100
11个服务节点运行结果
虚拟节点数:50 标准差:19297.022367828虚拟节点数:60 标准差:13789.030155457虚拟节点数:70 标准差:12054.611169436虚拟节点数:80 标准差:12896.92409033虚拟节点数:90 标准差:12870.180499226虚拟节点数:100 标准差:12419.704611145虚拟节点数:110 标准差:12476.328542517虚拟节点数:120 标准差:12978.090505677虚拟节点数:130 标准差:13230.367235022虚拟节点数:140 标准差:13510.130888637虚拟节点数:150 标准差:11336.710886276虚拟节点数:160 标准差:10958.520043199虚拟节点数:170 标准差:12371.022162917虚拟节点数:180 标准差:12781.696298397虚拟节点数:190 标准差:13978.722151604虚拟节点数:200 标准差:15354.59327459虚拟节点数:210 标准差:15417.344232181虚拟节点数:220 标准差:14829.183563958虚拟节点数:230 标准差:14817.24592527虚拟节点数:240 标准差:14795.569431756虚拟节点数:250 标准差:15065.574263891最小标准差:10958.520043199 此时虚拟节点数:160
用到的一些函数和思路:
哈希类:主要实现两个功能,初始化节点环路和通过Key找到环路上的服务节点。Key是字符串,环路节点是数字集合,所以需要把字符串转成哈希字符串。
这里使用了crc32()函数
相关参考资料:
https://www.php.net/manual/zh/function.crc32.php
https://www.cnblogs.com/masonzhang/p/10261855.html
在从环路上找到服务节点,这里我使用了二分查找算法。
又复习了下标准差:
相关参考资料:
https://www.zhihu.com/question/27276029
我计算的结果如果10个服务节点下,使用100个虚拟节点,标准差最小。
而在11个服务节点下,使用160个虚拟节点,标准差最小。
这与老师说的150-200的范围不太一样,不知是不是我理解的不对。
划线
评论
复制
发布于: 2020 年 07 月 04 日阅读数: 84
版权声明: 本文为 InfoQ 作者【任小龙】的原创文章。
原文链接:【http://xie.infoq.cn/article/fbb5d0356aea8d383dee17c3d】。文章转载请联系作者。
任小龙
关注
还未添加个人签名 2019.02.11 加入
还未添加个人简介
评论