memcached雖然稱爲「分佈式」緩存服務器,但服務器端並無「分佈式」功能。Memcache集羣主機不可以相互通訊傳輸數據,它的「分佈式」是基於客戶端的程序邏輯算法進一步實現的。php
請看下面簡圖:html
根據上圖咱們簡述分析分佈式memcached的set與get的過程node
set過程:算法
一、首先經過應用程序set(‘key’,’value’)數據庫
二、進入程序,使用key經過邏輯算法得出這個key須要存儲的節點位置緩存
三、根據節點位置鏈接相應的memcached服務器,併發送set命令服務器
get過程:數據結構
一、首先經過應用程序get(‘key’)併發
二、接着使用該key經過邏輯算法獲取該key的存儲節點分佈式
三、根據節點鏈接相應的memcached服務器,併發送get命令
實現memcached有不少種方式,其中最經常使用的就是一致哈希思想的分佈式(就簡稱爲一致哈希分佈式啦)。好的東西固然須要次劣品來陪襯它的優勢啦,所以在這裏除了講解一致哈希分佈式,還會講到取模分佈式。從而進一步分析他們的優缺點。
這裏的例子都會採用PHP代碼實現,固然啦,最重要的是思想與方法嘛!畢竟這兩樣東西在任何語言中都是相通的。
何爲取模算法方式分佈式?就是將key轉換爲32位的數字,並與memcached服務器的總數進行相除取得餘數。而這個餘數就是memcached服務器的節點node。有了這個node咱們就能夠肯定memcached服務器,就能夠發送命令給memcached執行了。
圖示解析:
整個過程上圖所示。
1)、PHP代碼實現
GetModMemcache.class.php
1 <?php 2 #分佈式memcache(取模計算) 3 class GetModMemcache 4 { 5 private $total=''; #存儲memcache服務器的總數 6 private $servers=array(); #存儲memcache服務器的具體信息 7 /** 8 * @desc 構造函數 9 * 10 * @param $serversArr array | memcache服務器具體信息 11 */ 12 public function __construct($serversArr) 13 { 14 $this->total=count($serversArr); 15 $this->servers=$serversArr; 16 } 17 18 /** 19 * @desc 計算$key的存儲位置(即哪一個服務器) 20 * 21 * @param string | key字符串 22 * 23 * @return int 返回第幾個服務器 24 */ 25 protected function position($key) 26 { 27 #使用crc32(),將字符串轉化爲32爲的數字 28 return sprintf('%u',crc32($key))%$this->total; #取餘 29 } 30 31 /** 32 * @desc 獲取memcached對象 33 * 34 * @param $position int | key的位置信息 35 * 36 * @return object 返回實例化memcached對象 37 */ 38 protected function getMemcached($position) 39 { 40 $host=$this->servers[$position]['host']; #服務器池中某臺服務器host 41 $port=$this->servers[$position]['port']; #服務器池中某臺服務器port 42 $m= new memcached(); 43 $m->addserver($host, $port); 44 return $m; 45 } 46 47 /** 48 * @desc 設置key-value值 49 * 50 * @param string | key字符串 51 * @param mixed | 值能夠是任何有效的非資源型php類型 52 * 53 * @return 返回結果 54 */ 55 public function setKey($key, $value) 56 { 57 $num=$this->position($key); 58 echo $num; #調試用 59 $m=$this->getMemcached($num); #獲取memcached對象 60 return $m->set($key, $value); 61 } 62 63 public function getKey($key) 64 { 65 $num=$this->position($key); 66 $m=$this->getMemcached($num); 67 return $m->get($key); 68 } 69 70 71 } 72 73 74 $arr=array( 75 array('host'=>'192.168.95.11', 'port'=>'11210'), 76 array('host'=>'192.168.95.11', 'port'=>'11211'), 77 array('host'=>'192.168.95.11', 'port'=>'11212'), 78 ); 79 $mod=new GetModMemcache($arr); 80 81 /* 82 #存儲數據 83 $a=$mod->setKey('key3', 'key33333'); 84 echo "<pre>"; 85 print_r($a); 86 echo "</pre>";die; 87 */ 88 /* 89 #獲取數據 90 $b=$mod->getKey('key1'); 91 echo "<pre>"; 92 print_r($b); 93 echo "</pre>";die; 94 */ 95 ?>
2)、進行相應測試
一、連續插入三個數據
#set(‘key1’,’value11111’); #node=1
#set(‘key2’,’value22222’); #node=1
#set(‘key3’,’value33333’;) #node=0
二、分別telnet鏈接192.168.95.11:(112十、112十一、11212)
11210含有key3數據
11211含有key一、key2數據
11212不含數據
三、使用程序get數據
結果都可以將數據取出來
3)、優缺點
優勢:
一、簡單實用易理解
二、數據分佈均勻
缺點:
一、宕了一臺memcached服務器時不能自動調整羣組去處理數據,使一部分數據不能使用緩存,一直持續從數據庫中獲取數據。
二、當須要擴容的時候,增長多臺memcached服務器,那麼原來已經緩存的數據大多數都不可以被命中,即數據無用。
何爲一致哈希算法方式分佈式呢?
想象一下,將32位的全部數字從小到大按順時針分佈在一個圓環上;
其次,將每一個存儲節點賦予一個名字,並經過crc32函數將其轉換爲32位的數字,此數字就是該memcached服務器的存儲節點
接着,將key也經過crc32函數轉換爲32位的數字,它的所在位置按順時針方向走第一個遇到的存儲節點所對應的memcached服務器就是該key的最終存儲服務器。
1)、圖像解析
假設node1節點服務器掛了,根據按順時針最近原則,那麼本來存儲在node1節點的數據此時也可存儲在node3節點中。
假設有擴容的須要,增長的兩臺memcached服務器,又將會怎麼樣呢?請看下圖分析
結果顯示只有少許數據會受到影響,相對於總體數據來講這些影響仍是在可接受的範圍內。
從上面的圖示咱們能夠很容易發現存在這麼個缺點,便是使用crc32函數咱們不能控制memcached存儲節點的具體位置,而且節點的總數量相對於2的32次方是顯得多麼的眇小。倘若剛好即便這幾個存儲節點都距離的很是近呢,那麼必將有一個memcached服務器承受絕大多數的數據緩存。
請看下圖分析:
解決辦法:
將一個真實存儲節點映射爲多個虛擬存儲節點,即真實節點+後綴再經過crc32處理(例如:node1_一、node1_二、node1_三、…..、node1_n)
看下圖節點分佈:
三個真實節點在圓環上就變成了三十個存儲節點,這樣就能夠避免存儲節點相距太近而致使數據緩存分佈不均勻的問題了,並且存儲機制沒有任何變化。
2)、PHP代碼實現
ConsistentHashMemcache.class.php
1 <?php 2 #分佈式memcache 一致性哈希算法(採用環狀數據結構) 3 class ConsistentHashMemcache 4 { 5 private $virtualNode=''; #用於存儲虛擬節點個數 6 private $realNode=array(); #用於存儲真實節點 7 private $servers=array(); #用於存儲memcache服務器信息 8 #private $totalNode=array(); #節點總數 9 /** 10 * @desc 構造函數 11 * 12 * @param $servers array | memcache服務器的信息 13 * @param $virtualNode int | 虛擬節點個數,默認64個 14 */ 15 public function __construct($servers, $virtualNode=64) 16 { 17 $this->servers=$servers; 18 $this->realNode=array_keys($servers); 19 $this->virtualNode=$virtualNode; 20 } 21 22 /** 23 * @return int 返回32位的數字 24 */ 25 private function hash($str) 26 { 27 return sprintf('%u',crc32($str)); #將字符串轉換爲32位的數字 28 } 29 30 /** 31 * @desc 處理節點 32 * 33 * @param $realNode array | 真實節點 34 * @param $virturalNode int | 虛擬節點個數 35 * 36 * @return array 返回全部節點信息 37 */ 38 private function dealNode($realNode, $virtualNode) 39 { 40 $totalNode=array(); 41 foreach ($realNode as $v) 42 { 43 for($i=0; $i<$virtualNode; $i++) 44 { 45 $hashNode=$this->hash($v.'-'.$i); 46 $totalNode[$hashNode]=$v; 47 } 48 } 49 ksort($totalNode); #按照索引進行排序,升序 50 return $totalNode; 51 } 52 53 /** 54 * @desc 獲取key的真實存儲節點 55 * 56 * @param $key string | key字符串 57 * 58 * @return string 返回真實節點 59 */ 60 private function getNode($key) 61 { 62 $totalNode=$this->dealNode($this->realNode, $this->virtualNode); #獲取全部虛擬節點 63 /* #查看虛擬節點總數 64 echo "<pre>"; 65 print_r($totalNode); 66 echo "</pre>";die; 67 */ 68 $hashNode=$this->hash($key); #key的哈希節點 69 foreach ($totalNode as $k => $v) #循環總結點環查找 70 { 71 if($k >= $hashNode) #查找第一個大於key哈希節點的值 72 { 73 return $v; #返回真實節點 74 } 75 } 76 return reset($totalNode); #倘若總節點環的值都比key哈希節點小,則返回第一個總哈希環的value值 77 } 78 79 /** 80 * @desc 返回memcached對象 81 * 82 * @param $key string | key值 83 * 84 * @return object 85 */ 86 private function getMemcached($key) 87 { 88 $node=$this->getNode($key); #獲取真實節點 89 echo $key.'真實節點:'.$node.'<br/>'; #測試使用,查看key的真實節點 90 $host=$this->servers[$node]['host']; #服務器池中某臺服務器host 91 $port=$this->servers[$node]['port']; #服務器池中某臺服務器port 92 $m= new memcached(); #實例化 93 $m->addserver($host, $port); #添加memcache服務器 94 return $m; #返回memcached對象 95 } 96 97 /** 98 * @desc 設置key-value值 99 */ 100 public function setKey($key, $value) 101 { 102 $m=$this->getMemcached($key); 103 return $m->set($key, $value); 104 } 105 106 /** 107 * @desc 獲取key中的value 108 */ 109 public function getKey($key) 110 { 111 $m=$this->getMemcached($key); 112 return $m->get($key); 113 } 114 115 116 } 117 118 ?>
3)、測試
一、查看全部虛擬節點
一共64*3=132個虛擬節點(虛擬節點設置仍是屬於偏少的,通常都會設置在100~200)
二、set測試
1 include './ConsistentHashMemcache.class.php'; 2 header("content-type: text/html;charset=utf8;"); 3 $arr=array( 4 'node1'=>array('host'=>'192.168.95.11', 'port'=>'11210'), 5 'node2'=>array('host'=>'192.168.95.11', 'port'=>'11211'), 6 'node3'=>array('host'=>'192.168.95.11', 'port'=>'11212'), 7 ); 8 9 $c=new ConsistentHashMemcache($arr); 10 11 #測試set 12 $c->setKey('aaa', '11111'); 13 $c->setKey('bbb', '22222'); 14 $c->setKey('ccc', '33333');
分別telnet鏈接192.168.95.11:(112十、112十一、11212)
在節點node1中get(‘aaa’)、get(‘bbb’)能取到值
在節點node3中get(‘ccc’)能取到值
三、get測試
1 include './ConsistentHashMemcache.class.php'; 2 header("content-type: text/html;charset=utf8;"); 3 $arr=array( 4 'node1'=>array('host'=>'192.168.95.11', 'port'=>'11210'), 5 'node2'=>array('host'=>'192.168.95.11', 'port'=>'11211'), 6 'node3'=>array('host'=>'192.168.95.11', 'port'=>'11212'), 7 ); 8 9 $c=new ConsistentHashMemcache($arr); 10 #測試get 11 echo $c->getKey('aaa').'<br/>'; 12 echo $c->getKey('bbb').'<br/>'; 13 echo $c->getKey('ccc').'<br/>';
四、優缺點
相對於取模方式分佈式,一致性哈希方式分佈式的代碼複雜性要高一點,但這也在能夠接受的範圍內,不構成任何阻礙問題。相反它的優勢就很是顯著,經過虛擬節點的方式實現,可使不可控的存儲節點可以儘量的均勻分佈在圓環上,從而達到數據均勻緩存在各個主機裏。其次增長與刪除虛擬節點對於以前緩存的總體數據影響很是小。
(以上是本身的一些看法與總結,如有不足或者錯誤的地方請各位指出)
做者:那一葉隨風
聲明:以上只表明本人在工做學習中某一時間內總結的觀點或結論。轉載時請在文章頁面明顯位置給出原文連接