zoukankan      html  css  js  c++  java
  • Memcached 笔记与总结(8)Memcached 的普通哈希分布算法和一致性哈希分布算法命中率对比

    准备工作

    ① 配置文件 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 }
    View Code

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

    <?php
    require './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:11212
    key2 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

    <?php
    header("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 

    <?php
    header("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

    <?php
    header("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() 输出数据格式如下

    Array
    (
        [pid] => 9616
        [uptime] => 2742
        [time] => 1448207741
        [version] => 1.4.24
        [libevent] => 2.0.22-stable
        [pointer_size] => 32
        [rusage_user] => 2.168000
        [rusage_system] => 8.439000
        [curr_connections] => 12
        [total_connections] => 13
        [connection_structures] => 13
        [reserved_fds] => 20
        [cmd_get] => 4
        [cmd_set] => 7045
        [cmd_flush] => 0
        [cmd_touch] => 0
        [get_hits] => 2
        [get_misses] => 2
        [delete_misses] => 0
        [delete_hits] => 0
        [incr_misses] => 0
        [incr_hits] => 0
        [decr_misses] => 0
        [decr_hits] => 0
        [cas_misses] => 0
        [cas_hits] => 0
        [cas_badval] => 0
        [touch_hits] => 0
        [touch_misses] => 0
        [auth_cmds] => 0
        [auth_errors] => 0
        [bytes_read] => 195866
        [bytes_written] => 89803
        [limit_maxbytes] => 4194304
        [accepting_conns] => 1
        [listen_disabled_num] => 0
        [threads] => 4
        [conn_yields] => 0
        [hash_power_level] => 16
        [hash_bytes] => 262144
        [hash_is_expanding] => 0
        [malloc_fails] => 0
        [bytes] => 62780
        [curr_items] => 1000
        [total_items] => 1000
        [expired_unfetched] => 0
        [evicted_unfetched] => 0
        [evictions] => 0
        [reclaimed] => 0
        [crawler_reclaimed] => 0
        [crawler_items_checked] => 0
        [lrutail_reflocked] => 0
    )
    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.9examplesdynamic-updateindex.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,
                         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- 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%。

     

  • 相关阅读:
    luogu P1451 求细胞数量
    P1443 马的遍历
    luogu P1194 买礼物
    codevs 4919 线段树练习4
    printf的实型
    printf的整型
    scanf
    printf
    c++常用函数
    字符类型C++(ascll码表)
  • 原文地址:https://www.cnblogs.com/dee0912/p/4873321.html
Copyright © 2011-2022 走看看