一、启动多个memcached服务

./memcached -m 100 -p 11211 -d -u nobody 
./memcached -m 100 -p 11212 -d -u nobody 
./memcached -m 100 -p 11213- d -u nobody

二、分布式设定  

    由于memcached服务端没有提供分布式设置功能,所以只能依赖程序去设定,不过这一切都很简单。以php为例

$memcache=newMemcached; 
$memcache->setOptions(array(
        Memcached::OPT_DISTRIBUTION=>Memcached::DISTRIBUTION_CONSISTENT,
        Memcached::OPT_LIBKETAMA_COMPATIBLE=>true,
        Memcached::OPT_REMOVE_FAILED_SERVERS=>true,
));
$memcache->addServer('127.0.0.1',11211); 
$memcache->addServer('127.0.0.1',11212); 
$memcache->addServer('127.0.0.1',11213); 
for($i=0;$i<1000;$i++) { 
    $memcache->set('key'.$i,'v:'.$i); 
} 

// 可以telnet127.0.0.111211进入服务中查看key,每台memcached服务中都有数据存在且分布在不同的memcache中 

var_dump($memcache->get('key999')); 
var_dump($memcache->get('key0')); 
var_dump($memcache->get('key55')); 

// 其他算法 
// 取模算法sprintf('%u',crc32($key))%count($server) 
// 一致性hash算法sprintf('%u',crc32($key))

取模哈希

    crc32($key)%N,通过这个算法将键名映射到某一台服务器,比如需要存取一个键名为myname的缓存,服务器数量为3,那么通过算法计算:crc32('myname')%3=0,那么这个缓存就落到第1台服务器上面

这种方式虽然简单可行,但是增减服务器的时候,缓存将面临大量的重建,比如上面的例子中,新增了1台服务器,服务器数量变为4台,通过算法计算:crc32('myname')%4=3,从第1台变成第3台了,导致缓存重建;又比如第1台服务器挂了,缓存的存取都会失败,导致短时间内大量的请求涌入mysql


一致性哈希

    一致性哈希就是为了解决上面的缓存重建而设计的,取模法不理想的原因就是算法的本质就是根据服务器数量来计算的,缓存跟服务器是一一对应,要想灵活一点就不能是一对一的关系,一致性哈希算法首先创建出一个首( 0 )尾( 2^32-1 )相接的环形的哈希空间,如下图的圆环,然后把服务器通过hash算法映射到环形的某一点,如下图中node1、node2、node3、node4,然后再把缓存的键映射到环形的某一点,获取某个键的内容是从这个键的节点按顺时针方向开始查找服务器节点,找到的第一台服务器就是这个缓存要进行存取的服务器,如此一来当node1服务器挂了,影响到的只是从node3到node1节点之间的缓存数据,这些数据将会去node2中存取,这样可以把缓存重建的代价降低。

    引入虚拟节点的思想,解决一致性hash算法分布不均导致负载不均的问题。一个真实节点对应若干个虚拟节点,当key被映射到虚拟节点上时,则被认为映射到虚拟节点所对应的真实节点上。 优点:引入虚拟节点的思想,每个物理节点对应圆环上若干个虚拟节点(比如200~300个),当keyhash到虚拟节点,就会存储到实际的物理节点上,有效的实现了负载均衡


三、一致性hash算法

<?php

// 一致性哈希算法
class ConsistentHash
{
    protected $nodes = [];
    protected $position = [];
    protected $mul = 64;  // 每个节点对应64个虚拟节点

    /**
     * 把字符串转为32位符号整数
     */
    public function hash($str)
    {
        return sprintf('%u', crc32($str));
    }

    /**
     * 核心功能
     */
    public function lookup($key)
    {
        $point = $this->hash($key);

        //先取圆环上最小的一个节点,当成结果
        $node = current($this->position);

        // 循环获取相近的节点
        foreach ($this->position as $key => $val) {
            if ($point <= $key) {
                $node = $val;
                break;
            }
        }

        reset($this->position);

        return $node;
    }

    /**
     * 添加节点
     */
    public function addNode($node)
    {
        if(isset($this->nodes[$node])) return;

        // 添加节点和虚拟节点
        for ($i = 0; $i < $this->mul; $i++) {
            $pos = $this->hash($node . '-' . $i);
            $this->position[$pos] = $node;
            $this->nodes[$node][] = $pos;
        }

        // 重新排序
        $this->sortPos();
    }

    /**
     * 删除节点
     */
    public function delNode($node)
    {
        if (!isset($this->nodes[$node])) return;

        // 循环删除虚拟节点
        foreach ($this->nodes[$node] as $val) {
            unset($this->position[$val]);
        }

        // 删除节点
        unset($this->nodes[$node]);
    }

    /**
     * 排序
     */
    public function sortPos()
    {
        ksort($this->position, SORT_REGULAR);
    }
}


// 测试
$con = new ConsistentHash();

$con->addNode('a');
$con->addNode('b');
$con->addNode('c');
$con->addNode('d');

$key1 = 'key1';
$key2 = 'key2';
$key3 = 'key3';

echo 'key' . $key1 . '落在' . $con->lookup($key1) . '号节点上!<br>';
echo 'key' . $key2 . '落在' . $con->lookup($key2) . '号节点上!<br>';
echo 'key' . $key3 . '落在' . $con->lookup($key3) . '号节点上!<br>';