Tair總述


Tair 是淘寶自己開發的一個分布式 key/value 存儲引擎。tair 分為持久化和非持久化兩種使用方式。非持久化的 tair 可以看成是一個分布式緩存。持久化的 tair 將數據存放於磁盤中。為了解決磁盤損壞導致數據丟失,tair 可以配置數據的備份數目,tair 自動將一份數據的不同備份放到不同的主機上,當有主機發生異常,無法正常提供服務的時候,其余的備份會繼續提供服務。

Tair的總體結構

Tair作為一個分布式系統,是由一個中心控制節點和一系列的服務節點組成。我們稱中心控制節點為config server,服務節點是data server。Config server負責管理所有的data server,維護data server的狀態信息。Data server對外提供各種數據服務,並以心跳的形式將自身狀況匯報給config server。Config server是控制節點,而且是單點,目前采用一主一備的形式來保證其可靠性。所有的data server的地位都是等價的。

Tair的負載均衡

Tair的分布采用的是一致性哈希算法。對於所有的key,分配到Q個桶中,桶是負載均衡和數據遷移的基本單位。Config server根據一定的策略把每個桶指派到不同的data server上。因為數據按照key做hash算法,所以可以認為每個桶中的數據基本是平衡的,保證了桶分布的均衡性,就保證了數據分布的均衡性。

增加或者減少data server的時候會發生什么

當有某台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上。遷移完成后調整路由。當然,系統中可能出現減少了某些data server,同時增加另外的一些data server。處理原理同上。每次路由的變更,config server都會將新的配置信息推給data server。在客戶端訪問data server的時候,會發送客戶端緩存的路由表的版本號。如果data server發現客戶端的版本號過舊,則會通知客戶端去config server取一次新的路由表。如果客戶端訪問某台data server 發生了不可達的情況(該 data server可能宕機了),客戶端會主動去config server取新的路由表。

發生遷移的時候data server如何對外提供服務

當遷移發生的時候,我們舉個例子,假設data server A 要把桶 3,4,5 遷移給data server B。因為遷移完成前,客戶端的路由表沒有變化,客戶端對 3,4,5的訪問請求都會路由到A。

現在假設3還沒遷移,4正在遷移中,5已經遷移完成。那么如果是對3的訪問,則沒什么特別,跟以前一樣。如果是對5的訪問,則A會把該請求轉發給B,並且將B的返回結果返回給客戶。如果是對4的訪問,在A處理,同時如果是對4的修改操作,會記錄修改log。當桶4遷移完成的時候,還要把log發送到B,在B上應用這些log。最終A、B上對於桶4來說,數據完全一致才是真正的遷移完成。

當然,如果是因為某data server宕機而引發的遷移,客戶端會收到一張中間臨時狀態的分配表。這張表中,把宕機的data server所負責的桶臨時指派給有其備份data server來處理。這個時候,服務是可用的,但是負載可能不均衡。當遷移完成之后,才能重新達到一個新的負載均衡的狀態。

桶在data server上分布時的策略

程序提供了兩種生成分配表的策略,一種叫做負載均衡優先,一種叫做位置安全優先

我們先看負載優先策略。當采用負載優先策略的時候,config server會盡量的把桶均勻的分布到各個data server上。

所謂盡量是指在不違背下面的原則的條件下盡量負載均衡

  • 每個桶必須有COPY_COUNT份數據
  • 一個桶的各份數據不能在同一台主機上。

位置安全優先原則是說,在不違背上面兩個原則的條件下,還要滿足位置安全條件,然后再考慮負載均衡。位置信息的獲取是通過 _pos_mask(參見安裝部署文檔中關於配置項的解釋) 計算得到。一般我們通過控制 _pos_mask 來使得不同的機房具有不同的位置信息。那么在位置安全優先的時候,必須被滿足的條件要增加一條,一個桶的各份數據不能都位於相同的一個位置(不在同一個機房)。

這里有一個問題,假如只有兩個機房,機房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采用復制技術來提高可靠性,並且為了提高效率做了一些優化,事實上在沒有錯誤發生的時候,tair提供的是一種強一致性。但是在有data server發生故障的時候,客戶有可能在一定時間窗口內讀不到最新的數據。甚至發生最新數據丟失的情況。

Tair提供的客戶端

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

主要的性能數據

待補充

系統架構

一個Tair集群主要包括3個必選模塊:config server、data server和client,以及一個可選模塊:invalid server。

通常情況下,一個集群中包含2台config server及多台data server。兩台config server互為主備並通過維護和data server之間的心跳獲知集群中存活可用的data server,構建數據在集群中的分布信息(對照表)。Data server負責數據的存儲,並按照config server的指示完成數據的復制和遷移工作。Client在啟動的時候,從config server獲取數據分布信息,根據數據分布信息和相應的data server交互完成用戶的請求。Invalid server主要負責對等集群的刪除和隱藏操作,保證對等集群的數據一致。

從架構上看,config server的角色類似於傳統應用系統的中心節點,整個集群服務依賴於config server的正常工作。但實際上相對來說,tair的config server是非常輕量級的,當正在工作的服務器宕機的時候另外一台會在秒級別時間內自動接管。而且,如果出現兩台服務器同時宕機的最惡劣情況,只要應用服務器沒有新的變化,tair依然服務正常。而有了config server這個中心節點,帶來的好處就是應用在使用的時候只需要配置config server的地址(現在可以直接配置Diamond key),而不需要知道內部節點的情況。

1 Config Server的功能

  • 通過維護和data server心跳來獲知集群中存活節點的信息。

  • 根據存活節點的信息來構建數據在集群中的分布表。

  • 提供數據分布表的查詢服務。

  • 調度data server之間的數據遷移、復制。

2 Data Server的功能

  • 提供存儲引擎

  • 接受client的put/get/remove等操作

  • 執行數據遷移,復制等

  • 插件:在接受請求的時候處理一些自定義功能

  • 訪問統計

3 InvalidServer的功能

  • 接收來自client的invalid/hide等請求后,對屬於同一組的集群(雙機房獨立集群部署方式)做delete/hide操作,保證同一組集群的一致。

  • 集群斷網之后的,臟數據清理。

  • 訪問統計。

4 client的功能

  • 在應用端提供訪問Tair集群的接口。

  • 更新並緩存數據分布表和invalidserver地址等。

  • LocalCache,避免過熱數據訪問影響tair集群服務。

  • 流控

存儲引擎與應用場景

Tair經過這兩年的發展演進,除了應用於cache緩存外,在存儲(持久化)上支持的應用需求也越來越廣泛。現在主要有mdb,rdb,ldb三種存儲引擎。

1 mdb

  • 定位於cache緩存,類似於memcache。
  • 支持k/v存取和prefix操作。

mdb的應用場景

在實際業務中,大部分當緩存用(后端有DB之類的數據源)。

也可用做大訪問少量臨時數據的存儲(例如session登錄,防攻擊統計等)。

集團內絕對多數cache服務都是采用的tair mdb。

2 rdb

  • 定位於cache緩存,采用了redis的內存存儲結構。
  • 支持k/v,list,hash,set,sortedset等數據結構。

rdb的應用場景

適用於需要高速訪問某些數據結構的應用,例如SNS中常見的的粉絲存儲就可以采用set等結構;或者存儲一個商品的多個屬性(hashmap);高效的消息隊列(list)等。現在有30個左右的應用在使用rdb服務。

3 ldb

定位於高性能存儲,並可選擇內嵌mdb cache加速,這種情況下cache與持久化存儲的數據一致性由tair進行維護。 支持k/v,prefix等數據結構。今后將支持list,hash,set,sortedset等redis支持的數據結構。

ldb的應用場景

1)存儲,里面可以細分如下場景:

  • 持續大數據量的存入讀取,類似淘寶交易快照。
  • 高頻度的更新讀取,例如計數器,庫存等。
  • 離線大批量數據導入后做查詢。參見fastdump。

2)也可以用作cache:

數據量大,響應時間敏感度不高的cache需求可以采用。例如天貓實時推薦。

基本概念

1 configID

唯一標識一個tair集群,每個集群都有一個對應的configID,在當前的大部分應用情況下configID是存放在diamond中的,對應了該集群的config server地址和group name。業務在初始化tair client的時候需要配置此ConfigID。

2 namespace

又稱area, 是tair中分配給應用的一個內存或者持久化存儲區域, 可以認為應用的數據存在自己的namespace中。

同一集群(同一個configID)中namespace是唯一的。

通過引入namespace,我們可以支持不同的應用在同集群中使用相同的key來存放數據,也就是key相同,但內容不會沖突。一個namespace下是如果存放相同的key,那么內容會受到影響,在簡單K/V形式下會被覆蓋,rdb等帶有數據結構的存儲引擎內容會根據不同的接口發生不同的變化。

3 quota配額

對應了每個namespace儲存區的大小限制,超過配額后數據將面臨最近最少使用(LRU)的淘汰。

持久化引擎(ldb)本身沒有配額,ldb由於自帶了mdb cache,所以也可以設置cache的配額。超過配額后,在內置的mdb內部進行淘汰。

3.1 配額是怎樣計算的

配額大小直接影響數據的命中率和資源利用效率,業務方需要給出一個合適的值,通常的計算方法是評估在保證一定命中率情況下所需要的記錄條數,這樣配額大小即為: 記錄條數 * 平均單條記錄大小。

3.2 管理員如何配置配額

單份數據情況下,業務提供的配額就是需要配置在Tair系統中的配額。但對於多備份,在系統中實際計算的配額為: 業務配額 * 備份數。

4 expireTime — 過期時間

expiredTime 是指數據的過期時間,當超過過期時間之后,數據將對應用不可見,這個設置同樣影響到應用的命中率和資源利用率。不同的存儲引擎有不同的策略清理掉過期的數據。調用接口時,expiredTime單位是秒,可以是相對時間(比如:30s),也可以是絕對時間(比如:當天23時,轉換成距1970-1-1 00:00:00的秒數)。

  • 小於0,不更改之前的過期時間
  • 如果不傳或者傳入0,則表示數據永不過期;
  • 大於0小於當前時間戳是相對時間過期;
  • 大於當前時間戳是絕對時間過期;

5 version

Tair中存儲的每個數據都有版本號,版本號在每次更新后都會遞增,相應的,在Tair put接口中也有此version參數,這個參數是為了解決並發更新同一個數據而設置的,類似於樂觀鎖。

很多情況下,更新數據是先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,然后在其基礎上修改,也可以選擇強行更新。

5.1 如何獲取到當前key的version

get接口返回的是DataEntry對象,該對象中包含get到的數據的版本號,可以通過getVersion()接口獲得該版本號。在put時,將該版本號作為put的參數即可。 如果不考慮版本問題,則可設置version參數為0,系統將強行覆蓋數據,即使版本不一致。

5.2 version是如何改變的

Version改變的邏輯如下:

  • 如果put新數據且沒有設置版本號,會自動將版本設置成1。
  • 如果put是更新老數據且沒有版本號,或者put傳來的參數版本與當前版本一致,版本號自增1。
  • 如果put是更新老數據且傳來的參數版本與當前版本不一致,更新失敗,返回VersionError。
  • put時傳入的version參數為0,則強制更新成功,版本號自增1。

5.3 version返回不一致的時候,該如何處理

如果更新所基於的version和系統中當前的版本不一致,則服務器會返回ResultCode.VERERROR。 這時你可以重新get數據,然后在新版本的數據上修改;或者設置version為0重新請求,以達到強制更新的效果,應用可以根據自身對數據一致性的要求在這兩種策略間進行選擇。

5.4 version具體使用案例

如果應用有10個client會對key進行並發put,那么操作過程如下:

  • get key。如果get key成功,則進入步驟2;如果數據不存在,則進入步驟3。

  • 在調用put的時候將get key返回的verison重新傳入put接口。服務端根據version是否匹配來返回client是否put成功。

  • get key數據不存在,則新put數據。此時傳入的version必須不是0和1,其他的值都可以(例如1000,要保證所有client是一套邏輯)。因為傳入0,tair會認為強制覆蓋;而傳入1,第一個client寫入會成功,但是新寫入時服務端的version以0開始計數啊,所以此時version也是1,所以下一個到來的client寫入也會成功,這樣造成了沖突

5.5 version分布式鎖

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

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

5.6 什么情況下需要使用version

業務對數據一致性有較高的要求,並且訪問並發高,那么通過version可以避免數據的意外結果。

如果不關心並發,那么建議不傳入version或者直接傳0。






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM