1. 程式人生 > >分庫深度翻頁問題&Elasticsearch的解決方式

分庫深度翻頁問題&Elasticsearch的解決方式

主要內容

o一 業界難題-跨庫分頁需求

o二 解決方案

oelasticsearch採用的解決方案&原始碼解析

o四 由分頁問題引發對es效能的思考

一 業界難題-跨庫分頁需求

1.1分頁查詢的業務需求&常用的解決方式

網際網路分頁拉取獲取資料的需求:

1)微信訊息過多時,拉取第N頁訊息

2)京東下單過多時,拉取第N頁訂單

3)瀏覽58同城,檢視第N帖子

4)樂高後臺,檢視第N頁的視訊資訊

分頁拉取的特點:

   (1)有一個業務主鍵id, 例如msg_idorder_idtiezi_id

   (2)分頁排序是按照非業務主鍵

id來排序的,業務中經常按照時間time來排序order by

常用的解決方案:

     可以通過在排序欄位time上建立索引利用SQL提供的offset/limit功能就能滿足分頁查詢需求

     select * from t_msg order by time offset 200 limit 100

     select * from t_order order by time offset 200 limit 100

     select * from t_tiezi order by time offset 200 limit 100

此處假設一頁資料為100條,均拉取第3頁資料。

1.2

分庫分頁需求

分庫需求

     高併發大流量的網際網路架構中,隨著資料量的增加,資料庫需要進行水平切分,將資料分佈到不同的資料庫例項上,來達到資料擴容的目的。

Eg:使用分庫依據patition key進行分庫示例為id取模

問題

   select * from t_user order by time offset 200 limit 100

   變成兩個庫後,分庫依據是uid,排序依據是time。如果跨越多個水平切分資料庫,且分庫依據與排序依據為不同屬性,並需要進行分頁,實現:

   select * from T order by time offset X limit Y

的跨庫分頁SQL

資料庫層失去了排序欄位的全域性視野,資料分佈在兩個庫,如何解決?

二 全域性視野法解決跨庫分頁

服務層通過uid取模將資料分佈到兩個庫上去之後,每個資料庫都失去了全域性視野,資料按照time區域性排序之後,不管哪個分庫的第3頁資料,都不一定是全域性排序的第3頁資料

2.1全域性排序場景分類

 (1極端情況,兩個庫的排序欄位的資料完全一樣。方案:每個庫offeset一半,再取半頁,就是最終需要的結果:

 (2極端情況:資料分佈極不均勻,結果資料全部來自於一個庫(eg.db1前三頁的time排序字段均小於db0的前三頁中的time欄位)。

方案:只取結果庫db1中的第3頁。

3)一般情況,每個庫資料各包含一部分。方案:由於不清楚到底是哪種情況,所以必須每個庫都返回3頁數每個庫返回X+Y條資料,X為偏移量,Y為每個分頁的大小所得到的6頁資料在服務層進行記憶體排序,得到資料全域性視野,再取第3頁資料,便能夠得到想要的全域性分頁數該方案為elasticsearch分頁排序採用的方案

2.1.2 分庫(X+Y)*N聚合詳細步驟

一般情況步驟

1)將order by time offset X limit Y,改寫成order by time offset 0 limit X+Y

2)服務層將改寫後的SQL語句發往各個分庫:即例子中的各取3頁資料

3)假設共分為N個庫,服務層將得到N*(X+Y)條資料:即例子中的6頁資料

4)服務層對得到的N*(X+Y)條資料進行記憶體排序,記憶體排序後再取偏移量X後的Y條記錄,就是全域性視野所需的一頁數

   eg:索引有5個分片,現在要請求size大小為100,獲取第200頁的資料。則偏移量X=100*200 ,Y=100,N=5,需要在記憶體中進行排序SIZE=5*(100*200+100)=100500

方案優點:通過服務層修改SQL語句,擴大資料召回量,能夠得到全域性視野,業務無損,精準返回所需資料。

方案缺點

1)每個分庫需要返回更多的資料,增大了網路傳輸量(耗網路);

2)除了資料庫按照time進行排序,服務層還需要進行二次排序,增大了服務層的計算量(耗CPU);

3)最致命的,這個演算法隨著頁碼的增大,效能會急劇下降,這是因為SQL改寫後每個分庫要返回X+Y行資料:返回第3頁,offset中的X=200;假如要返回第100頁,offset中的X=9900,即每個分庫要返回100頁資料,資料量和排序量都將大增,效能平方級下降es只允許最多對1w條資料分頁排序的原因,0.9版本不做限制,導致頻繁OOM,效能急劇下降

2.2.2 業務折衷法解決分頁排序允許精度缺失

資料庫分庫-資料均衡原理

使用patition key進行分庫,在資料量較大,資料分佈足夠隨機的情況下,各分庫所有非patition key屬性,在各個分庫上的資料分佈,統計概率情況是一致的。

例如,在uid隨機的情況下,使用uid取模分兩庫,db0和db1:

(1)性別屬性,如果db0庫上的男性使用者佔比70%,則db1上男性使用者佔比也應為70%

(2)年齡屬性,如果db0庫上18-28歲少女使用者比例佔比15%,則db1上少女使用者比例也應為15%

(3)時間屬性,如果db0庫上每天10:00之前登入的使用者佔比為20%,則db1上應該是相同的統計規律

       利用資料均衡原理原理,要查詢全域性100頁資料,offset 9900 limit 100改寫為offset 4950 limit 50,每個分庫偏移4950(一半),獲取50條資料(半頁),得到的資料集的並集,基本能夠認為,是全域性資料的offset 9900 limit 100的資料,當然,這一頁資料的精度,並不是精準的。

      使用允許精度缺失的技術方案,技術複雜度大大降低了,既不需要返回更多的資料,也不需要進行服務記憶體排序。典型的應用如論壇帖子1024 你懂得) 58同城早期招聘網站。

2.3 二次查詢法

為了方便舉例,假設一頁只有5條資料,查詢第200頁的SQL語句為select * from T order by time offset 1000 limit 5;

步驟一:查詢改寫

將select * from T order by time offset 1000 limit 5

改寫為select * from T order by time offset 500 limit 5

並投遞給所有的分庫,注意,這個offset的500,來自於全域性offset的總偏移量1000,除以水平切分資料庫個數2。

如果是3個分庫,則可以改寫為select * from T order by time offset 333 limit 5(offset=1000/3

假設這三個分庫返回的資料(time, uid)如下(每頁的返回結果都是按照time排序)

步驟二:找到所返回3頁全部資料的最小值,以及各個庫的最大值。

第一個庫,5條資料的time最小值是1487501123

第二個庫,5條資料的time最小值是1487501133

第三個庫,5條資料的time最小值是1487501143

在全域性中,三個庫的最小值為1487501123

步驟三:查詢二次改寫

第一次改寫的SQL語句是select * from T order by time offset 333 limit 5

第二次要改寫成一個between語句,between的起點是time_min,between的終點是原來每個分庫各自返回資料的最大值

第一個分庫,第一次返回資料的最大值是1487501523

所以查詢改寫為select * from T order by time where time between time_min and 1487501523

第二個分庫,第一次返回資料的最大值是1487501323

所以查詢改寫為select * from T order by time where time between time_min and 1487501323

第三個分庫,第一次返回資料的最大值是1487501553

所以查詢改寫為select * from T order by time where time between time_min and 1487501553

相對第一次查詢,第二次查詢條件放寬了,故第二次查詢會返回比第一次查詢結果集更多的資料,假設這三個分庫返回的資料(time, uid)如下:

可以看到:

由於time_min來自原來的分庫一,所以分庫一的返回結果集和第一次查詢相同(所以其實這次訪問是可以省略的);

分庫二的結果集,比第一次多返回了1條資料,頭部的1條記錄(time最小的記錄)是新的(上圖中粉色記錄);

分庫三的結果集,比第一次多返回了2條資料,頭部的2條記錄(time最小的2條記錄)是新的(上圖中粉色記錄);

步驟四:在每個結果集中虛擬一個time_min記錄,找到time_min在全域性的offset

在第一個庫中,time_min在第一個庫的offset是333

在第二個庫中,(1487501133, uid_aa)的offset是333(根據第一次查詢條件得出的),故虛擬time_min在第二個庫的offset是331

在第三個庫中,(1487501143, uid_aaa)的offset是333(根據第一次查詢條件得出的),故虛擬time_min在第三個庫的offset是330

綜上,time_min在全域性的offset是333+331+330=994

步驟五:既然得到了time_min在全域性的offset,就相當於有了全域性視野,根據第二次的結果集,就能夠得到全域性offset 1000 limit 5記錄

    在步驟二的子結果集中,取子結果集為:limit+1000-time_min_offset的資料,然後對三個子資料集進行彙總,排序。

2.2二次查詢法存在的問題

步驟12的作用在於找到最小值time_min,偏移量在X/N,對es來說該步驟在建立在列式儲存的結構中【time列式儲存,已經排好序速度很快,可以迅速返回。步驟3二次查詢的範圍是 order by time between time_min and time_i_max。 假設time_min在分庫1,其他分庫返回的結果量為:分庫比time_min大的結果數量+size,

極端情況下,其他分庫的結果都比time_min大,那麼其他庫返回的結果最大資料量為X/N+sizeN為常數,則x越大,返回的結果集越大,需要在步驟5中彙總排序的量就越大。極端情況下需要彙總排序的量為

      假如偏移量x為10億分片個數為3,分頁大小為5,深度翻頁需要排序的量為(3-1)*(10億/3+5)+5=6.7億。

      因此即使採用這種方式,在資料分佈極其不均勻的情況下,進行深度翻頁,一次進行彙總排序總量也是非常大的。推測這也是es不採用這種方式來進行深度翻頁的原因。

elasticsearch採用的解決方案&原始碼解析

      ES search_after採用的方式為業務折衷法。每次查詢的時候,為常量的查詢延遲和開銷。但是對大規模資料匯出來說,短時間內需要重複一個查詢成百上千次也是一個非常巨大的消耗。以下圖為示例,如果批量匯出資料時,每次都使用業務折衷法,則會使得各個分片再次執行一遍請求。

3.1 essearch_after&scroll實現基本原理

ES scroll查詢是對業務折中法做了進一步的優化。將彙總的結果快取一段時間。如scroll=1mrespose比傳統的查詢返回多了一個scroll_id相當用於唯一標識】,下次查詢時候,用scroll_id即可找到上次快取的結果。

注:如果業務處理時間超過快取時間,則快取會失效,導致匯出資料不全。快取時間太長會大量消耗記憶體。目前搜尋系統設定預設時長為5min。閱讀業務曾出現業務處理資料太慢,導致匯出資料量不全,解決方式為同步匯出,非同步處理業務資料

ES scroll查詢原生API使用舉例(size代表一次互動所返回的資料總量):

Elasticsearch系統架構圖

Elasticsearch系統互動圖

Elasticsearch 中處理REST相關的客戶端請求的類都放在包:org.elasticsearch.action.rest下。該包下所有的類均繼承自抽象類BaseRestHandler使用了設計模式:模板方法,業務邏輯均類似。需要對Restrequest請求作出不同的響應。

ScrollAction向控制器註冊包含滾動查詢關鍵字【scroll】的路徑,當httpServer獲取客戶端發來的請求時,呼叫restController.dispatchRequest(),restctroller通過路徑判斷將請求轉發給scrollAction.

四 由分頁問題引發對es效能的思考

       “任何脫離業務的架構設計都是耍流氓”。對於深度翻頁場景來說,除了網路爬蟲,很少有人去深度逐頁去查詢,檢視每條資料,即便是百度、google搜尋引擎,也對返回頁數做了限制。ES對分頁排序,使用全域性視野法,業務無損,精準返回全所需要的資料,但是限制分頁資料的最大size大小(預設1萬條),避免頁碼太大導致佔用大量網路和CPU資源,效能急劇下降。在我看來,ES在資源佔用和效能之間找到一個平衡點。

        同時ES對業務做了折中,支援search_after查詢方式,禁止跳頁查詢,降低了對記憶體的大量消耗以及CPU資源的佔用,為業務需求提供了另外的解決方式。

      對於批量匯出資料的場景,ES使用了scroll查詢,其是通過對search_after加入快取,做了進一步優化,實現對多分庫的資料快速、高效匯出。