本篇從介紹搜索分頁爲起點,簡單闡述分頁式數據搜索與原有集中式數據搜索思惟方式的差別,就分頁問題對deep paging問題的現象進行分析,最後介紹分頁式系統top N的案例。java
Elasticsearch中search語法有from和size兩個參數用來實現分頁的效果:node
from和size這兩個參數的含義和MySql使用limit關鍵字分頁的參數含義是同樣的。mysql
舉幾個示例,查詢第1-3頁的請求:sql
GET /music/children/_search?size=10 GET /music/children/_search?size=10&from=10 GET /music/children/_search?size=10&from=20
集中式數據存儲方式,從最先的單體應用模式,到早期的SOA服務模式,那時存儲大多數都是採用集中式數據存儲,數據落地到mysql等關係型數據中,有支持讀寫分離,部署了多臺數據庫實例實現主-從結構的本質上也仍是集中式存儲。數據庫
在單數據庫或主從數據庫中,執行分頁查詢,統計排序等思路相對清晰,畢竟數據都完完整整地放在一塊兒,直接挑一臺實例搞就是了,可能就是容量有上限,出結果慢一些而已。數據結構
關係型數據庫使用分佈式數據存儲的經典方案是分庫分表,同一張表的數據,用必定的路由邏輯,拆分在不一樣的數據庫實例裏存儲,此時作數據統計,就不能只關注一個實例了。架構
分佈式數據存儲方式,搜索思路就開始有了細微的改變,好比說Elasticsearch,索引的數據是拆分存儲在各個shard裏的,每一個shard可能散佈在ES集羣的各個node上,這種狀況下,作查詢,統計分析等操做,雖然ES已經封裝好了技術細節,咱們仍然須要明白這是一個分佈式儲存的查詢方案。併發
我的認爲,分佈式數據與集中式數據的處理差別,雖然在關係型數據庫或ES方面,已經有成熟的框架對其進行封裝,但使用者仍是須要從思惟上去理解分佈式帶來的改變,這樣才能獲得正確的結果。框架
deep paging簡單來講叫深度分頁,就是搜索得特別深,顯示第好幾百頁的數據。爲何說deep paging是有問題的?分佈式
咱們假定索引內有20000條數據,存儲在5個shard裏,發送一個有條件和指定排序字段的查詢請求,若是我要取第1頁的數據,那麼每一個shard都取10條數據,彙總到Coordinate Node裏,共50條,Coordinate Node對這50條數據再進行排序,濾掉後面的40條數據,只取最前面的
10條,返回給客戶端。
若是是第1000頁呢?
按老套路每一個shard取10000-10010條,彙總到Coordinate Node裏,仍是50條,最後返回給客戶端?
這麼作就錯啦,分佈式數據不是這麼查的,第1000頁,在每一個shard中不是取第10000-10010條,而是取前面10010,5個shard共取50050條給Coordinate Node,Coordinate Node彙總數據完成排序等操做後再取第10000-10010條,返回客戶端10條數據。
費了這麼大勁收集到50050條數據,實際給客戶端的就10條,丟掉50040條數據,好費內存。
若是第10000頁呢?這結果不忍直視
若是一個索引的分片數越多,須要彙總的數據就會成倍增加 ,能夠看到分佈式系統對結果排序分佈的成本隨深度呈指數上升,最重要的兩個影響維度是分頁深度和shard數量。這種重量級的查詢,極有可能拖垮整個Elasticsearch集羣,因此說搜索引擎對任何查詢都不要返回超過1000個結果。
deep paging的問題,經過優化搜索關鍵詞,控制分頁深度,問題能獲得必定的改善,那top N問題如如何解決呢?
聚合查詢中,常常能遇到查詢最XX的10條記錄這種分析需求,這種就是top N問題模型。
咱們先舉個熟悉的案例:統計播放量最高的10首的英文兒歌。
document數據結構:
{ "_index": "music", "_type": "children", "_id": "2", "_version": 6, "found": true, "_source": { "name": "wake me, shark me", "content": "don't let me sleep too late, gonna get up brightly early in the morning", "language": "english", "length": "55", "likes": 0 } }
這個需求ES處理起來駕輕就熟,有下面幾個緣由:
有這上面幾點的保證,查詢時ES就能夠放心大膽地在每一個shard取播放數最高的前10條數據,Coordinate Node彙總的數據也就50條,此時性能很是高。
上一小節依賴document的預先設計和shard存儲數據的特性,避免了全索引掃描,性能特別高,假設系統中針對天天的播放點擊,都有一個播放日誌記錄,記錄着歌曲ID,點擊人,點擊時間,收聽時長,時長百分比(與完整歌曲的百分比,有聽到一半就退出不聽了的,這個值就是50%),該document的數據示例:
{ "_index": "playlog-20191121", "_type": "music", "_id": "1", "_version": 1, "found": true, "_source": { "music_id": 1, "listener": "tony wang", "listen_date": "2019-11-21 15:35:00", "music_length": 52, "isten_percentage": 0.95 } }
假設歌曲總量200萬條,天天的播放日誌1億條,日誌索引天天創建一個,primary shard數量爲10,命名格式playlog-yyyyMMdd,需求是搜索當天的播放排行榜,取排名前10的記錄。
若是直接統計,就只能硬扛了,基本過程以下:
這個過程絕對是重量級,若是每次都實時統計的話,ES集羣的壓力可想而知。
預先增長按日期統計的索引數據結構,每次有用戶點擊播放時,額外發送一條更新消息將其數據更新,查詢時直接從統計的索引裏出結果,避免每次查詢。
數據統計的需求,能夠用定時任務進行計算,將計算結果存儲起來,經過下降實時性,來避免全索引掃描計算的壓力。
簡單對比:
良好的數據結構設計能夠很大程度地下降ES查詢壓力,提升實時查詢的性能,但有一點須要接受:考慮得再周全的設計,也難適應變幻無窮的需求;需求變動是沒法避免的,沒有一勞永逸的方案。
本篇從分頁查詢入手,闡述了deep paging的問題緣由,並順帶將本身對分佈式系統與集中式系統處理的思惟差別作了簡單描述,最後引伸了top N場景的問題,上面提到的改進方案,只是針對比較簡單的場景,實際生產要面臨的狀況確定更復雜,好比採用分佈式計算組件storm來解決top N 問題的,這裏當作是拋磚引玉,歡迎各位分享本身的見解。
專一Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公衆號:Java架構社區