本文主要解決的是基於內存的K-V存儲引擎在實際應用中出現的熱點問題,設計了一個熱點可感知的KV存儲引擎,極大的提高了KV存儲引擎對於熱點數據訪問的承載能力。html
熱點問題,能夠理解爲在一個嚴重傾斜的工做負載下,頻繁的訪問和操做某一小部分數據。node
如圖,是阿里的不一樣業務中數據訪問分佈狀況,大量的數據訪問只集中在少部分的熱點數據中。在日常的狀況下,百分之五十的用戶訪問請求只是針對其中百分之一的數據,在一些極端的狀況下,當新產品發售後,大量的粉絲瘋狂進行搶購下單,業務的訪問量基本都彙集在某一小部分數據上,會出現百分之九十的用戶請求針對其中百分之一的數據。算法
目前有一些方法來解決熱點數據問題:緩存
第一種和第二種方法中由於節點的添加,會使得系統的成本會大大增長。第三種方法,若是用戶請求中涉及到不少數據更新的請求,對客戶端層面的緩存的數據須要維護,這個過程實現會變得很複雜。安全
本篇論文則是從提高單個點對熱點數據的處理能力出發(Improve Single node’s ability to handle hotspots),設計了一種熱點可感知的鍵值數據結構,實如今嚴重傾斜的工做負載下,快速的完成用戶的訪問請求。服務器
現有不少基於內存的鍵值存儲引擎一般採用鏈式哈希做爲索引,這種索引結構對熱數據是沒法感應到,數據項被隨機的分佈在鏈表上。若是圖中item3是一個熱數據項,他處在某一鏈表的尾部,須要通過屢次內存訪問才能夠將item3的數據取出來。若是高度傾斜的工做負載下,要經過屢次內存訪問才能夠的獲得item3,會使得系統的性能就會很低下。若是能夠將熱點數據放置在衝突鏈頭部,那麼系統對於熱點數據的訪問將會有更快的響應速度。數據結構
設計一個熱點可感知的索引結構須要解決兩個問題:多線程
論文提出的索引結構被稱做hotring,與傳統的哈希結構不一樣的是,將哈希表中的鏈式結構改爲了環,哈希表中存儲的頭指針能夠指向環中的任意數據項。當鏈表變成環的時候,以頭指針所指的數據項爲起點,查找要訪問的元素,若是元素不存在,就會一直循環查找下去,所以,做者將該還設計成一個有序環。併發
在變成有序環的時候,由於表示key大小所用的字節數通常不會少(一般爲10-100字節大小),只是簡單的對key進行排序,比較會帶來巨大的開銷。咱們構建字典序:order = (tag, key)。先根據tag進行排序,tag相同再根據key進行排序:以itemB舉例,鏈式哈希須要遍歷全部數據才能返回read miss。而HotRing在訪問itemA與C後,便可確認B read miss。dom
每段時間用戶的訪問需求在不斷變化,數據的熱度是動態變化的,HotRing實現了兩種策略來實現週期性的動態識別熱點並調整頭指針指向。
隨機移動策略
每一個線程維護一個變量,記錄執行了多少次請求,每R次訪問,移動頭指針指向第R次訪問的數據項。若指針已經指向該數據項,則頭指針不移動。該策略的優點是, 不須要額外的元數據開銷,實現簡單,響應速度快。
缺點:
採樣分析策略
使用該策略時,一樣的,在R次訪問後,若第R次訪問的item已是頭指針指向的item,則不啓動採樣,不然,嘗試啓動對應衝突環的進行採樣。
在介紹採樣策略以前,先介紹一下頭指針和數據項對應的數據結構。頭指針head包括:
而環上每一項的next包括(由於現代操做系統所使用的內存地址空間使用64bit,而環中下一項的地址實際只須要48bit便可表示,其他的16bit來控制併發,訪問次數等信息):
如今開始介紹採樣分析策略,若是第R次訪問數據項並非頭指針指向的數據項,說明熱點數據已經發生了變化,這個時候會對衝突環進行採樣。採樣的過程以下:
CAS(Compare And Swap):當要操做內存中某一個變量的時候,會記錄下變量中的舊值,經過對舊值進行一系列的操做後獲得新值,而後將舊值會與內存中的變量作比較,若是不相同,則說明內存中的值在這期間內被修改過,這時CAS操做將會失敗,新值將不會被寫入內存。若是相同,使內存中的變量變爲新值。
對數據採樣結束後,利用已有的信息能夠進行判斷將哪個數據項做爲頭節點。具體過程以下:
3. 使用CAS原子操做將新的頭指針指向數據項。
4. 重置頭指針和數據項的計數器。
在通常狀況下,對於更新操做,HotRing能夠對不超過8字節的數據進行update-in-place原子更新操做,這種狀況下,讀取和更新被視做同樣的操做。但對於超過8個字節的大數據進行更新,hotring則會使用read-copy-update協議,RCU——更新數據的時候,首先拷貝一個副本,而後對副本進行修改,最後使用一個回調(callback)機制在適當的時機把指向原來數據的指針從新指向新的被修改的數據,這個期間數據都是能夠隨意讀的。
當更新的數據項是頭指針指向的熱數據項時,由於要修改前一個數據項的next指針,須要遍歷整個環來獲取頭節點的前一項。如圖,遍歷獲得熱數據的前一項須要花費大量的內存訪問開銷。論文在這種狀況下,更新的只是前一項的計數器,其餘項的計數器不變,這樣可使得頭指針能夠在後面的策略調整中直接指向熱數據的前一項,使得對熱數據的更新須要的內存訪問操做就會減小。
1.Read操做
Hotringd的讀取不須要任何的操做,操做徹底無鎖。
2.Insert操做
當兩個線程都在B、D之間插入時,對原來結構的修改,只涉及到B項的next項,修改B項的next項的競爭衝突能夠經過CAS保證線程安全。若前一項next字段發生競爭,CAS會失敗,此時操做須要重試。
3.Update操做
當更新的數據不超過8字節:使用in-place update方法去更新便可,不須要其它操做,線程安全能夠經過CAS保證。當更新的數據超過8字節:使用RCU更新,由於採用RCU方法,這時候須要分3種狀況:
①RCU-update & Insert
2個線程分別更新B和插入C,兩個線程對原來結構的影響分別是修改A的next指針和修改B的next指針,即使存在CAS操做,但二者之間不存在衝突,因此兩個操做都會成功,但結果確定不是咱們所但願的。
解決辦法:RCU-update前,嘗試CAS修改B.next值,置B.next.occupy = 1。另一個線程的在完成插入操做時,使用CAS原子操做訪問前一個節點B的next字段,發現該字段的occupy位爲1,操做會失敗,重試。操做完成後,新版本項的occupy爲0。
②RCU-update & RCU-update
2個線程分別更新B和更新D,兩個線程對原來結構的影響分別是修改A的next指針和修改B的next指針,即使存在CAS操做,但二者之間不存在衝突,因此兩個操做都會成功,但結果確定不是咱們所但願的。
解決辦法:更新B的時候,建立一個新的數據項B’,使用CAS操做修改B的,置B.next.occupy = 1,當另外一個線程修改D節點後,使用CAS鏈接前一個節點B的時候,發現B.next.occupy = 1,操做會失敗,重試。
③RCU-update & Delete
2個線程分別刪除B和更新D,會出現和上述同樣的問題。此處再也不贅述。
4.頭指針在併發下的移動操做
當衝突環上存在多個熱點數據時,鍵值對存儲引擎的性能就會大大下降。所以HotRing設計了無鎖rehash策略來解決這一問題。和普通的哈希表不一樣的是使用負載因子來觸發rehash不一樣,HotRing使用訪問開銷(即操做平均內存訪問次數)來觸發rehash,文中設置平均內存訪問次數超過2的時候,就會自動觸發。HotRing rehash分爲3步:
1. 初始化 ——首先建立線程來專門處理rehash操做,初始化一個2倍大小的散列表,複用tag的最高一位來進行索引,將原先的一個環拆分紅了兩個環。根據tag範圍對數據項進行劃分。假設tag最大值爲T,tag範圍爲[0,T),則兩個新的頭指針對應tag範圍爲[0,T/2)和[T/2,T)。而後該線程建立一個rehash node,裏面包含2個rehash child item,做爲2個新環的頭,它的格式和data item同樣,可是tag值分別是0和T/2。
2. 分割——接下來須要分割原有的環到2個新的環。如圖,由於itemB和E是tag的範圍邊界,因此線程會將兩個rehash item節點分別插入到itemB和E以前。到目前爲止,已經在邏輯上將衝突環一分爲二。
3. 刪除——最後一步,將每個環中首尾兩部分鏈接在一塊兒。此外,還有一些收尾工做,包括舊哈希表的回收、以及rehash節點的刪除回收等。須要注意的是,在完成刪除操做以前,要確保全部對舊哈希表的訪問已經結束。只有rehash線程會阻塞一段時間。
在一臺內存容量爲32GB的服務器上測試的,測試的時候使用YCSB提供5種工做負載,默認狀況下,使用64個線程在兩億五千萬個鍵值對測試負載B,在測試負載中,有百分之97.8的操做是針對其中百分只1的數據,百分之99.8的操做是針對10%的數據,
Deployment
Baselines
在單線程和多線程狀況下,對這幾種數據結構的性能進行了測試。Hotring在大量讀操做的狀況下,能夠實現一個很高的性能。
下圖中,左圖代表,鏈或者環中數據項的個數即使不少,hotring也能夠保持一個很好的性能。右圖代表hotring在數據訪問呈現嚴重傾斜的狀況下,也能保持很是好的性能。(θ是齊夫分佈的參數,YCSB生成工做負載時, θ的值越高,代表測試的所使用負載的傾斜程度就越嚴重。)
在下圖中,左圖代表hotring在read miss的狀況下的性能比chaining hash的性能要好。這是由於hotring中每個桶的環是有序的,判斷元素不存在不須要遍歷桶中的全部元素。右圖熱點切換時,不一樣的熱點選擇策略穩定下來須要的時間,hotring-r在兩秒內就能夠達到一種穩定的狀態了。
在分裂前,爲了保證從舊哈希表進入的訪問均已經返回,rehash的過程被阻塞一段時間,隨着數據容量的不斷增加,rehash操做能夠維持這個穩定的性能。以下圖所示:
參考資料:
1.Jiqiang Chen, Liang Chen, Sheng Wang, Guoyun Zhu, Yuanyuan Sun, Huan Liu, Feifei Li:HotRing: A Hotspot-Aware In-Memory Key-Value Store. FAST 2020: 239-252