Redis一致性hash(php版)

一致性hash的使用在PHP中有三种选择分别是原生的memcache扩展,memcached扩展,还有一个是网上比较流行的flexihash类。前两者都适用于memcache但不适合Redis。

php一致性hash类下载地址:http://code.google.com/p/flexihash/

我们根据flexihash的应该改写了一遍Redis的应用。

下面给出测试源码:

flexihash.php
[php]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <?php  
  2. /** 
  3.  * Flexihash - A simple consistent hashing implementation for PHP. 
  4.  * 
  5.  * The MIT License 
  6.  * 
  7.  * Copyright (c) 2008 Paul Annesley 
  8.  * 
  9.  * Permission is hereby granted, free of charge, to any person obtaining a copy 
  10.  * of this software and associated documentation files (the "Software"), to deal 
  11.  * in the Software without restriction, including without limitation the rights 
  12.  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
  13.  * copies of the Software, and to permit persons to whom the Software is 
  14.  * furnished to do so, subject to the following conditions: 
  15.  * 
  16.  * The above copyright notice and this permission notice shall be included in 
  17.  * all copies or substantial portions of the Software. 
  18.  * 
  19.  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
  20.  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
  21.  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
  22.  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
  23.  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
  24.  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
  25.  * THE SOFTWARE. 
  26.  * 
  27.  * @author Paul Annesley 
  28.  * @link http://paul.annesley.cc/ 
  29.  * @copyright Paul Annesley, 2008 
  30.  * @comment by MyZ (http://blog.csdn.net/mayongzhan) 
  31.  */  
  32.   
  33. /** 
  34.  * A simple consistent hashing implementation with pluggable hash algorithms. 
  35.  * 
  36.  * @author Paul Annesley 
  37.  * @package Flexihash 
  38.  * @licence http://www.opensource.org/licenses/mit-license.php 
  39.  */  
  40. class flexihash  
  41. {  
  42.   
  43.     /** 
  44.      * The number of positions to hash each target to. 
  45.      * 
  46.      * @var int 
  47.      * @comment 虚拟节点数,解决节点分布不均的问题 
  48.      */  
  49.     private $_replicas = 64;  
  50.   
  51.     /** 
  52.      * The hash algorithm, encapsulated in a Flexihash_Hasher implementation. 
  53.      * @var object Flexihash_Hasher 
  54.      * @comment 使用的hash方法 : md5,crc32 
  55.      */  
  56.     private $_hasher;  
  57.   
  58.     /** 
  59.      * Internal counter for current number of targets. 
  60.      * @var int 
  61.      * @comment 节点记数器 
  62.      */  
  63.     private $_targetCount = 0;  
  64.   
  65.     /** 
  66.      * Internal map of positions (hash outputs) to targets 
  67.      * @var array { position => target, ... } 
  68.      * @comment 位置对应节点,用于lookup中根据位置确定要访问的节点 
  69.      */  
  70.     private $_positionToTarget = array();  
  71.   
  72.     /** 
  73.      * Internal map of targets to lists of positions that target is hashed to. 
  74.      * @var array { target => [ position, position, ... ], ... } 
  75.      * @comment 节点对应位置,用于删除节点 
  76.      */  
  77.     private $_targetToPositions = array();  
  78.   
  79.     /** 
  80.      * Whether the internal map of positions to targets is already sorted. 
  81.      * @var boolean 
  82.      * @comment 是否已排序 
  83.      */  
  84.     private $_positionToTargetSorted = false;  
  85.   
  86.     /** 
  87.      * Constructor 
  88.      * @param object $hasher Flexihash_Hasher 
  89.      * @param int $replicas Amount of positions to hash each target to. 
  90.      * @comment 构造函数,确定要使用的hash方法和需拟节点数,虚拟节点数越多,分布越均匀,但程序的分布式运算越慢 
  91.      */  
  92.     public function __construct(Flexihash_Hasher $hasher = null, $replicas = null)  
  93.     {  
  94.         $this->_hasher = $hasher ? $hasher : new Flexihash_Crc32Hasher();  
  95.         if (!empty($replicas)) $this->_replicas = $replicas;  
  96.     }  
  97.   
  98.     /** 
  99.      * Add a target. 
  100.      * @param string $target 
  101.      * @chainable 
  102.      * @comment 添加节点,根据虚拟节点数,将节点分布到多个虚拟位置上 
  103.      */  
  104.     public function addTarget($target)  
  105.     {  
  106.         if (isset($this->_targetToPositions[$target]))  
  107.         {  
  108.             throw new Flexihash_Exception("Target '$target' already exists.");  
  109.         }  
  110.   
  111.         $this->_targetToPositions[$target] = array();  
  112.   
  113.         // hash the target into multiple positions  
  114.         for ($i = 0; $i < $this->_replicas; $i++)  
  115.         {  
  116.             $position = $this->_hasher->hash($target . $i);  
  117.             $this->_positionToTarget[$position] = $target// lookup  
  118.             $this->_targetToPositions[$target] []= $position// target removal  
  119.         }  
  120.   
  121.         $this->_positionToTargetSorted = false;  
  122.         $this->_targetCount++;  
  123.   
  124.         return $this;  
  125.     }  
  126.   
  127.     /** 
  128.      * Add a list of targets. 
  129.      * @param array $targets 
  130.      * @chainable 
  131.      */  
  132.     public function addTargets($targets)  
  133.     {  
  134.         foreach ($targets as $target)  
  135.         {  
  136.             $this->addTarget($target);  
  137.         }  
  138.   
  139.         return $this;  
  140.     }  
  141.   
  142.     /** 
  143.      * Remove a target. 
  144.      * @param string $target 
  145.      * @chainable 
  146.      */  
  147.     public function removeTarget($target)  
  148.     {  
  149.         if (!isset($this->_targetToPositions[$target]))  
  150.         {  
  151.             throw new Flexihash_Exception("Target '$target' does not exist.");  
  152.         }  
  153.   
  154.         foreach ($this->_targetToPositions[$targetas $position)  
  155.         {  
  156.             unset($this->_positionToTarget[$position]);  
  157.         }  
  158.   
  159.         unset($this->_targetToPositions[$target]);  
  160.   
  161.         $this->_targetCount--;  
  162.   
  163.         return $this;  
  164.     }  
  165.   
  166.     /** 
  167.      * A list of all potential targets 
  168.      * @return array 
  169.      */  
  170.     public function getAllTargets()  
  171.     {  
  172.         return array_keys($this->_targetToPositions);  
  173.     }  
  174.   
  175.     /** 
  176.      * Looks up the target for the given resource. 
  177.      * @param string $resource 
  178.      * @return string 
  179.      */  
  180.     public function lookup($resource)  
  181.     {  
  182.         $targets = $this->lookupList($resource, 1);  
  183.         if (empty($targets)) throw new Flexihash_Exception('No targets exist');  
  184.         return $targets[0];  
  185.     }  
  186.   
  187.     /** 
  188.      * Get a list of targets for the resource, in order of precedence. 
  189.      * Up to $requestedCount targets are returned, less if there are fewer in total. 
  190.      * 
  191.      * @param string $resource 
  192.      * @param int $requestedCount The length of the list to return 
  193.      * @return array List of targets 
  194.      * @comment 查找当前的资源对应的节点, 
  195.      *          节点为空则返回空,节点只有一个则返回该节点, 
  196.      *          对当前资源进行hash,对所有的位置进行排序,在有序的位置列上寻找当前资源的位置 
  197.      *          当全部没有找到的时候,将资源的位置确定为有序位置的第一个(形成一个环) 
  198.      *          返回所找到的节点 
  199.      */  
  200.     public function lookupList($resource$requestedCount)  
  201.     {  
  202.         if (!$requestedCount)  
  203.             throw new Flexihash_Exception('Invalid count requested');  
  204.   
  205.         // handle no targets  
  206.         if (empty($this->_positionToTarget))  
  207.             return array();  
  208.   
  209.         // optimize single target  
  210.         if ($this->_targetCount == 1)  
  211.             return array_unique(array_values($this->_positionToTarget));  
  212.   
  213.         // hash resource to a position  
  214.         $resourcePosition = $this->_hasher->hash($resource);  
  215.   
  216.         $results = array();  
  217.         $collect = false;  
  218.   
  219.         $this->_sortPositionTargets();  
  220.   
  221.         // search values above the resourcePosition  
  222.         foreach ($this->_positionToTarget as $key => $value)  
  223.         {  
  224.             // start collecting targets after passing resource position  
  225.             if (!$collect && $key > $resourcePosition)  
  226.             {  
  227.                 $collect = true;  
  228.             }  
  229.   
  230.             // only collect the first instance of any target  
  231.             if ($collect && !in_array($value$results))  
  232.             {  
  233.                 $results []= $value;  
  234.             }  
  235.   
  236.             // return when enough results, or list exhausted  
  237.             if (count($results) == $requestedCount || count($results) == $this->_targetCount)  
  238.             {  
  239.                 return $results;  
  240.             }  
  241.         }  
  242.   
  243.         // loop to start - search values below the resourcePosition  
  244.         foreach ($this->_positionToTarget as $key => $value)  
  245.         {  
  246.             if (!in_array($value$results))  
  247.             {  
  248.                 $results []= $value;  
  249.             }  
  250.   
  251.             // return when enough results, or list exhausted  
  252.             if (count($results) == $requestedCount || count($results) == $this->_targetCount)  
  253.             {  
  254.                 return $results;  
  255.             }  
  256.         }  
  257.   
  258.         // return results after iterating through both "parts"  
  259.         return $results;  
  260.     }  
  261.   
  262.     public function __toString()  
  263.     {  
  264.         return sprintf(  
  265.             '%s{targets:[%s]}',  
  266.             get_class($this),  
  267.             implode(','$this->getAllTargets())  
  268.         );  
  269.     }  
  270.   
  271.     // ----------------------------------------  
  272.     // private methods  
  273.   
  274.     /** 
  275.      * Sorts the internal mapping (positions to targets) by position 
  276.      */  
  277.     private function _sortPositionTargets()  
  278.     {  
  279.         // sort by key (position) if not already  
  280.         if (!$this->_positionToTargetSorted)  
  281.         {  
  282.             ksort($this->_positionToTarget, SORT_REGULAR);  
  283.             $this->_positionToTargetSorted = true;  
  284.         }  
  285.     }  
  286.   
  287. }  
  288.   
  289.   
  290. /** 
  291.  * Hashes given values into a sortable fixed size address space. 
  292.  * 
  293.  * @author Paul Annesley 
  294.  * @package Flexihash 
  295.  * @licence http://www.opensource.org/licenses/mit-license.php 
  296.  */  
  297. interface Flexihash_Hasher  
  298. {  
  299.   
  300.     /** 
  301.      * Hashes the given string into a 32bit address space. 
  302.      * 
  303.      * Note that the output may be more than 32bits of raw data, for example 
  304.      * hexidecimal characters representing a 32bit value. 
  305.      * 
  306.      * The data must have 0xFFFFFFFF possible values, and be sortable by 
  307.      * PHP sort functions using SORT_REGULAR. 
  308.      * 
  309.      * @param string 
  310.      * @return mixed A sortable format with 0xFFFFFFFF possible values 
  311.      */  
  312.     public function hash($string);  
  313.   
  314. }  
  315.   
  316.   
  317. /** 
  318.  * Uses CRC32 to hash a value into a signed 32bit int address space. 
  319.  * Under 32bit PHP this (safely) overflows into negatives ints. 
  320.  * 
  321.  * @author Paul Annesley 
  322.  * @package Flexihash 
  323.  * @licence http://www.opensource.org/licenses/mit-license.php 
  324.  */  
  325. class Flexihash_Crc32Hasher  
  326.     implements Flexihash_Hasher  
  327. {  
  328.   
  329.     /* (non-phpdoc) 
  330.      * @see Flexihash_Hasher::hash() 
  331.      */  
  332.     public function hash($string)  
  333.     {  
  334.         return crc32($string);  
  335.     }  
  336.   
  337. }  
  338.   
  339.   
  340. /** 
  341.  * Uses CRC32 to hash a value into a 32bit binary string data address space. 
  342.  * 
  343.  * @author Paul Annesley 
  344.  * @package Flexihash 
  345.  * @licence http://www.opensource.org/licenses/mit-license.php 
  346.  */  
  347. class Flexihash_Md5Hasher  
  348.     implements Flexihash_Hasher  
  349. {  
  350.   
  351.     /* (non-phpdoc) 
  352.      * @see Flexihash_Hasher::hash() 
  353.      */  
  354.     public function hash($string)  
  355.     {  
  356.         return substr(md5($string), 0, 8); // 8 hexits = 32bit  
  357.   
  358.         // 4 bytes of binary md5 data could also be used, but  
  359.         // performance seems to be the same.  
  360.     }  
  361.   
  362. }  
  363.   
  364.   
  365. /** 
  366.  * An exception thrown by Flexihash. 
  367.  * 
  368.  * @author Paul Annesley 
  369.  * @package Flexihash 
  370.  * @licence http://www.opensource.org/licenses/mit-license.php 
  371.  */  
  372. class Flexihash_Exception extends Exception  
  373. {  
  374. }  


flexihash_redis.php
[php]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <?php  
  2. require 'flexihash.php';  
  3. Class FRedis  
  4. {  
  5.     public $hash = null;  
  6.     public $memcache = null;  
  7.     public $connectPool = null;  
  8.     public $timeOut = 3;  
  9.     public $auth = "";  
  10.    
  11.     public function __construct()  
  12.     {  
  13.         $this->hash = new Flexihash();  
  14.     }  
  15.    
  16.     public function addServers( $servers )  
  17.     {  
  18.         foreach ($servers as $server)  
  19.         {  
  20.             $node = $server['host'] . ':' . $server['port'];  
  21.             $this->connectPool[$node] = false;  
  22.             $targets[] = $node;  
  23.         }  
  24.         $this->hash->addTargets( $targets );  
  25.           
  26.     }  
  27.    
  28.     public function hset($hashkey$valuekey$value )  
  29.     {  
  30.         $node = $this->hash->lookup( $hashkeycount$this->connectPool ) );  
  31.         if (!$this->connectPool[$node])  
  32.         {  
  33.             $server = explode':'$node );  
  34.             $redis = new Redis();  
  35.             $redis->connect($server[0], $server[1], $this->timeOut);  
  36.             if(!empty($this->auth)){  
  37.                 $redis->auth($this->auth);  
  38.             }  
  39.             $this->connectPool[$node] = $redis;  
  40.   
  41.         }  
  42.         if ($this->connectPool[$node])  
  43.         {  
  44.             $redis = $this->connectPool[$node];  
  45.             if($redis->hset($hashkey,$valuekey,$value)){  
  46.                 return true;  
  47.             }  
  48.         }  
  49.         return false;  
  50.     }  
  51.    
  52.     public function hget( $hashkey$valuekey )  
  53.     {  
  54.         $node = $this->hash->lookup( $hashkeycount$this->connectPool ) );  
  55.           
  56.         if (!$this->connectPool[$node])  
  57.         {  
  58.             $server = explode':'$node );  
  59.             $redis = new Redis();  
  60.             $redis->connect($server[0], $server[1], $this->timeOut);  
  61.             if(!empty($this->auth)){  
  62.                 $redis->auth($this->auth);  
  63.             }  
  64.             $this->connectPool[$node] = $redis;  
  65.           
  66.         }  
  67.         if ($this->connectPool[$node])  
  68.         {  
  69.             $redis = $this->connectPool[$node];  
  70.             if($value = $redis->hget($hashkey,$valuekey)){  
  71.                 return $value;  
  72.             }  
  73.         }  
  74.         return false;  
  75.     }  
  76. }  

FRedis_test.php
[php]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <?php  
  2. require_once 'flexihash_redis.php';  
  3. $st = microtime( true );  
  4. $fredis = new FRedis();  
  5. $tt_server_pool = array(  
  6.     '0' => array(  
  7.         'host' => '192.168.75.128',  
  8.         'port' => '6379',  
  9.     ),  
  10.     '1' => array(  
  11.         'host' => '192.168.75.130',  
  12.         'port' => '6379',  
  13.     ),  
  14. );  
  15. $fredis->addServers( $tt_server_pool );  
  16.   
  17. for($i=0;$i<10;$i++){  
  18.     for($k=0;$k<10;$k++){  
  19.         $fredis->hset("hashkey:".$i,"valuekey".$k,md5($i*$k));  
  20.     }  
  21. }  
  22.   
  23. $et = microtime( true ) - $st;  
  24. echo "time used:" . $et."<br/>";  
  25. echo $fredis->hget("hashkey:0","valuekey9");  

Redis版哈希函数类:

[php]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <?php  
  2. class UniqueHashService{  
  3.     private static $len//64位  
  4.     private static $loop = 1; //循环次数  
  5.     private $prefix = '';  
  6.       
  7.     public function __construct() {  
  8.         if(empty(self::$len)){  
  9.             self::$len = abs(1 << 0x3f); //左移63位  
  10.         }  
  11.           
  12.     }  
  13.       
  14.     /** 
  15.      * 获取字符串的哈希值 
  16.      */  
  17.     public function hashing($key){  
  18.         return abs((('0x'.hash('crc32',$key).hash('crc32b',$key)) + 0)) % self::$len;  
  19.     }  
  20.       
  21.     /** 
  22.      * 获取到redis hash 的键名 
  23.      * @param type $str 
  24.      */  
  25.     public function getRedisKey($str$prefix){  
  26.         //前缀  
  27.         $this->prefix = $prefix;  
  28.           
  29.         $hstr = $this->hashing($str);    //获取一个字符串的哈希值  
  30.           
  31.         //如果字符串长度小于6,往字符串左侧填充0,填充至6位长度。  
  32.         if(strlen($hstr) < 6){  
  33.             $hstr = str_pad($hstr, 6, '0', STR_PAD_LEFT);  
  34.         }  
  35.         $kbin = $this->str2Bin($hstr);  //计算hash key的二进制,$hstr的二进制  
  36.           
  37.         $karr = explode(' '$kbin);  
  38.           
  39.         //数组长度  
  40.         $kcount = count($karr);  
  41.           
  42.         $kcount_mid = ceil($kcount/2);  
  43.           
  44.         $redis_key = $this->prefix; //排重表前缀  
  45.           
  46.         //循环次数  
  47.         $i = 0;  
  48.         while(1){  
  49.             if($i > self::$loop){  
  50.                 break;  
  51.             }  
  52.               
  53.             //组装redis键  
  54.             $redis_key .= ':'.$this->returnHashKey($karr[$i]);  
  55.             $redis_key .= ':'.$this->returnHashKey($karr[$kcount_mid+$i]);  
  56.             $redis_key .= ':'.$this->returnHashKey($karr[$kcount-1-$i]);  
  57.               
  58.             ++$i;  
  59.         }  
  60.           
  61.         return $redis_key;  
  62.     }  
  63.       
  64.     /** 
  65.      * 截取二进制字符串中的一部分 
  66.      * @param type $sbin 
  67.      * @return type 
  68.      */  
  69.     private function returnHashKey($sbin){  
  70.         return substr($sbin, -2, 2);  
  71.     }  
  72.       
  73.     /** 
  74.      * 把给定的字符串中的每个字符转为二进制,并以空格连接每个二进制字符串并返回。 
  75.      * @param string $hstr 
  76.      * @return type 
  77.      */  
  78.     private function str2Bin($str){  
  79.         //把字符串拆分成数组,每个字符为数组中的一个元素。  
  80.         $arr = str_split($str);  
  81.           
  82.         //把数组中的每个字符转成二进制  
  83.         foreach($arr as &$v){  
  84.             $tmp = unpack('H*'$v);        //把字符解包为16进制  
  85.             $v = base_convert($tmp[1],16,2);//把16进制转为2进制  
  86.             unset($tmp);  
  87.         }  
  88.           
  89.         //把二进制字符串数组以空格连接在一起并返回  
  90.         $bin = join(' ',$arr);  
  91.         unset($arr);  
  92.         return $bin;  
  93.     }  
  94.       
  95.     /** 
  96.      * 把$this->str2Bin 处理过的字符串还原 
  97.      * @param string $bin 二进制字符串 
  98.      * @return type 
  99.      */  
  100.     private function bin2Str($bin){  
  101.         //把字符串拆分成数组,以空拆分。  
  102.         $arr = explode(' '$bin);  
  103.           
  104.         foreach($arr as &$v){  
  105.             $tmp = base_convert($v, 2, 16);     //二进制转换成16进制  
  106.             $v = pack('H'.strlen($tmp), $tmp);  //把16进制打包成二进制字符串  
  107.             unset($tmp);  
  108.         }  
  109.           
  110.         return join(''$arr);  
  111.     }  
  112. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: Redis一致性哈希是一种分布式算法,用于解决在缓存系统中增加或减少实例时可能出现的缓存击穿和缓存雪崩问题。这种算法使用哈希函数将整个哈希值空间组织成一个虚拟的圆环,将不同的缓存数据映射到圆环上的不同位置。具体而言,Redis实例的个数可能会发生变化,当增加或减少实例时,映射关系就会改变,导致大量的Redis请求找不到对应的实例。而一致性哈希算法通过对232取模的方式,将哈希值映射到圆环上的位置,从而实现了负载均衡和缓存数据的一致性存储。相比传统的取模操作,一致性哈希算法能够更好地应对增减Redis实例的情况,提高缓存命中率。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Redis-Hash一致性算法](https://blog.csdn.net/WangLi1201/article/details/79270073)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Redis一致性Hash算法](https://blog.csdn.net/qq_21125183/article/details/90019034)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Redis 一致性哈希](https://blog.csdn.net/m0_54921756/article/details/125987939)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值