1、memcached分布式簡介
memcached雖然稱為“分布式”緩存服務器,但服務器端並沒有“分布式”功能。Memcache集群主機不能夠相互通信傳輸數據,它的“分布式”是基於客戶端的程序邏輯算法進一步實現的。
請看下面簡圖:
根據上圖我們簡述分析分布式memcached的set與get的過程
set過程:
1、首先通過應用程序set(‘key’,’value’)
2、進入程序,使用key通過邏輯算法得出這個key需要存儲的節點位置
3、根據節點位置連接相應的memcached服務器,並發送set命令
get過程:
1、首先通過應用程序get(‘key’)
2、接着使用該key通過邏輯算法獲取該key的存儲節點
3、根據節點連接相應的memcached服務器,並發送get命令
實現memcached有很多種方式,其中最常用的就是一致哈希思想的分布式(就簡稱為一致哈希分布式啦)。好的東西當然需要次劣品來襯托它的優點啦,因此在這里除了講解一致哈希分布式,還會講到取模分布式。從而進一步分析他們的優缺點。
這里的例子都會采用PHP代碼實現,當然啦,最重要的是思想與方法嘛!畢竟這兩樣東西在任何語言中都是相通的。
2、取模算法方式
何為取模算法方式分布式?就是將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)、進行相應測試
1、連續插入三個數據
#set(‘key1’,’value11111’); #node=1
#set(‘key2’,’value22222’); #node=1
#set(‘key3’,’value33333’;) #node=0
2、分別telnet連接192.168.95.11:(11210、11211、11212)
11210含有key3數據
11211含有key1、key2數據
11212不含數據
3、使用程序get數據
結果都能夠將數據取出來
3)、優缺點
優點:
1、簡單實用易理解
2、數據分布均勻
缺點:
1、宕了一台memcached服務器時不能自動調整群組去處理數據,使一部分數據不能使用緩存,一直持續從數據庫中獲取數據。
2、當需要擴容的時候,增加多台memcached服務器,那么原來已經緩存的數據大多數都不能夠被命中,即數據無用。
3、一致哈希算法方式
何為一致哈希算法方式分布式呢?
想象一下,將32位的所有數字從小到大按順時針分布在一個圓環上;
其次,將每個存儲節點賦予一個名字,並通過crc32函數將其轉換為32位的數字,此數字就是該memcached服務器的存儲節點
接着,將key也通過crc32函數轉換為32位的數字,它的所在位置按順時針方向走第一個遇到的存儲節點所對應的memcached服務器就是該key的最終存儲服務器。
1)、圖像解析
假設node1節點服務器掛了,根據按順時針最近原則,那么原本存儲在node1節點的數據此時也可存儲在node3節點中。
假設有擴容的需要,增加的兩台memcached服務器,又將會怎么樣呢?請看下圖分析
結果顯示只有少量數據會受到影響,相對於整體數據來說這些影響還是在可接受的范圍內。
從上面的圖示我們可以很容易發現存在這么個缺點,即是使用crc32函數我們不能控制memcached存儲節點的具體位置,並且節點的總數量相對於2的32次方是顯得多么的渺小。假若恰好即使這幾個存儲節點都距離的非常近呢,那么必將有一個memcached服務器承受絕大多數的數據緩存。
請看下圖分析:
解決辦法:
將一個真實存儲節點映射為多個虛擬存儲節點,即真實節點+后綴再通過crc32處理(例如:node1_1、node1_2、node1_3、…..、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)、測試
1、查看所有虛擬節點
一共64*3=132個虛擬節點(虛擬節點設置還是屬於偏少的,一般都會設置在100~200)
2、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:(11210、11211、11212)
在節點node1中get(‘aaa’)、get(‘bbb’)能取到值
在節點node3中get(‘ccc’)能取到值
3、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/>';
4、優缺點
相對於取模方式分布式,一致性哈希方式分布式的代碼復雜性要高一點,但這也在可以接受的范圍內,不構成任何阻礙問題。相反它的優點就非常顯著,通過虛擬節點的方式實現,可以使不可控的存儲節點能夠盡可能的均勻分布在圓環上,從而達到數據均勻緩存在各個主機里。其次增加與刪除虛擬節點對於之前緩存的整體數據影響非常小。
(以上是自己的一些見解與總結,若有不足或者錯誤的地方請各位指出)
作者:那一葉隨風
聲明:以上只代表本人在工作學習中某一時間內總結的觀點或結論。轉載時請在文章頁面明顯位置給出原文鏈接