/**
 * 分布式缓存部署方案
 * 当有1台cache服务器不能满足我们的需求,我们需要布置多台来做分布式服务器,但是
 * 有个问题,怎么确定一个数据应该保存到哪台服务器上呢?
 * 有两种方案,第一种普通hash分布,第二种一致性哈希分布
 * 
 * 普通hash分布
 * 首先将key处理为一个32位字符串,取前8位,在经过hash计算处理成整数并返回,然后映射到其中一台服务器
 * $servers[mhash($key) % 2] 这样得到其中一台服务器的配置,利用这个配置完成分布式部署
 * 在服务器数量不发生变化的情况下,普通hash分布可以很好的运作,当服务器的数量发生变化,问题就来了
 * 试想,增加一台服务器,同一个key经过hash之后,与服务器取模的结果和没增加之前的结果肯定不一样,这就导致了,之前保存的数据丢失
 * 
 * 一致性哈希算法
 * 优点:在分布式的cache缓存中,其中一台宕机,迁移key效率最高
 * 将服务器列表进行排序,根据mHash($key) 匹配相邻服务器
 */

/**
 * hash算法
 * @param string $key
 * @return int
 */
function mHash($key)
{
    $md5 = substr(md5($key), 0, 8);
    $seed = 31;
    $hash = 0;
    
    for($i = 0; $i < 8; $i++){
        $hash = $hash * $seed + ord($md5{$i});
        $i++;
    }
    return $hash & 0x7FFFFFFF;
}

class FlexiHash
{
    // 服务器列表
    private $serverList = array();
    // 是否排序
    private $isSorted = false;
    
    /**
     * 添加服务器
     * @param string $server
     * @return boolean
     */
    function addServer($server)
    {
        $hash = mHash($server);
        if (!isset($this->serverList[$hash])) {
            $this->serverList[$hash] = $server;
        }
        $this->isSorted = false;
        return true;
    }
    
    /**
     * 移除服务器
     * @param string $server
     * @return boolean
     */
    function removeServer($server) 
    {
        $hash = mHash($server);
        if (isset($this->serverList[$hash])) {
            unset($this->serverList[$hash]);
        }
        $this->isSorted = false;
        return true;
    }
    
    /**
     * 根据$key逆时针查找相邻的服务器
     * @param string $key
     * @return string
     */
    function lookup($key)
    {
        $hash = mHash($key);
        // 对服务器列表逆排序
        if (!$this->isSorted) {
            krsort($this->serverList, SORT_NUMERIC);
            $this->isSorted = true;
        }
        // 查找相邻的数据
        foreach ($this->serverList as $pos => $server) {
            if ($hash >=  $pos) return $server;
        }
        // 找不到,返回最后一个
        return $this->serverList[count($this->serverList) - 1];
    }
}

        $hserver = new FlexiHash();
        $hserver->addServer('192.168.1.1');
        $hserver->addServer('192.168.1.2');
        $hserver->addServer('192.168.1.3');
        $hserver->addServer('192.168.1.4');
        $hserver->addServer('192.168.1.5');
        echo " save key1 in server:", $hserver->lookup('key1');
        echo '<br>';
        echo " save key2 in server:", $hserver->lookup('key2');
        
        $hserver->removeServer('192.168.1.4');
        echo '<br>';
        echo " save key1 in server:", $hserver->lookup('key1');
        echo '<br>';
        echo " save key2 in server:", $hserver->lookup('key2');
        
        
        ============================================================================================
        
        /**
 * 一致性hash算法的php实现
 */
class ConsistentHash 
{
    protected static $nodes = [];
    protected static $positions = [];
    protected $mulNum = 64;
    protected static function hashEncode($string)
    {
        #把字符串转换成32位无符号整数
        return sprintf('%u', crc32($string));
    }
    protected static function sort() 
    {
        ksort(self::$positions);
    }
    public function attach($node) 
    {
        for ($i=0; $i < $this->mulNum; $i++) { 
            $key = self::hashEncode($node . '-' . $i);
            self::$positions[$key] = $node;
        }
        self::sort();
    }
    public function detach($node)
    {
        foreach (self::$positions as $key => $value) {
            if ($node == $value) {
                unset(self::$positions[$key]);
                break;
            }
        }
    }
    public function getNodes() 
    {
        return self::$nodes;
    }
    public function getPositions() 
    {
        return self::$positions;
    }
    public function setMulNum($num) 
    {
        $this->mulNum = (int) $num;
    }
    public function search($key) 
    {
        $point = self::hashEncode($key);
        $hits = current(self::$positions);
        foreach (self::$positions as $key => $node) {
            if ($point <= $key) {
                $hits = $node;
                break;
            }
        }
        return $hits;
    }
}