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份,划分的数量越多,则分布的均匀。