淘寶分佈式NOSQL框架:Tair

Tair 分佈式K-V存儲方案

 

tair 是淘寶的一個開源項目,它是一個分佈式的key/value結構數據的解決方案。前端

做爲一個分佈式系統,Tair由一箇中心控制節點(config server)和一系列的服務節點(data server)組成,java

  • config server 負責管理全部的data server,並維護data server的狀態信息;爲了保證高可用(High Available),config server可經過hearbeat 以一主一備形式提供服務;
  • data server 對外提供各類數據服務,並以心跳的形式將自身情況彙報給config server;全部的 data server 地位都是等價的。

 

 

 

tair集羣的基本概念:web

  • 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 分爲持久化和非持久化兩種使用方式:redis

  • 非持久化的 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支持的數據結構。

 

 

 

MDB流程

 

 

RDB流程

 

 

LDB流程

 

 

fastdump

大數據量導入:數據預排序,按桶分memtable。緩存

 

 

 


 

分佈式策略

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的對照表,好比:服務器

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

這裏共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還內置了一個插件容器,能夠支持熱插拔插件。

插件由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,目前彷佛尚未什麼辦法了,除非本身去開發一個模塊。

 

 

 

 

 

 

參考文檔:

http://csrd.aliapp.com/?cat=8

http://code.taobao.org/p/tair/wiki/index/

相關文章
相關標籤/搜索