随着memcache、Redis以及其它一些内存K/V数据库的流行,一致性哈希也越来越被开发者所了解。因为这些内存K/V数据库大多不提供分布式支持(本文以redis为例),所以如果要提供多台redis server来提供服务的话,就需要解决如何将数据分散到redis server,并且在增减redis server时如何最大化的不令数据重新分布,这将是本文讨论的范畴。 取模算法 取模运算通常用于得到某个半开区间内的值:m % n = v,其中n不为0,值v的半开区间为:[0, n)。取模运算的算法很简单:有正整数k,并令k使得k和n的乘积最大但不超过m,则v的值为:m - kn。比如1 % 5,令k = 0,则k * 5的乘积最大并不超过1,故结果v = 1 - 0 * 5 = 1。 我们在分表时也会用到取模运算。如一个表要划分三个表,则可对3进行取模,因为结果总是在[0, 3)之内,也就是取值为:0、1、2。 但是对于应用到redis上,这种方式就不行了,因为太容易冲突了。 哈希(Hash) Hash,一般翻译做“散列”,也有直接音译为"哈希"的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。 简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。 目前普遍采用的哈希算法是time33,又称DJBX33A (Daniel J. Bernstein, Times 33 with Addition)。这个算法被广泛运用于多个软件项目,Apache、Perl和Berkeley DB等。对于字符串而言这是目前所知道的最好的哈希算法,原因在于该算法的速度非常快,而且分类非常好(冲突小,分布均匀)。 PHP内核就采用了time33算法来实现HashTable,来看下time33的定义: [php] view plain copy print?
- hash(i) = hash(i-1) * 33 + str[i]
- <?php
- function myHash($str) {
- // hash(i) = hash(i-1) * 33 + str[i]
- $hash = 0;
- $s = md5($str);
- $seed = 5;
- $len = 32;
- for ($i = 0; $i < $len; $i++) {
- // (hash << 5) + hash 相当于 hash * 33
- //$hash = sprintf("%u", $hash * 33) + ord($s{$i});
- //$hash = ($hash * 33 + ord($s{$i})) & 0x7FFFFFFF;
- $hash = ($hash << $seed) + $hash + ord($s{$i});
- }
- return $hash & 0x7FFFFFFF;
- }
- echo myHash("test"); //输出 786776064
- <?php
- function myHash($str) {
- // hash(i) = hash(i-1) * 33 + str[i]
- $hash = 0;
- $s = md5($str);
- $seed = 5;
- $len = 32;
- for ($i = 0; $i < $len; $i++) {
- // (hash << 5) + hash 相当于 hash * 33
- //$hash = sprintf("%u", $hash * 33) + ord($s{$i});
- //$hash = ($hash * 33 + ord($s{$i})) & 0x7FFFFFFF;
- $hash = ($hash << $seed) + $hash + ord($s{$i});
- }
- return $hash & 0x7FFFFFFF;
- }
- echo "key1: " . (myHash("key1") % 2) . "\n";
- echo "key2: " . (myHash("key2") % 2) . "\n";
- <?php
- function myHash($str) {
- // hash(i) = hash(i-1) * 33 + str[i]
- $hash = 0;
- $s = md5($str);
- $seed = 5;
- $len = 32;
- for ($i = 0; $i < $len; $i++) {
- // (hash << 5) + hash 相当于 hash * 33
- //$hash = sprintf("%u", $hash * 33) + ord($s{$i});
- //$hash = ($hash * 33 + ord($s{$i})) & 0x7FFFFFFF;
- $hash = ($hash << $seed) + $hash + ord($s{$i});
- }
- return $hash & 0x7FFFFFFF;
- }
- //echo myHash("却道天凉好个秋~");
- echo "key1: " . myHash("key1") . "\n";
- echo "key2: " . myHash("key2") . "\n";
- echo "key3: " . myHash("key3") . "\n";
- echo "serv1: " . myHash("server1") . "\n";
- echo "serv2: " . myHash("server2") . "\n";
- <?php
- function myHash($str) {
- // hash(i) = hash(i-1) * 33 + str[i]
- $hash = 0;
- $s = md5($str);
- $seed = 5;
- $len = 32;
- for ($i = 0; $i < $len; $i++) {
- // (hash << 5) + hash 相当于 hash * 33
- //$hash = sprintf("%u", $hash * 33) + ord($s{$i});
- //$hash = ($hash * 33 + ord($s{$i})) & 0x7FFFFFFF;
- $hash = ($hash << $seed) + $hash + ord($s{$i});
- }
- return $hash & 0x7FFFFFFF;
- }
- class ConsistentHash {
- // server列表
- private $_server_list = array();
- // 延迟排序,因为可能会执行多次addServer
- private $_layze_sorted = FALSE;
- public function addServer($server) {
- $hash = myHash($server);
- $this->_layze_sorted = FALSE;
- if (!isset($this->_server_list[$hash])) {
- $this->_server_list[$hash] = $server;
- }
- return $this;
- }
- public function find($key) {
- // 排序
- if (!$this->_layze_sorted) {
- asort($this->_server_list);
- $this->_layze_sorted = TRUE;
- }
- $hash = myHash($key);
- $len = sizeof($this->_server_list);
- if ($len == 0) {
- return FALSE;
- }
- $keys = array_keys($this->_server_list);
- $values = array_values($this->_server_list);
- // 如果不在区间内,则返回最后一个server
- if ($hash <= $keys[0] || $hash >= $keys[$len - 1]) {
- return $values[$len - 1];
- }
- foreach ($keys as $key=>$pos) {
- $next_pos = NULL;
- if (isset($keys[$key + 1]))
- {
- $next_pos = $keys[$key + 1];
- }
- if (is_null($next_pos)) {
- return $values[$key];
- }
- // 区间判断
- if ($hash >= $pos && $hash <= $next_pos) {
- return $values[$key];
- }
- }
- }
- }
- $consisHash = new ConsistentHash();
- $consisHash->addServer("serv1")->addServer("serv2")->addServer("server3");
- echo "key1 at " . $consisHash->find("key1") . ".\n";
- echo "key2 at " . $consisHash->find("key2") . ".\n";
- echo "key3 at " . $consisHash->find("key3") . ".\n";
- $ php -f test.php
- key1 at server3.
- key2 at server3.
- key3 at serv2.