作为程序员一定要保持良好的睡眠,才能好编程

memcache分布式算法求余以及hash算法说明

发布时间:2016-10-31

memcache没有提供分布式,分布式需要自己去写。

这个算法有很多种,今天我们简单来介绍两种算法:


php操作memcache的API:http://php.net/manual/zh/memcache.getserverstatus.php


既然是分布式,那么必须就需要多台服务器的。 多台服务器分两种:

一、一台服务器上架设多个端口的memcache,IP相同端口不同

二、多台服务器上架设多个服务器,IP不同端口相同


使用 addServer("IP","PORT");


假设:192.168.1.80 上有五台服务器  11211、11212、11213、11214、11215

使用memcache进行连接,并循环1000次 存放在这五台服务器上,

1、看看都是在哪台服务器上的?

2、依次取出,看一看


求余法


1、注入变量:

     header("Content-type:text/html;charset=utf-8");
	$memcache=new Memcache();
	if(!$memcache){
		die("PHP不支持memcache");
	}
	$memcache->addServer("192.168.1.80",11211);
	$memcache->addServer("192.168.1.80",11212);
	$memcache->addServer("192.168.1.80",11213);
	$memcache->addServer("192.168.1.80",11214);
	$memcache->addServer("192.168.1.80",11215);
	$_keys=[];
	$_str="1234567890abcdefghklmgopqrstuvwxyzABCDEFGHKLMGOPQRSTUVWXYZ";
	
	for($i=1;$i<=1000;$i++){
		$k="k".$i;
		$v=substr(str_shuffle($_str),0,6);
		$_keys[$k]=$v;	
		$res=$memcache->set($k,$v,0,1000);		
		if($res){
			echo "插入成功:$k=$v <br/>";
		}
	}
	echo file_put_contents("keys.txt",serialize($_keys));
	
	//echo $memcache->set("company","lyld",0,1000);
	//echo $memcache->get("company");


向memcache中注入1000个变量,


2、取出变量,并和数组中的比对:

    header("Content-type:text/html;charset=utf-8");
	$memcache=new Memcache();
	if(!$memcache){
		die("PHP不支持memcache");
	}
	$memcache->addServer("192.168.1.80",11211);
	$memcache->addServer("192.168.1.80",11212);
	$memcache->addServer("192.168.1.80",11213);
	$memcache->addServer("192.168.1.80",11214);
	$memcache->addServer("192.168.1.80",11215);
	$keys=unserialize(file_get_contents("keys.txt"));
	$err_count=0;
	foreach($keys as $k=>$v){
		$mem_val=$memcache->get($k);
		if($mem_val==$v){
			$r="true";
		}else{
			$r="false";
			$err_count++;
		}
		echo "查询成功:$k=".$mem_val." 数组:$v  对比结果:".$r."<br>";
	}
	
	//输入结果:
	echo "共计错误:".$err_count;


如果服务器正常运行,没有出错率。也就是共计错误是 0


如果哪天一台服务器挂了,会造成一定数量变量的丢失:


挂机的顺序不同,挂机丢失的变量也不一样:比如说 1台服务器挂了


第5台:丢失变量172个

第4台:丢失变量231个

第3台:丢失变量179个

第2台:丢失变量133个

第1台:丢失变量285个


这个变量随机存放的,不知道存放在哪台服务器上,memcache可以通过get 方法得到。

不用理会存在哪个服务器上。


如果丢失两台服务器,它的随时率也会随之更高。

比如说丢了第四台和第三台:


共计丢失:410个


丢失.jpg

注释掉以后,和 kill -9 进程号 是同样的结果,都是都是410个变量。


4.jpg

5.jpg



以上方法是通过memcache自带的方法计算出来的数据。



下面通过写代码的方式进行测试:


$first=0;    
    $second=0;
    $three=0;
    $fourth=0;
    $fiveth=0;
    $_str="1234567890abcdefghklmgopqrstuvwxyzABCDEFGHKLMGOPQRSTUVWXYZ";
    for($i=1;$i<=1000;$i++){
    $k="k".$i;
    //$num=sprintf("%u",crc32($k))%5;
    $num=$mem->getk($k)%5;
    echo $num;
    switch($num){
        case 0:
        $first++;
        break;
        case 1:
        $second++;
        break;
        case 2:
        $three++;
        break;
        case 3:
        $fourth++;
        break;
        case 4:
        $fiveth++;
        break;
    }
        echo " <br/>";
    }
    echo "第一台服务器要存储:".$first;echo " <br/>";
    echo "第二台服务器要存储:".$second;echo " <br/>";
    echo "第三台服务器要存储:".$three;echo " <br/>";
    echo "第四台服务器要存储:".$fourth;echo " <br/>";
    echo "第五台服务器要存储:".$fiveth;echo " <br/>";


6.jpg


$k=substr(str_shuffle($_str),0,6).$i;

如果把key改成随机的,那么再来看看结果分配:

7.jpg


$k=substr(str_shuffle($_str),0,strlen($i)).$i;

8.jpg

$k=substr(str_shuffle($_str),0,strlen($i)*2).$i;

9.jpg


这就是服务器分配资源的个数:



现在看看自己写的类:

Memcache.class.php


 class Mem{
		public $_server;
				
		public function addServer($ip,$port=11211){
			$mem=new Memcache();
			$flag=$mem->connect($ip,$port);
			if($flag){
				//$this->_server[md5($ip.$port)]=$mem;
				$this->_server[]=$mem;
			}			
		}
		
		public function getk($key){
			$num=0;
			for($i=0;$i<strlen($key);$i++){
				$num+=ord($key[$i]);
			}
			return $num;
		}	
		
		public function clearData(){			
			foreach($this->_server as $mem){
				$mem->flush();//清空所有的数据
			}
		}
		
		public function getServer($key){			
			$index=$this->getk($key)%sizeof($this->_server);
			return $this->_server[$index];
		}
		
		public function getServer_num(){
			return sizeof($this->_server);
		}
		
		public function readServer(){
			return $this->_server;
		}
	}



mem.php

        
        header("Content-type:text/html;charset=utf-8");
	include "Memcache.class.php";
	
	
	$mem=new Mem();
	$mem->addServer("192.168.1.80",11211);
	$mem->addServer("192.168.1.80",11212);
	$mem->addServer("192.168.1.80",11213);
	$mem->addServer("192.168.1.80",11214);
	$mem->addServer("192.168.1.80",11215);
	
	
	$first=0;
	$second=0;
	$three=0;
	$fourth=0;
	$fiveth=0;
	$_str="1234567890abcdefghklmgopqrstuvwxyzABCDEFGHKLMGOPQRSTUVWXYZ";
	$_keys=array();
	for($i=1;$i<=1000;$i++){
		$k=substr(str_shuffle($_str),0,strlen($i)*2).$i;		 
		//$num=sprintf("%u",crc32($k))%5;
		$num=$mem->getk($k)%$mem->getServer_num();		 
		switch($num){
			case 0:
			$first++;
			break;
			case 1:
			$second++;
			break;
			case 2:
			$three++;
			break;
			case 3:
			$fourth++;
			break;
			case 4:
			$fiveth++;
			break;
		}
		echo " <br/>";
		$v=substr(str_shuffle($_str),0,6);
		echo "插入成功:$k=$v <br/>";
		$_keys[$k]=$v;	
		//设置放进memcache中	
		$mem->getServer($k)->set($k,$v,0,1000);
	}
	echo file_put_contents("keys_mem_my.txt",serialize($_keys));
	echo " <br/>";
	
	echo "第一台服务器要存储:".$first;echo " <br/>";
	echo "第二台服务器要存储:".$second;echo " <br/>";
	echo "第三台服务器要存储:".$three;echo " <br/>";
	echo "第四台服务器要存储:".$fourth;echo " <br/>";
	echo "第五台服务器要存储:".$fiveth;echo " <br/>";

10.jpg


readmem.php

	header("Content-type:text/html;charset=utf-8");
	include "Memcache.class.php";
	$mem=new Mem();
	$mem->addServer("192.168.1.80",11211);
	$mem->addServer("192.168.1.80",11212);
	$mem->addServer("192.168.1.80",11213);
	$mem->addServer("192.168.1.80",11214);
	$mem->addServer("192.168.1.80",11215);	
	 
	$keys=unserialize(file_get_contents("keys_mem_my.txt"));
	$err_count=0;
	foreach($keys as $k=>$v){
		$mem_val=$mem->getServer($k)->get($k);
		if($mem_val==$v){
			$r="true";
		}else{
			$r="false";
			$err_count++;
		}
		echo "查询成功:$k=".$mem_val." 数组:$v  对比结果:".$r."<br>";
	}
	
	//输入结果:
	echo "共计错误:".$err_count;


11.jpg

好,现在刚刚添加好,也查询了,没有任何问题。



比如:11215 这台服务器崩盘了,再看看效果:

是不是预期的丢失了196个变量?


12.jpg

错了798个?

   

答案:不是  丢失了798个。


如果丢失一台服务器,命中率会在 80.4% ,正确的,

但是实际算出只有:20.2%是正确的, 天壤之别啊。疯了吧

这就是求余算法的弊端。

不建议使用




求余比例出现了问题:

public function getServer($key){			
        $index=$this->getk($key)%sizeof($this->_server);
        return $this->_server[$index];
 }


这句代码的写法,是根据自动添加服务器的数量来自动分配,比如按照 5台服务器算:


12%5=2 在第二台服务器

14%5=4 在第四台服务器


现在比如最后一台服务器崩盘了:就剩下4台服务器了


12%4=0 在第0台服务器上

14%4=2 在第二台服务器上


这样算错的比例大大提升。只有是 4*5=20 20的公倍数才能正确读取出数据。


这就是为什么198 到 798 的差距了。


具体实施代码包:


memcache.rar



hash算法

    header("Content-type:text/html;charset=utf-8");
	
	$hash=new Hash();
	
	$hash->addPos("a");
	$hash->addPos("b");
	$hash->addPos("c");
	

	
	echo "Hash数:".$hash->_hash("user");
	echo "<br>";
	echo "找到服务器:".$hash->getPos("user");
	echo "<br>";
	$hash->printPos();
	
	class Hash{
		protected $_position;
		//分成的接点
		protected $_mul=64;
			
		public function __construct(){
			 
		}
		
		//把传入的key按照一定的规律转换成数字
		public function _hash($key){
			return sprintf("%u",crc32($key));
		}
		
		//测试 使用
		public function printPos(){
			print_r($this->_position);
		}
		
		//根据key得到 node
		public function getPos($key){
			$_hash=$this->_hash($key);
			//初始化 默认得到第一个,如果数大,foreach循环中没有得到数据
			//就得到第一个
			$res=current($this->_position);
			foreach($this->_position as $key=>$val){
				if($_hash<$key){
					$res=$val;
					break;
				}
			}
			return $res;
		}
		
		//添加接点
		public function addPos($node){		
			for($i=1;$i<=$this->_mul;$i++){
				$this->_position[$this->_hash($node."-".$i)]=$node;
			}
			$this->sortPosition();
		}
		
		//添加的接点按照
		public function sortPosition(){
			ksort($this->_position,SORT_REGULAR);
		}
		
		
	}



13.jpg


有了这个信息,我们就能进行延伸,

比如:

现在共计有1000个key ,需要均匀的分布在每一个memcache中,就需要通过 Hash 找到key对应的服务器,

然后进行连接,并将这个key加入到memcache中


下面我们来看看代码如何实现:


hash1.jpg


看结果 b 号服务器需要插入 194个变量,但只有119???

这是程序上的bug 为什么,因为Hash.class.php  要 connect("")  连接问题。



Hash.class.php

    
    class Hash{
        protected $_position;
        //分成的接点
        protected $_mul=64;
        protected $_ser=array();
            
        public function __construct(){
             
        }        
        //把传入的key按照一定的规律转换成数字
        public function _hash($key){
            return sprintf("%u",crc32($key));
        }
        
        public function getMem($memcache,$memInfo){
            $memcache->connect($memInfo['ip'],$memInfo['port'],2);
            return $memcache;
        }
        
        //测试 使用
        public function printPos(){
            print_r($this->_position);
        }
        
        //获取sever
        public function getServer($key){
            $_hash=$this->_hash($key);
            //初始化 默认得到第一个,如果数大,foreach循环中没有得到数据
            //就得到第一个
            $res=current($this->_position);
            foreach($this->_position as $key=>$val){
                if($_hash<$key){
                    $res=$val;
                    break;
                }
            }//array_merge(,array("servername"=>$val));
            return $this->_ser[$val];
        }
        
        //根据key得到 node
        public function getPos($key){
            $_hash=$this->_hash($key);
            //初始化 默认得到第一个,如果数大,foreach循环中没有得到数据
            //就得到第一个
            $res=current($this->_position);
            foreach($this->_position as $key=>$val){
                if($_hash<$key){
                    $res=$val;
                    break;
                }
            }//array_merge(,array("servername"=>$val));
            return $val;
            
        }
        
        //添加接点
        public function addPos($node,$_ser){                    
            for($i=1;$i<=$this->_mul;$i++){
                $this->_position[$this->_hash($node."-".$i)]=$node;
            }
            $mem=new Memcache();
            $flag=$mem->pconnect($_ser['ip'],$_ser['port']);
            if($flag){
                //$this->_server[md5($ip.$port)]=$mem;
                $this->_ser[$node]=$mem;                     
            }            
            $this->sortPosition();
            
        }
        
        //添加的接点按照
        public function sortPosition(){
            ksort($this->_position,SORT_REGULAR);
        }
        
        
    }



hash.php

        
       header("Content-type:text/html;charset=utf-8");
    include("Hash.class.php");
    $hash=new Hash();
    
    $hash->addPos("a",array("ip"=>"192.168.1.80","port"=>11211));
    $hash->addPos("b",array("ip"=>"192.168.1.80","port"=>11212));
    $hash->addPos("c",array("ip"=>"192.168.1.80","port"=>11213));
    $hash->addPos("d",array("ip"=>"192.168.1.80","port"=>11214));
    $hash->addPos("e",array("ip"=>"192.168.1.80","port"=>11215));
    
     
    
    /**echo "Hash数:".$hash->_hash("user");
    echo "<br>";
    echo "找到服务器:";
    
    
     
    echo "<br>";
    $hash->printPos();
    $_serInfo=$hash->getPos("user");
    print_r($_serInfo);
    
    exit;**/
    
     
    
    
    $first=0;
    $second=0;
    $three=0;
    $fourth=0;
    $fiveth=0;
    $_str="1234567890abcdefghklmgopqrstuvwxyzABCDEFGHKLMGOPQRSTUVWXYZ";
    $_keys=array();
    for($i=1;$i<=1000;$i++){
        $k=substr(str_shuffle($_str),0,strlen($i)*2).$i;         
        //$num=sprintf("%u",crc32($k))%5;
        //$num=$mem->getk($k)%$mem->getServer_num();    
        $serverName=$hash->getPos($k);
        switch($serverName){
            case "a":
            $first++;
            break;
            case "b":
            $second++;
            break;
            case "c":
            $three++;
            break;
            case "d":
            $fourth++;
            break;
            case "e":
            $fiveth++;
            break;
        }
        echo " <br/>";
        $v=substr(str_shuffle($_str),0,6);
        echo "插入成功:$k=$v <br/>";
        $_keys[$k]=$v;    
        //设置放进memcache中
        echo $hash->getServer($k)->set($k,$v,0,0)."<br>";
        usleep(3000);
    }
    echo file_put_contents("keys_mem_my.txt",serialize($_keys));
    echo " <br/>";    
    echo "第a台服务器要存储:".$first;echo " <br/>";
    echo "第b台服务器要存储:".$second;echo " <br/>";
    echo "第c台服务器要存储:".$three;echo " <br/>";
    echo "第d台服务器要存储:".$fourth;echo " <br/>";
    echo "第e台服务器要存储:".$fiveth;echo " <br/>";

hash3.jpg



readhash.php

       
       header("Content-type:text/html;charset=utf-8");
    include("Hash.class.php");
    $hash=new Hash();
    
    $hash->addPos("a",array("ip"=>"192.168.1.80","port"=>11211));
    $hash->addPos("b",array("ip"=>"192.168.1.80","port"=>11212));
    $hash->addPos("c",array("ip"=>"192.168.1.80","port"=>11213));
    $hash->addPos("d",array("ip"=>"192.168.1.80","port"=>11214));
    $hash->addPos("e",array("ip"=>"192.168.1.80","port"=>11215));
    $keys=unserialize(file_get_contents("keys_mem_my.txt"));
    $err_count=0;
    foreach($keys as $k=>$v){
        $mem_val=$hash->getServer($k)->get($k);
        if($mem_val==$v){
            $r="true";
        }else{
            $r="false";
            $err_count++;
        }
        echo "查询成功:$k=".$mem_val." 数组:$v  对比结果:".$r."<br>";
    }
    //输入结果:
    echo "共计错误:".$err_count;





hash2.jpg


 

通过上面的实验,已经说明,一致性hash 的容错率要比 求余数 算法高很多,请一定要切记。



hash 算法:通过crc32 来得到字符串的值,然后进行数字的判断。

每一个节点 划分成 64份,划分的数量越多,则分布的均匀。