Memcached 笔记与总结(8)Memcached 的普通哈希分布算法和一致性哈希分布算法命中率对比

来源:互联网 发布:linux实时守护进程 编辑:程序博客网 时间:2024/04/26 02:45

准备工作

① 配置文件 config.php

② 封装 Memcached 类 hash.class.php,包含普通哈希算法(取模)和一致性哈希算法

③ 初始化 Memcached 节点信息 init.php

④ 减少 Memcached 节点 down.php 

⑤ 统计命中率 statistics.php

⑥ 使用 Highcharts(4.1.9) js 图表库来展示减少节点后两种算法命中率的变化

 

 

1. 配置文件

config.php

复制代码
<?php/*    Memcached 配置文件*///Memcached 节点信息$mem_servers = array();$mem_servers['s1'] = array('host'=>'127.0.0.1', 'port'=>'11211');$mem_servers['s2'] = array('host'=>'127.0.0.1', 'port'=>'11212');$mem_servers['s3'] = array('host'=>'127.0.0.1', 'port'=>'11213');$mem_servers['s4'] = array('host'=>'127.0.0.1', 'port'=>'11214');$mem_servers['s5'] = array('host'=>'127.0.0.1', 'port'=>'11215');//哈希策略选择$method = 'mod';//普通哈希//$method = 'dis';//一致性哈希
复制代码

说明:模拟 5 台 Memcached 服务器,使用相同的本地主机,不同的端口号。 

 

 

2. 封装 Memcached 类

在 Memcached 笔记与总结(6)PHP 实现 Memcached 的一致性哈希分布算法 的基础上增加普通哈希类:

复制代码
//普通哈希class modHash implements hash, distribute {    private $serverList = array();//服务器列表    private $size = 0;            //节点的个数        public function _hash($str){        return sprintf('%u', crc32($str));//把字符串转成32为无符号整数    }    public function lookup($key){        $key = $this->_hash($key) % $this->size; //取模        return $this->serverList[$key];    }    public function addServer($server){        if (in_array($server, $this->serverList)) {            return;        }        $this->serverList[] = $server;        $this->size += 1;        return true;    }    public function removeServer($server){        if (!in_array($server, $this->serverList)) {            return;        }        $key = array_search($server, $this->serverList);        unset($this->serverList[$key]);        $this->serverList = array_merge($this->serverList);//删除节点后重新索引数组        $this->size -= 1;        return true;    }}
复制代码

说明:如果仅仅向 array_merge() 函数输入了一个数组,且键名是整数,则该函数将返回带有整数键名的新数组,其键名以 0 开始进行重新索引。

 

完整 哈希类:

复制代码
  1 <?php  2 //把字符串转换为整数  3 interface hash{  4     public function _hash($str);  5 }  6   7 interface distribute{  8     //在当前的服务器列表中找到合适的服务器存放数据  9     public function lookup($key); 10      11     //添加一个服务器到服务器列表中 12     public function addServer($server); 13  14     //从服务器列表中删除一个服务器 15     public function removeServer($server); 16 } 17  18 //一致性哈希 19 class consistentHash implements hash, distribute{ 20  21     private $serverList = array();//以二维数组保存服务器列表和每一个服务器下虚拟节点的哈希值 22     private $position = array();//以键值形式保存所有虚拟节点的哈希值(键)和对应的服务器(值)的一维数组 23     private $isSorted = FALSE; //记录虚拟节点哈希值列表是否已经排列过序  24      25     public function _hash($str){ 26         return sprintf('%u', crc32($str));//把字符串转成32为无符号整数 27     } 28  29     public function lookup($key){ 30         //计算出服务器的Hash值 31         $hash = $this->_hash($key); 32  33         //判断服务器列表是否排过序 34         if (!$this->isSorted) { 35             //倒序排列(把虚拟节点列表转换成逆时针圆环) 36             krsort($this->position, SORT_NUMERIC); 37             $this->isSorted = TRUE; 38         } 39  40         //遍历虚拟节点列表,找到合适的服务器并返回 41         foreach($this->position as $server_hash=> $server){ 42             if ($hash >= $server_hash) return $server; 43         } 44         return end($this->position); 45     } 46  47     public function addServer($server, $nodesNum = 25){ 48  49         if (isset($this->serverList[$server])) { 50             return; 51         } 52  53         //增加虚拟节点,默认每个物理节点变成25个虚拟节点 54         for($i = 0; $i < $nodesNum; $i++){ 55             $hash = $this->_hash($server.'-'.$i);//计算虚拟节点的Hash值 56             $this->position[$hash] = $server; 57             $this->serverList[$server][] = $hash; 58         } 59          60         //此时服务器列表发生了变化,因此标识为FALSE 61         $this->isSorted = FALSE; 62         return TRUE; 63     } 64  65     public function removeServer($server){ 66  67         if (!isset($this->serverList[$server])) { 68             return; 69         } 70  71         //循环position数组,如果要删除的服务器的值等于position数组某个元素的键,则删除该元素 72         foreach($this->position as $k=>$v){ 73             if($server == $v){ 74                 unset($this->position[$k]); 75             } 76         } 77  78         unset($this->serverList[$server]); 79  80         $this->isSorted = FALSE; 81         return TRUE; 82     } 83 } 84  85 //普通哈希 86 class modHash implements hash, distribute { 87     private $serverList = array();//服务器列表 88     private $size = 0;              //节点的个数 89      90     public function _hash($str){ 91         return sprintf('%u', crc32($str));//把字符串转成32为无符号整数 92     } 93  94     public function lookup($key){ 95         $key = $this->_hash($key) % $this->size; 96         return $this->serverList[$key]; 97     } 98  99     public function addServer($server){100 101         if (in_array($server, $this->serverList)) {102             return;103         }104 105         $this->serverList[] = $server;106         $this->size += 1;107 108         return true;109     }110 111     public function removeServer($server){112 113         if (!in_array($server, $this->serverList)) {114             return;115         }116 117         $key = array_search($server, $this->serverList);118         unset($this->serverList[$key]);119         $this->serverList = array_merge($this->serverList);//删除节点后重新索引数组120         $this->size -= 1;121 122         return true;123     }124 }
复制代码

 

测试普通哈希类节点是否正确:

复制代码
<?phprequire './config.php';require './hash.class.php';$hashserver = new modHash();$hashserver->addServer($mem_servers['s1']);$hashserver->addServer($mem_servers['s2']);$hashserver->addServer($mem_servers['s3']);$hashserver->addServer($mem_servers['s4']);$hashserver->addServer($mem_servers['s5']);function showServer($obj, $key) {    $serverInfo = $obj->lookup($key);    return $key.' on server:'.$serverInfo['host'].", port:".$serverInfo['port'];}echo showServer($hashserver, 'key1'),'<br />';echo showServer($hashserver, 'key2'),'<br />';
复制代码

输出:

key1 on server:127.0.0.1, port:11212key2 on server:127.0.0.1, port:11213

其中 key1 经过 crc32 转换后得到 744252496,模 5 为 1;key2 经过 crc32 转换后得到 3042260458,模 5 为 3。

 

 

3. 初始化 Memcached 节点信息 init.php

循环添加服务器,并且把 10000 条数据(按照普通哈希/一致性哈希)插入到添加的 5 台 Memcached 服务器中,平均每台 2000 条数据

分别开启 5 台 Memcached 服务器:

 

init.php

复制代码
<?phpheader("Content-type:text/html; charset=utf-8");set_time_limit(0);require './config.php';require './hash.class.php';$mem = new memcache();$hash = new modHash();//普通哈希//循环添加服务器foreach($mem_servers as $k=>$v){    $hash->addServer($k);}//向服务器中添加共10000条数据for($i = 0; $i < 10000; $i++) {    $key = 'key'.$i;    $value = 'value'.$i;    $server = $mem_servers[$hash->lookup($key)];    $mem->pconnect($server['host'], (int)$server['port'], 2);//设置超时时间为2秒        $mem->set($key, $value, 0, 0);//不自动过期    usleep(3000);}echo '初始化数据完毕';
复制代码

说明:

memcache::pconnect() :打开一个到服务器的持久化连接,它的第 2 个参数要求是长整型 long

 

执行 init.php

 

输出:初始化数据完毕

 

 

使用 Telnet 客户端连接 Memcached 服务器查看数据:

 

(127.0.0.1:11211)输入 stats:

其中 total_items 有 2559 个。

 

 

4. 减少 Memcached 节点 :down.php 

 

复制代码
<?phpheader("Content-type:text/html; charset=utf-8");set_time_limit(0);require './config.php';require './hash.class.php';$mem = new memcache();$hash = new modHash();//普通哈希//循环添加服务器foreach($mem_servers as $k=>$v){    $hash->addServer($k);}//模拟减少一台Memcached服务器$hash->removeServer('s3');for($i = 0; $i < 10000; $i++) {    $key = 'key'.$i;    $value = 'value'.$i;    $server = $mem_servers[$hash->lookup($key)];    $mem->pconnect($server['host'], (int)$server['port'], 2);//设置超时时间为2秒        if(!$mem->get($key, $value)){        $mem->set($key, $value, 0, 0);//不自动过期    }    usleep(3000);}
复制代码

 

 

5. 统计命中率 statistics.php

统计 Memcached 各节点的平均命中率,用于 Ajax 请求

statistics.php

复制代码
<?phpheader("Content-type:text/html; charset=utf-8");set_time_limit(0);require './config.php';$mem = new memcache();$gets = 0;//请求次数$hits = 0;//命中次数foreach ($mem_servers as $k => $v) {    $mem->pconnect($v['host'], $v['port'], 2);//设置超时时间为2秒    $res = $mem->getstats();    $gets += $res['cmd_get'];    $hits += $res['get_hits'];}$rate = 1;if($gets > 0) {    $rate = $hits / $gets;}echo $rate;
复制代码

 

说明:

memcache::getstats() 输出数据格式如下

View Code

 

 

6. 使用 Highcharts(4.1.9) js 图表库来展示减少节点后两种算法命中率的变化

官方地址:http://www.highcharts.com/

下载地址:http://code.highcharts.com/zips/Highcharts-4.1.9.zip

 

index.html,该文件通过 Ajax 每 2 秒向 statistics.php 发出请求获取 Memcached 的命中率

注:解压 Highcharts 压缩包, 拷贝 Highcharts-4.1.9\examples\dynamic-update\index.html 至项目目录,修改并且重命名为 index.html

复制代码
<!DOCTYPE HTML><html>    <head>        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">        <title>Highcharts Example</title>        <script type="text/javascript" src="jquery-1.8.3.min.js"></script>        <style type="text/css">${demo.css}        </style>        <script type="text/javascript">$(function () {    $(document).ready(function () {        Highcharts.setOptions({            global: {                useUTC: false            }        });        $('#container').highcharts({            chart: {                type: 'spline',                animation: Highcharts.svg, // don't animate in old IE                marginRight: 10,                events: {                    load: function () {                        // set up the updating of the chart each second                        var series = this.series[0];                        setInterval(function () {                            var x = (new Date()).getTime(), // current time                                y = parseFloat($.ajax({url:'statistics.php', async:false}).responseText);                            series.addPoint([x, y], true, true);                        }, 2000);                    }                }            },            title: {                text: 'Memcached hit rates'            },            xAxis: {                type: 'datetime',                tickPixelInterval: 150            },            yAxis: {                title: {                    text: 'Value'                },                plotLines: [{                    value: 0,                    width: 1,                    color: '#808080'                }]            },            tooltip: {                formatter: function () {                    return '<b>' + this.series.name + '</b><br/>' +                        Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' +                        Highcharts.numberFormat(this.y, 2);                }            },            legend: {                enabled: false            },            exporting: {                enabled: false            },            series: [{                name: 'Random data',                data: (function () {                    // generate an array of random data                    var data = [],                        time = (new Date()).getTime(),                        i;                    for (i = -19; i <= 0; i += 1) {                        data.push({                            x: time + i * 1000,                            y: 1                        });                    }                    return data;                }())            }]        });    });});        </script>    </head>    <body><script src="./Highcharts-4.1.9/js/highcharts.js"></script><script src="./Highcharts-4.1.9/js/modules/exporting.js"></script><div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>    </body></html>
复制代码

 

 

比较过程

当没有减少节点时,访问 index.html 时,命中保持在 100%:

 

 当减少一个服务器节点时,即执行 down.php,命中率的变化(普通哈希)变化如下:

 00:46:45 时突然减少一台服务器,命中率急剧下降;

 

 

 

 

 直到 01:00:10 时恢复稳定。耗时约 13 min,稳定后的命中率在 94% - 95% 之间。

 

 

一致性哈希

修改 init.php 和 down.php:

$hash = new consistentHash();

 

首先执行 init.php,然后当减少一个服务器节点时(执行 down.php),一致性哈希命中率的变化变化如下:

 

 

 

 01:32:39 模拟宕调一台服务器

 

 

 

 

 

到 01:45:55 恢复稳定。耗时约 13 min,稳定后的命中率为 95.03%。

 

0 0