第六章 memcached剖析

注:本篇博客參考於兩本書。

  • 《memcached全面剖析》,該書籍市面上應該沒有,我傳到了百度雲盤,鏈接如下:http://pan.baidu.com/s/1qX00Lti
  • 《大型網站技術架構:核心原理與案例分析》

前提:

  • 本文是基於memcached1.4版本的,之前的版本與該版本在一些地方是不一樣的(eg.《memcached全面剖析》的memcached1.2的內存管理方式就與1.4不同)
  • 在看本文之前,最好先看一下memcached在實際開發中怎麼進行操作的,鏈接《第八章 企業項目開發--分佈式緩存memcached

1、memcached特徵

  • 協議簡單(文本協議、二進制協議)
  • 基於libevent的事件處理,libevent封裝了Linux的epoll模型的時間處理功能。
  • slab存儲模型
  • 集羣中服務器間互不通信(在大集羣的情況下,其性能遠超其他同步更新緩存的緩存器,當然小集羣下,memcached的性能也十分優秀)

 

2、memcached訪問模型

說明:

Xmemcached的具體使用代碼查看"Java企業項目開發實踐"系列博客的《第八章 企業項目開發--分佈式緩存memcached》,下面的解釋會依據該代碼進行。

在上圖中,memcached客戶端假設使用XMemcached

  • 服務器列表:在根pom.xml文件中進行了配置
  • 路由算法有兩種:(可以在程序中指定)
    • 一致性hash算法(推薦)
    • 簡單求餘法
  • 通信模塊:
    • 通信協議:TCP協議
    • 序列化協議:二進制協議(推薦)、文本協議
  • Memcached API(緩存的增刪改查):在程序中編寫

整個流程:

應用程序(AdminService)調用Memcached API(假設爲add操作),向memcached服務器添加緩存,這時候,程序會首先根據配置的路由算法(假設是一致性hash算法)在服務器列表中選出一臺服務器(假設是node1),之後該API通過序列化協議序列化對象(當然,這個是可無的,eg.value是一個String),並通過TCP協議將將要存儲的key-value對存入相應的服務器。在get時,只要採用的是與add時相同的hash算法,就會選中add時的那一臺服務器。

看完這一段,流程明白了。但是有幾點疑問:

  • 兩種路由算法是怎樣實現的?爲什麼使用一致性hash算法
  • 緩存到達服務器的時候究竟怎麼存儲?(slab內存模型)
  • 當緩存超過一定的容量後,緩存的自動刪除是採用什麼策略,怎樣刪除的?(LRU)
  • 兩種序列化協議有什麼優缺點?

 

3、hash算法

3.1、簡單求餘法

原理步驟:求得key的整數hash值(對於Java對象而言,直接使用其hashCode()方法就好),再除以服務器臺數,獲取餘數,根據該餘數選擇服務器。

注意:如果選擇的服務器無法連接時,會進行rehash,即:將連接次數添加到鍵中,重新計算hash值後,再重新連接。當然可以禁止rehash。

優點:

  • 簡單
  • hash分散性好(因爲hashCode()的值具有隨機性)

缺點:

  • 添加或刪除服務器的時候,緩存的獲取就會出問題了(因爲服務器臺數變了,求餘的時候分母變了,餘數也就可能變了),假設在99臺memcached服務器中又新添加而一臺,則緩存的不命中率是99%,即n/(n+1),n表示原有的服務器。

注意:

  • 在XMemcached中仍保留了該算法
  • 適用於不需要考慮集羣伸縮性的時候(即機器總數不變)

3.2、一致性hash算法

對於絕大部分系統,集羣的伸縮性是五個非功能需求中比較重要的一個,也就是說必須克服"簡單求餘法"的缺點。

 

  • 原理:先構造一個長度爲0~232的整數環(使用二叉樹構造),根據節點(memcached服務器)名稱的hash值將緩存服務器節點放置在這個hash環上,然後根據需要緩存的數據的key來計算其hash值,然後在hash環上順時針查找距離這個key的hash值最近的緩存服務器節點,完成key到服務器的hash映射查找。
    • 如果超過232還找不到,則存在第一臺memcached上(依舊是順時針)
  • 存在的問題:當服務器數量比較少的情況下,有可能造成負載不均衡的情況,爲了防止這種情況的發生,使用將物理服務器先虛擬化成多臺虛擬服務器,然後將這些虛擬服務器的hash值放在環上,當客戶端路由到某臺虛擬服務器上時,找到該虛擬服務器所對應的物理服務器即可。
    • 一般而言,一臺物理服務器虛擬化爲150臺虛擬服務器最合適,太少會造成負載不均,太多會影響性能
  • Memcached採用這樣的算法,在我們新加入服務器或集羣中的某臺服務器宕機時,都不會有太大的影響,只會影響一小段(見下圖),確保了集羣的可用性與伸縮性

注意:

  • hash環是一個二叉樹,最後邊葉子與最左邊相連成環
  • 整個緩存的查找過程就是找一個剛剛大於等於查找數的最小值

疑問:(這一點沒查到資料)

  • 服務器的hash算法是怎樣的
  • 計算緩存key的hash算法是否要與服務器的一致,還能不能使用原來的hashCode()

思路:hash算法實際上就是"先將字符串轉化爲整數,然後再將該整數放到相應的服務器上或環上",對於key不用講,我們可以用crc32將字符串的key轉化爲整數,之後放在0~232的環上的一點,對於服務器我們可以採用將"ip:port"這個字符串使用crc32轉化爲整數,之後放在環上(當然這裏我們需要將一個實例"ip:port"虛擬化成一堆虛擬節點,每臺虛擬節點可以使用"ip:port-i"作爲節點名稱,其中i是>0的整數,將每臺虛擬節點的名稱採用crc32算法算出整數放到環上)。

4、slab內存模型

4.1、爲什麼使用slab內存模型?

  在最一開始的內存分配與回收是通過malloc和free來處理的,該方式會產生內存碎片,加重內存管理器的負擔,嚴重緩存操作影響效率。

slab模型的出現就是爲了:

  • 提高緩存操作效率
  • 完全的解決內存碎片問題。

注意:

  • 第一個目的:已經實現了(因爲直接定位合適的chunk會很快)
  • 第二個目的:採用slab機制依舊會產生內存碎片,或者說成是內存浪費

4.2、slab模型原理

 

說明:該圖摘自一篇博客(圖中有標記,但是看不清),但是是很久以前摘的了,忘記了。以後找到了,我會標明出處的。

memcached的內存分配就是下面這一句話:採用分組管理預分配方式。

4.2.1、分組管理

  • 分組方式:Memcached將內存空間分爲一組slab,每個slab的大小固定爲1M,每個slab裏又包含一組chunk,同一個slab裏的每個chunk大小相同。根據這些slab中的chunk的大小,將這些slab編號slab class(也就是上圖中的Classes i)。
  • 存儲原理:當來一個要存儲的key-value對時,我們查看這個數據的大小,選擇最適合的slab class中的空閒chunk放置該對象。
    • 最合適的chunk:即該chunk的大小剛剛大於等於所存儲數據的大小,而比該chunk小一級的大小剛剛比所要存儲的數據小。

以上這種方式會造成內存大量浪費(我認爲這也是內存碎片)。

  • 減少內存浪費的方式:預估自己的緩存數據的大小,然後在啓動Memcached時合理的指定參數-f(增長因子)和-n(chunk最小尺寸)來劃分內存大小,根據公式chunk size = 80*f*(n-1)將內存分配爲若干個slab class。

疑問:上邊這個若干到底是多少?

我們可以根據f,n,以及一個slab最大爲1M來確定。(例子,我不舉了,自己想想)

4.2.2、預分配

  在啓動Memcached時通過-m參數爲Memcached分配可用內存(假設-m 1024,即分配了1G內存),但是啓動的時候不會把這些內存一次全部分配出去,而是默認先分配若干個slab class(數量取決於-f與-n參數),當其中的一個slab class被用完之後,Memcached就會再次申請1M空間,產生一個該slab class。這一塊兒結合緩存刪除機制中的LRU算法來看。(這一塊如果有誤,請大神幫忙指出來)

 

5、緩存刪除機制

  • memcached不會釋放已分配的內存,記錄超時後,其存儲空間即可重複使用
  • memcached內部不會監視緩存是否過期(即memcached不會在過期監視上耗費CPU時間),在get時查看緩存的時間戳,檢查緩存是否過期
  • memcached會優先使用已超時的緩存的空間,但是當所有空間都沒有超時,所有內存都已經分配完了,就刪除最近最少使用(LRU)的緩存,將其空間分配給新緩存(注意,假設防止一個100k的數據,而最合適的chunk是112k,假設最合適的chunk全部用完了,這時候就取剩下的內存分配112k chunk的slab,若是剩下的內存頁分配完了,不會使用剛剛大於112k的144k chunk,而是會採用LRU算法刪除最近最少使用的元素,其實這樣的話,就會有一個可能,就是原本112k中的數據還未過期,就有可能被踢出去了,這就是"老數據被踢現象")

注意:第三條與內存分配部分的預分配結合來看。

LRU算法原理:

當某個單元被請求時,維護一個計數器,通過計數器來判斷最近最少被使用的元素被踢出去。 

 

6、兩種序列化協議

  • 文本協議:
    • XML、JSON
    • key的長度爲256字節
  • 二進制協議:相較於文本協議
    • jdk序列化機制、protobuf
    • 不需要文本協議的解析處理,速度更快
    • 具有更長的key,理論上最大可使用65536字節長度的key
    • 出現在1.4,推薦使用

注意:對於以上兩種協議,自己選擇吧。

  • 二進制協議+JDK的序列化機制,那麼由於JDK自己的序列化機制低效,所以在速度上未必會比使用了fastjson的文本協議更快
  • 二進制協議+protobuf,速度很快,但是使用起來不太方便
  • 文本協議+fastjson

 

7、部分API

  • add:僅當存儲空間中不存在相同key的數據時才保存
  • replace:替換。即僅當存儲空間中存在相同的數據時才保存
  • set:add+replace。即無論何時都保存
  • delete(key, '阻塞時間(秒)')
  • 增1、減1操作,做計數器
  • get_multi(key1, key2):一次性非同步的同時(即併發的)獲取多個鍵,比循環調用getKIA數十倍

 

注意點:

  • 對於memcached的監視:可以採用"nagios"

 

原文地址:https://www.cnblogs.com/java-zhao/p/5158034.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章