天天看點

Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)

tair 是淘寶的一個開源項目,它是一個分布式的key/value結構資料的解決方案。

作為一個分布式系統,Tair由一個中心控制節點(config server)和一系列的服務節點(data server)組成,

  • config server 負責管理所有的data server,并維護data server的狀态資訊;為了保證高可用(High Available),config server可通過hearbeat 以一主一備形式提供服務;
  • data server 對外提供各種資料服務,并以心跳的形式将自身狀況彙報給config server;所有的 data server 地位都是等價的。
Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)

tair叢集的基本概念:

  • configID,唯一辨別一個tair叢集,每個叢集都有一個對應的configID,在目前的大部分應用情況下configID是存放在diamond中的,對應了該叢集的configserver位址和groupname。業務在初始化tair client的時候需要配置此ConfigID。
  • namespace,又稱area, 是tair中配置設定給應用的一個記憶體或者持久化存儲區域, 可以認為應用的資料存在自己的namespace中。 同一叢集(同一個configID)中namespace是唯一的。通過引入namespace,我們可以支援不同的應用在同叢集中使用相同的key來存放資料,也就是key相同,但内容不會沖突。一個namespace下是如果存放相同的key,那麼内容會受到影響,在簡單K/V形式下會被覆寫,rdb等帶有資料結構的存儲引擎内容會根據不同的接口發生不同的變化。
  • quota配額,對應了每個namespace儲存區的大小限制,超過配額後資料将面臨最近最少使用(LRU)的淘汰。持久化引擎(ldb)本身沒有配額,ldb由于自帶了mdb cache,是以也可以設定cache的配額。超過配額後,在内置的mdb内部進行淘汰。
  • expireTime,資料的過期時間。當超過過期時間之後,資料将對應用不可見,不同的存儲引擎有不同的政策清理掉過期的資料。

存儲引擎

tair 分為持久化和非持久化兩種使用方式:

  • 非持久化的 tair 可以看成是一個分布式緩存;
  • 持久化的 tair 将資料存放于磁盤中,為了解決磁盤損壞導緻資料丢失,tair 可以配置資料的備份數目。tair 自動将一份資料的不同備份放到不同的主機上,當有主機發生異常,無法正常提供服務的時候,其餘的備份會繼續提供服務。

Tair的存儲引擎有一個抽象層,隻要滿足存儲引擎需要的接口,便可以很友善的替換Tair底層的存儲引擎。比如你可以很友善的将bdb、tc、redis、leveldb甚至MySQL作為Tair的存儲引擎,而同時使用Tair的分布方式、同步等特性。

Tair主要有下面三種存儲引擎:

  • mdb,定位于cache緩存,類似于memcache。支援k/v存取和prefix操作;
  • rdb,定位于cache緩存,采用了redis的記憶體存儲結構。支援k/v,list,hash,set,sortedset等資料結構;
  • ldb,定位于高性能存儲,采用了levelDB作為引擎,并可選擇内嵌mdb cache加速,這種情況下cache與持久化存儲的資料一緻性由tair進行維護。支援k/v,prefix等資料結構。今後将支援list,hash,set,sortedset等redis支援的資料結構。
Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)

MDB流程

Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)

RDB流程

Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)
Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)

LDB流程

Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)
Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)

fastdump

大資料量導入:資料預排序,按桶分memtable。

Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)

分布式政策

tair 的分布采用的是一緻性雜湊演算法,對于所有的key,分到Q個桶中,桶是負載均衡和資料遷移的基本機關。config server 根據一定的政策把每個桶指派到不同的data server上,因為資料按照key做hash算法,是以可以認為每個桶中的資料基本是平衡的,保證了桶分布的均衡性, 就保證了資料分布的均衡性。

具體說,首先計算Hash(key),得到key所對應的bucket,然後再去config server查找該bucket對應的data server,再與相應的data server進行通信。也就是說,config server維護了一張由bucket映射到data server的對照表,比如:

Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)
Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)
bucket   data server
0    192.168.10.1
1    192.168.10.2
2    192.168.10.1
3    192.168.10.2
4    192.168.10.1
5    192.168.10.2      
Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)
Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)

這裡共6個bucket,由兩台機器負責,每台機器負責3個bucket。用戶端将key hash後,對6取模,找到負責的資料節點,然後和其直接通信。表的大小(行數)通常會遠大于叢集的節點數,這和consistent hash中的虛拟節點很相似。

假設我們加入了一台新的機器——192.168.10.3,Tair會自動調整對照表,将部分bucket交由新的節點負責,比如新的表很可能類似下表:

0    192.168.10.1
1    192.168.10.2
2    192.168.10.1
3    192.168.10.2
4    192.168.10.3
5    192.168.10.3      

在老的表中,每個節點負責3個桶,當擴容後,每個節點将負責2個桶,資料被均衡的分布到所有節點上。

如果有多個備份,那麼對照表将包含多列,比如備份是為3,則表有4列,後面的3列都是資料存儲的節點。

為了增強資料的安全性,Tair支援配置資料的備份數(COPY_COUNT)。比如你可以配置備份數為3,則每個bucket都會寫在不同的3台機器上。當資料寫入一個節點(通常我們稱其為主節點)後,主節點會根據對照表自動将資料寫入到其他備份節點,整個過程對使用者是透明的。

當有新節點加入或者有節點不可用時,config server會根據目前可用的節點,重新build一張對照表。資料節點同步到新的對照表時,會自動将在新表中不由自己負責的資料遷移到新的目标節點。遷移完成後,用戶端可以從config server同步到新的對照表,完成擴容或者容災過程。整個過程對使用者是透明的,服務不中斷。

為了更進一步的提高資料的安全性,Tair的config server在build對照表的時候,可以配置考慮機房和機架資訊。比如你配置備份數為3,叢集的節點分布在兩個不同的機房A和B,則Tair會確定每個機房至少有一份資料。當A機房包含兩份資料時,Tair會確定這兩份資料會分布在不同機架的節點上。這可以防止整個機房發生事故和某個機架發生故障的情況。這裡提到的特性需要節點實體分布的支援,目前是通過可配置的IP掩碼來差別不同機房和機架的節點。

Tair 提供了兩種生成對照表的政策:

  1. 負載均衡優先,config server會盡量的把桶均勻的分布到各個data server上,所謂盡量是指在不違背下面的原則的條件下盡量負載均衡:每個桶必須有COPY_COUNT份資料; 一個桶的各份資料不能在同一台主機上;
  2. 位置安全優先,一般我們通過控制 _pos_mask(Tair的一個配置項) 來使得不同的機房具有不同的位置資訊,一個桶的各份資料不能都位于相同的一個位置(不在同一個機房)。

位置優先政策還有一個問題,假如隻有兩個機房,機房1中有100台data server,機房2中隻有1台data server。這個時候,機房2中data server的壓力必然會非常大,于是這裡産生了一個控制參數 _build_diff_ratio(參見安裝部署文檔),當機房差異比率大于這個配置值時,config server也不再build新表,機房差異比率是如何計出來的呢?首先找到機器最多的機房,不妨設使RA,data server數量是SA,那麼其餘的data server的數量記做SB,則機房差異比率=|SA – SB|/SA,因為一般我們線上系統配置的COPY_COUNT=3,在這個情況下,不妨設隻有兩個機房RA和RB,那麼兩個機房什麼樣的data server數量是均衡的範圍呢? 當差異比率小于 0.5的時候是可以做到各台data server負載都完全均衡的。這裡有一點要注意,假設RA機房有機器6台,RB有機器3台,那麼差異比率 = 6 – 3 / 6 = 0.5,這個時候如果進行擴容,在機房A增加一台data server,擴容後的差異比率 = 7 – 3 / 7 = 0.57,也就是說,隻在機器數多的機房增加data server會擴大差異比率。如果我們的_build_diff_ratio配置值是0.5,那麼進行這種擴容後,config server會拒絕再繼續build新表。

一緻性和可靠性

分布式系統中的可靠性和一緻性是無法同時保證的,因為我們必須允許網絡錯誤的發生。tair 采用複制技術來提高可靠性,并且為了提高效率做了一些優化。事實上在沒有錯誤發生的時候,tair 提供的是一種強一緻性,但是在有data server發生故障的時候,客戶有可能在一定時間視窗内讀不到最新的資料,甚至發生最新資料丢失的情況。

version

Tair中的每個資料都包含版本号,版本号在每次更新後都會遞增。這個特性可以幫助防止資料的并發更新導緻的問題。

如何擷取到目前key的version?

get接口傳回的是DataEntry對象,該對象中包含get到的資料的版本号,可以通過getVersion()接口獲得該版本号。

在put時,将該版本号作為put的參數即可。 如果不考慮版本問題,則可設定version參數為0,系統将強行覆寫資料,即使版本不一緻。

很多情況下,更新資料是先get,然後修改get回來的資料,再put回系統。如果有多個用戶端get到同一份資料,都對其修改并儲存,那麼先儲存的修改就會被後到達的修改覆寫,進而導緻資料一緻性問題,在大部分情況下應用能夠接受,但在少量特殊情況下,這個是我們不希望發生的。

比如系統中有一個值”1”, 現在A和B用戶端同時都取到了這個值。之後A和B用戶端都想改動這個值,假設A要改成12,B要改成13,如果不加控制的話,無論A和B誰先更新成功,它的更新都會被後到的更新覆寫。Tair引入的version機制避免了這樣的問題。剛剛的例子中,假設A和B同時取到資料,當時版本号是10,A先更新,更新成功後,值為12,版本為11。當B更新的時候,由于其基于的版本号是10,此時伺服器會拒絕更新,傳回version error,進而避免A的更新被覆寫。B可以選擇get新版本的value,然後在其基礎上修改,也可以選擇強行更新。

Version改變的邏輯如下:

  1. 如果put新資料且沒有設定版本号,會自動将版本設定成1;
  2. 如果put是更新老資料且沒有版本号,或者put傳來的參數版本與目前版本一緻,版本号自增1;
  3. 如果put是更新老資料且傳來的參數版本與目前版本不一緻,更新失敗,傳回VersionError;
  4. put時傳入的version參數為0,則強制更新成功,版本号自增1。

version具體使用案例,如果應用有10個client會對key進行并發put,那麼操作過程如下: 

  1. get key,如果成功,則進入步驟2;如果資料不存在,則進入步驟3;
  2. 在調用put的時候将get key傳回的verison重新傳入put接口,服務端根據version是否比對來傳回client是否put成功;
  3. get key資料不存在,則新put資料。此時傳入的version必須不是0和1,其他的值都可以(例如1000,要保證所有client是一套邏輯)。因為傳入0,tair會認為強制覆寫;而傳入1,第一個client寫入會成功,但是新寫入時服務端的version以0開始計數啊,是以此時version也是1,是以下一個到來的client寫入也會成功,這樣造成了沖突

version分布式鎖

Tair中存在該key,則認為該key所代表的鎖已被lock;不存在該key,在未加鎖。操作過程和上面相似。業務方可以在put的時候增加expire,已避免該鎖被長期鎖住。

當然業務方在選擇這種政策的情況下需要考慮并處理Tair當機帶來的鎖丢失的情況。

config server

client 和 config server的互動主要是為了擷取資料分布的對照表,當client啟動時擷取到對照表後,會cache這張表,然後通過查這張表決定資料存儲的節點,是以請求不需要和config server互動,這使得Tair對外的服務不依賴configserver,是以它不是傳統意義上的中心節點,也并不會成為叢集的瓶頸。

config server維護的對照表有一個版本号,每次新生成表,該版本号都會增加。當有data server狀态發生變化(比如新增節點或者有節點不可用了)時,configserver會根據目前可用的節點重新生成對照表,并通過資料節點的心跳,将新表同步給data server。當client請求data server時,後者每次都會将自己的對照表的版本号放入response中傳回給客client,client接收到response後,會将data server傳回的版本号和自己的版本号比較,如果不相同,則主動和config server通信,請求新的對照表。

這使得在正常的情況下,client不需要和configserver通信,即使config server不可用了,也不會對整個叢集的服務造成大的影響。有了config server,client不需要配置data server清單,也不需要處理節點的的狀态變化,這使得Tair對最終使用者來說使用和配置都很簡單。

容災

當有某台data server故障不可用的時候,config server會發現這個情況,config server負責重新計算一張新的桶在data server上的分布表,将原來由故障機器服務的桶的通路重新指派到其它有備份的data server中。這個時候,可能會發生資料的遷移,比如原來由data server A負責的桶,在新表中需要由 B負責,而B上并沒有該桶的資料,那麼就将資料遷移到B上來。同時,config server會發現哪些桶的備份數目減少了,然後根據負載情況在負載較低的data server上增加這些桶的備份。

擴容

當系統增加data server的時候,config server根據負載,協調data server将他們控制的部分桶遷移到新的data server上,遷移完成後調整路由。

注意:

不管是發生故障還是擴容,每次路由的變更,config server都會将新的配置資訊推給data server。在client通路data server的時候,會發送client緩存的路由表的版本号,如果data server發現client的版本号過舊,則會通知client去config server取一次新的路由表。如果client通路某台data server 發生了不可達的情況(該 data server可能當機了),用戶端會主動去config server取新的路由表。

遷移

當發生遷移的時候,假設data server A 要把 桶 3,4,5 遷移給data server B。因為遷移完成前,client的路由表沒有變化,是以對 3, 4, 5 的通路請求都會路由到A。現在假設 3還沒遷移,4 正在遷移中,5已經遷移完成,那麼:

  • 如果是對3的通路,則沒什麼特别,跟以前一樣;
  • 如果是對5的通路,則A會把該請求轉發給B,并且将B的傳回結果傳回給client;
  • 如果是對4的通路,在A處理,同時如果是對4的修改操作,會記錄修改log,桶4遷移完成的時候,還要把log發送到B,在B上應用這些log,最終A B上對于桶4來說,資料完全一緻才是真正的遷移完成;

Tair更多功能

用戶端

tair 的server端是C++寫的,因為server和用戶端之間使用socket通信,理論上隻要可以實作socket操作的語言都可以直接實作成tair用戶端。目前實際提供的用戶端有java 和 C++, 用戶端隻需要知道config server的位置資訊就可以享受tair叢集提供的服務了。

plugin支援

Tair還内置了一個插件容器,可以支援熱插拔插件。

Tair 分布式Key/Value高可用解決方案(可用于緩存及持久)

插件由config server配置,config server會将插件配置同步給各個資料節點,資料節點會負責加載/解除安裝相應的插件。

插件分為request和response兩類,可以分别在request和response時執行相應的操作,比如在put前檢查使用者的quota資訊等。

插件容器也讓Tair在功能友善具有更好的靈活性。

原子計數支援

Tair從伺服器端支援原子的計數器操作,這使得Tair成為一個簡單易用的分布式計數器。

item支援

Tair還支援将value視為一個item數組,對value中的部分item進行操作。比如有一個key的value為 [1,2,3,4,5],我們可以隻擷取前兩個item,傳回[1,2],也可以删除第一個item。還支援将資料删除,并傳回被删除的資料,通過這個接口可以實作一個原子的分布式FIFO的隊列。

用戶端 

目前淘寶開源的用戶端有C++和Java兩個版本,不過tair如果作為存儲層,前端肯定還需部署Nginx這樣的web伺服器,以Nginx為例,淘寶似乎還沒有開源其tair子產品,春哥(agentzh)也沒有公布tair的lua插件,如果想在Nginx裡面通路tair,目前似乎還沒有什麼辦法了,除非自己去開發一個子產品。

 原文連結:https://www.cnblogs.com/chenny7/p/4875396.html

繼續閱讀