一致性哈希虚节点解决雪崩问题
一致性哈希的基本原理大家都知道,就是找一个最近的节点或者顺时针,逆时针找一个节点,而不是通过模一个N去找节点,这样做的好处就是当其中一台磬机的时候,一致性哈希还可以继续工作,而模N的方法,所有定位在磬机节点上都会失败。如下代码是一个简单的一致性哈希:
<?php class Redis { //redis实例的id public $id; public function __construct($id) { $this->id = $id; } public function set($key, $value) { echo 'redis '.$this->id.': set key '. $key.'</br>'; } public function ok() { return true; } } class RedisGlobal { public $redis = array(); public function addServer($redis) { $this->redis[md5($redis->id)] = $redis; } public function set($key, $value) { $hash = md5($key); $serverFind = $hash; ksort($this->redis, SORT_REGULAR); foreach ($this->redis as $serverIndex=>$redis) { if ($serverIndex > $serverFind) { //找到一个server节点,检测该节点上redis是否可用 if ($redis->ok()) { $redis->set($key, $value); break; } } } } } $global = new RedisGlobal(); $global->addServer(new Redis(1)); $global->addServer(new Redis(2)); $global->addServer(new Redis(3)); for ($i=0; $i<10; $i++) { $global->set($i, $i); } ?>
//output
redis 3: set key 0
redis 2: set key 1
redis 3: set key 2
redis 1: set key 4
redis 3: set key 5
redis 1: set key 6
redis 1: set key 7
redis 3: set key 8
redis 1: set key 9
以上代码基本OK,不过上面的代码主要有三个问题,也是一致性哈希基本思想存在的问题。面试中也经常考察一个方案的弊端。
1.分布不均匀。
2.当一台磬机以后,磬机节点的所有请求会落到下一台主机,这样就有可能使下一台主机也磬机,这就是雪崩问题。
3.不同主机处理能力不同如何配置量。
解决以上两个问题,是通过配虚节点,也就是一个主机映射N个节点,下面的例子中默认映射100个节点,处理能力较强的主机可以配200或者更高的虚节点数量。
<?php class Redis { //redis实例的id public $id; public function __construct($id) { $this->id = $id; } public function set($key, $value) { echo 'redis '.$this->id.': set key '. $key.'</br>'; } public function ok() { return true; } } class RedisGlobal { public $redis = array(); public function addServer($redis, $weight=100) { for ($i=0; $i<$weight; $i++) { $this->redis[md5($redis->id.'-'.$i)] = $redis; } } public function set($key, $value) { $hash = md5($key); $serverFind = $hash; ksort($this->redis, SORT_REGULAR); foreach ($this->redis as $serverIndex=>$redis) { if ($serverIndex > $serverFind) { //找到一个server节点,检测该节点上redis是否可用 if ($redis->ok()) { $redis->set($key, $value); break; } } } } } $global = new RedisGlobal(); $global->addServer(new Redis(1)); $global->addServer(new Redis(2)); $global->addServer(new Redis(3)); for ($i=0; $i<10; $i++) { $global->set($i, $i); } ?>
//output
redis 1: set key 0
redis 1: set key 1
redis 3: set key 2
redis 1: set key 3
redis 2: set key 4
redis 1: set key 5
redis 2: set key 6
redis 2: set key 7
redis 3: set key 8
redis 1: set key 9