PHP一致性hash代码

纵然是瞬间 提交于 2020-02-28 11:24:50

[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();

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!