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个
注释掉以后,和 kill -9 进程号 是同样的结果,都是都是410个变量。
以上方法是通过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/>";
$k=substr(str_shuffle($_str),0,6).$i;
如果把key改成随机的,那么再来看看结果分配:
$k=substr(str_shuffle($_str),0,strlen($i)).$i;
$k=substr(str_shuffle($_str),0,strlen($i)*2).$i;
这就是服务器分配资源的个数:
现在看看自己写的类:
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/>";
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;
好,现在刚刚添加好,也查询了,没有任何问题。
比如:11215 这台服务器崩盘了,再看看效果:
是不是预期的丢失了196个变量?
错了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 的差距了。
具体实施代码包:
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); } }
有了这个信息,我们就能进行延伸,
比如:
现在共计有1000个key ,需要均匀的分布在每一个memcache中,就需要通过 Hash 找到key对应的服务器,
然后进行连接,并将这个key加入到memcache中
下面我们来看看代码如何实现:
看结果 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/>";
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;
通过上面的实验,已经说明,一致性hash 的容错率要比 求余数 算法高很多,请一定要切记。
hash 算法:通过crc32 来得到字符串的值,然后进行数字的判断。
每一个节点 划分成 64份,划分的数量越多,则分布的均匀。