百億數據,毫秒級返回,如何設計?--淺談實時索引構建之道

本文已整理致個人 github 地址 https://github.com/allentofight/easy-cs,歡迎你們 star 支持一下html

前言

近年來公司業務迅猛發展,數據量爆炸式增加,隨之而來的的是海量數據查詢等帶來的挑戰,咱們須要數據量在十億,甚至百億級別的規模時依然能以秒級甚至毫秒級的速度返回,這樣的話顯然離不開搜索引擎的幫助,在搜索引擎中,ES(ElasticSearch)毫無疑問是其中的佼佼者,連續多年在 DBRanking 的搜索引擎中評測中排名第一,也是絕大多數大公司的首選,那麼它與傳統的 DB 如 MySQL 相比有啥優點呢,ES 的數據又是如何生成的,數據達到 PB 時又是如何保證 ES 索引數據的實時性以更好地知足業務的需求的呢。node

本文會結合我司在 ES 上的實踐經驗與你們談談如何構建準實時索引的一些思路,但願對你們有所啓發。本文目錄以下mysql

  1. 爲何要用搜索引擎,傳統 DB 如 MySQL 不香嗎
    • MySQL 的不足
    • ES 簡介
  2. ES 索引數據構建
  3. PB 級的 ES 準實時索引數據構建之道

爲何要用搜索引擎,傳統 DB 如 MySQL 不香嗎

MySQL 的不足

MySQL 架構天生不適合海量數據查詢,它只適合海量數據存儲,但沒法應對海量數據下各類複雜條件的查詢,有人說加索引不是能夠避免全表掃描,提高查詢速度嗎,爲啥說它不適合海量數據查詢呢,有兩個緣由:git

一、加索引確實能夠提高查詢速度,但在 MySQL 中加多個索引最終在執行 SQL 的時候它只會選擇成本最低的那個索引,若是沒有索引知足搜索條件,就會觸發全表掃描,並且即使你使用了組合索引,也要符合最左前綴原則才能命中索引,但在海量數據多種查詢條件下頗有可能不符合最左前綴原則而致使索引失效,並且咱們知道存儲都是須要成本的,若是你針對每一種狀況都加索引,以 innoDB 爲例,每加一個索引,就會建立一顆 B+ 樹,若是是海量數據,將會增長很大的存儲成本,以前就有人反饋說他們公司的某個表實際內容的大小才 10G, 而索引大小卻有 30G!這是多麼巨大的成本!因此千萬不要以爲索引建得越多越好。github

二、有些查詢條件是 MySQL 加索引都解決不了的,好比我要查詢商品中全部 title 帶有「格力空調」的關鍵詞,若是你用 MySQL 寫,會寫出以下代碼sql

SELECT * FROM product WHERE title like '%格力空調%'

這樣的話沒法命中任何索引,會觸發全表掃描,並且你不能期望全部人都能輸對他想要的商品,是人就會犯錯誤,咱們常常會犯相似把「格力空調」記成「格空間」的錯誤,那麼 SQL 語句就會變成:數據庫

SELECT * FROM product WHERE title like '%格空調%'

這種狀況下就算你觸發了全表掃描也沒法查詢到任何商品,綜上所述,MySQL 的查詢確實能力有限。編程

ES 簡介

與其說上面列的這些點是 MySQL 的不足,倒不如說 MySQL 自己就不是爲海量數據查詢而設計的,術業有專攻,海量數據查詢還得用專門的搜索引擎,這其中 ES 是其中當之無愧的王者,它是基於 Lucene 引擎構建的開源分佈式搜索分析引擎,能夠提供針對 PB 數據的近實時查詢,普遍用在全文檢索、日誌分析、監控分析等場景。服務器

它主要有如下三個特色:架構

  • 輕鬆支持各類複雜的查詢條件: 它是分佈式實時文件存儲,會把每個字段都編入索引(倒排索引),利用高效的倒排索引,以及自定義打分、排序能力與豐富的分詞插件等,能實現任意複雜查詢條件下的全文檢索需求
  • 可擴展性強:自然支持分佈式存儲,經過極其簡單的配置實現幾百上千臺服務器的分佈式橫向擴容,輕鬆處理 PB 級別的結構化或非結構化數據。
  • 高可用,容災性能好:經過使用主備節點,以及故障的自動探活與恢復,有力地保障了高可用

咱們先用與 MySQL 類比的形式來理解 ES 的一些重要概念

經過類比的形式不難看出 ES 的如下幾個概念
一、 MySQL 的數據庫(DataBase)至關於 Index(索引),數據的邏輯集合,ES 的工做主要也是建立索引,查詢索引。
二、 一個數據庫裏會有多個表,一樣的一個 Index 也會有多個 type
三、 一個表會有多行(Row),一樣的一個 Type 也會有多個 Document。
四、 Schema 指定表名,表字段,是否創建索引等,一樣的 Mapping 也指定了 Type 字段的處理規則,即索引如何創建,是否分詞,分詞規則等
五、 在 MySQL 中索引是須要手動建立的,而在 ES 一切字段皆可被索引,只要在 Mapping 在指定便可

那麼 ES 中的索引爲什麼如此高效,能在海量數據下達到秒級的效果呢?它採用了多種優化手段,最主要的緣由是它採用了一種叫作倒排索引的方式來生成索引,避免了全文檔掃描,那麼什麼是倒排索引呢,經過文檔來查找關鍵詞等數據的咱們稱爲正排索引,返之,經過關鍵詞來查找文檔的形式咱們稱之爲倒排索引,假設有如下三個文檔(Document)

要在其中找到含有 comming 的文檔,若是要正排索引,那麼要把每一個文檔的內容拿出來查找是否有此單詞,毫無疑問這樣的話會致使全表掃描,那麼用倒排索引會怎麼查找呢,它首先會將每一個文檔內容進行分詞,小寫化等,而後創建每一個分詞與包含有此分詞的文檔以前的映射關係,若是有多個文檔包含此分詞,那麼就會按重要程度即文檔的權重(一般是用 TF-IDF 給文檔打分)將文檔進行排序,因而咱們能夠獲得以下關係

這樣的話咱們我要查找全部帶有 comming 的文檔,就只需查一次,並且這種狀況下查詢多個單詞性能也是很好的,只要查詢多個條件對應的文檔列表,再取交集便可,極大地提高了查詢效率。

畫外音:這裏簡化了一些流程,實際上還要先根據單詞表來定位單詞等,不過這些流程都很快,能夠忽略,有興趣的讀者能夠查閱相關資料瞭解。

除了倒排索引外,ES 的分佈式架構也自然適合海量數據查詢,來看下 ES 的架構

一個 ES 集羣由多個 node 節點組成,每一個 index 是以分片(Shard,index 子集)的數據存在於多個 node 節點上的,這樣的話當一個查詢請求進來,分別在各個 node 查詢相應的結果並整合後便可,將查詢壓力分散到多個節點,避免了單個節點 CPU,磁盤,內存等處理能力的不足。

另外當新節點加入後,會自動遷移部分分片至新節點,實現負載均衡,這個功能是 ES 自動完成的,對比一個下 MySQL 的分庫分表須要開發人員引入 Mycat 等中間件並指定分庫分表規則等繁瑣的流程是否是一個巨大的進步?這也就意味着 ES 有很是強大的水平擴展的能力,集羣可輕鬆擴展致幾百上千個節點,輕鬆支持 PB 級的數據查詢。

固然 ES 的強大不止於此,它還採用了好比主備分片提高搜索吞吐率,使用節點故障探測,Raft 選主機制等提高了容災能力等等,這些不是本文重點,讀者可自行查閱,總之通過上面的簡單總結你們只須要明白一點:ES 的分佈式架構設計天生支持海量數據查詢

那麼 ES 的索引數據(index)是如何生成的呢,接下來咱們一塊兒來看看本文的重點

如何構建 ES 索引

要構建 ES 索引數據,首先得有數據源,通常咱們會使用 MySQL 做爲數據源,你能夠直接從 MySQL 中取數據而後再寫入 ES,但這種方式因爲直接調用了線上的數據庫查詢,可能會對線上業務形成影響,好比考慮這樣的一個場景:

在電商 APP 裏用的最多的業務場景想必是用戶輸入關鍵詞來查詢相對應的商品了,那麼商品會有哪些信息呢,一個商品會有多個 sku(sku 即同一個商品下不一樣規格的品類,好比蘋果手機有 iPhone 6, iPhone 6s等),會有其基本屬性如價格,標題等,商品會有分類(居家,服飾等),品牌,庫存等,爲了保證表設計的合理性,咱們會設計幾張表來存儲這些屬性,假設有 product_sku(sku 表), product_property(基本屬性表),sku_stock(庫存表),product_category(分類表)這幾張表,那麼爲了在商品展現列表中展現全部這些信息,就必須把這些表進行 join,而後再寫入 ES,這樣查詢的時候就會在 ES 中獲取全部的商品信息了。

這種方案因爲直接在 MySQL 中執行 join 操做,在商品達到千萬級時會對線上的 DB 服務產生極大的性能影響,因此顯然不可行,那該怎麼生成中間表呢,既然直接在 MySQL 中操做不可行,可否把 MySQL 中的數據同步到另外一個地方再作生成中間表的操做呢,也就是加一箇中間層進行處理,這樣就避免了對線上 DB 的直接操做,說到這相信你們又會對計算機界的名言有進一步的體會:沒有什麼是加一箇中間層不能解決的,若是有,那就再加一層。

這個中間層就是 hive

什麼是 hive

hive 是基於 Hadoop 的一個數據倉庫工具,用來進行數據提取、轉化、加載,這是一種能夠存儲、查詢和分析存儲在 Hadoop 中的大規模數據的機制,它的意義就是把好寫的 hive 的 sql 轉換爲複雜難寫的 map-reduce 程序(map-reduce 是專門用於用於大規模數據集(大於1TB)的並行運算編程模型),也就是說若是數據量大的話經過把 MySQL 的數據同步到 hive,再由 hive 來生成上述的 product_tmp 中間表,能極大的提高性能。hive 生成臨時表存儲在 hbase(一個分佈式的、面向列的開源數據庫) 中,生成後會定時觸發 dump task 調用索引程序,而後索引程序主要從 hbase 中讀入全量數據,進行業務數據處理,並刷新到 es 索引中,整個流程以下

這樣構建索引看似很美好,但咱們須要知道的是首先 hive 執行 join 任務是很是耗時的,在咱們的生產場景上,因爲數據量高達幾千萬,執行 join 任務一般須要幾十分鐘,從執行 join 任務到最終更新至 ES 整個流程經常須要至少半小時以上,若是這期間商品的價格,庫存,上線狀態(如被下架)等重要字段發生了變動,索引是沒法更新的,這樣會對用戶體驗產生極大影響,優化前咱們常常會看到經過 ES 搜索出的中有狀態是上線但實際是下架的產品,嚴重影響用戶體驗,那麼怎麼解決呢,有一種可行的方案:創建寬表

既然咱們發現 hive join 是性能的主要瓶頸,那麼可否規避掉這個流程呢,可否在 MySQL 中將 product_sku,product_property,sku_stock 等表組合成一個大表(咱們稱其爲寬表)

這樣在每一行中商品涉及到的的數據都有了,因此將 MySQL 同步到 hive 後,hive 就不須要再執行耗時的 join 操做了,極大的提高了總體的處理時間,從 hive 同步 MySQL 再到 dump 到 ES 索引中從原來的半小時以上下降到了幾分鐘之內,看起來確實不錯,但幾分鐘的索引延遲依然是沒法接受的。

爲何 hive 沒法作到實時導入索引

由於 hive 構建在基於靜態批處理的Hadoop 之上,Hadoop 一般都有較高的延遲而且在做業提交和調度的時候須要大量的開銷。所以,hive 並不可以在大規模數據集上實現低延遲快速的查詢等操做,再且千萬級別的數據全量從索引程序導入到 ES 集羣至少也是分鐘級。

另外引入了寬表,它的維護也成了一個新問題,設想 sku 庫存變了,產品下架了,價格調整了,那麼除了修改原表(sku_stock,product_categry 等表)記錄以外,還要將全部原表變動到的記錄對應到寬表中的全部記錄也都更新一遍,這對代碼的維護是個噩夢,由於你須要在全部商品相關的表變動的地方緊接着着變動寬表的邏輯,與寬表的變動邏輯變動緊藕合!

PB 級的 ES 準實時索引構建之道

該如何解決呢?仔細觀察上面兩個問題,其實都是同一個問題,若是咱們能實時監聽到 db 的字段變動,再將變動的內容實時同步到 ES 和寬表中不就解決了咱們的問題了。

怎麼才能實時監聽到表字段的變動呢?

答案:binlog

來一塊兒複習下 MySQL 的主從同步原理

  1. MySQL master 將數據變動寫入二進制日誌( binary log, 其中記錄叫作二進制日誌事件binary log events,能夠經過 show binlog events 進行查看)
  2. MySQL slave 將 master 的 binary log events 拷貝到它的中繼日誌(relay log)
  3. MySQL slave 重放 relay log 中事件,將數據變動反映它本身的數據

能夠看到主從複製的原理關鍵是 Master 和 Slave 遵循了一套協議才能實時監聽 binlog 日誌來更新 slave 的表數據,那咱們能不能也開發一個遵循這套協議的組件,當組件做爲 Slave 來獲取 binlog 日誌進而實時監聽表字段變動呢?阿里的開源項目 Canal 就是這個乾的,它的工做原理以下:

  • canal 模擬 MySQL slave 的交互協議,假裝本身爲 MySQL slave ,向 MySQL master 發送dump 協議
  • MySQL master 收到 dump 請求,開始推送 binary log 給 slave (即 canal )
  • canal 解析 binary log 對象(原始爲 byte 流)

這樣的話經過 canal 就能獲取 binlog 日誌了,固然 canal 只是獲取接收了 master 過來的 binlog,還要對 binlog 進行解析過濾處理等,另外若是咱們只對某些表的字段感興趣,該如何配置,獲取到 binlog 後要傳給誰? 這些都須要一個統一的管理組件,阿里的 otter 就是幹這件事的。

什麼是 otter

Otter 是由阿里提供的基於數據庫增量日誌解析,準實時同步到本機房或異地機房 MySQL 數據庫的一個分佈式數據庫同步系統,它的總體架構以下:


注:以上是我司根據 otter 改造後的業務架構,與原版 otter 稍有不一樣,不過大同小異

主要工做流程以下

  1. 在 Manager 配置好 zk,要監聽的表 ,負責監聽表的節點,而後將配置同步到 Nodes 中
  2. node 啓動後則其 canal 會監聽 binlog,而後通過 S(select),E(extract),T(transform),L(load) 四個階段後數據發送到 MQ
  3. 而後業務就能夠訂閱 MQ 消息來作相關的邏輯處理了

畫外音:zookeeper 主要協調節點間的工做,如在跨機房數據同步時,可能要從 A 機房的節點將數據同步到 B 機房的節點,要用 zookeeper 來協調,

你們應該注意到了node 中有 S,E,T,L 四個階段,它們的主要做用以下

  • Select 階段: 爲解決數據來源的差別性,好比接入 canal 獲取增量數據,也能夠接入其餘系統獲取其餘數據等。

  • Extract階段: 組裝數據,針對多種數據來源,mysql,oracle,store,file等,進行數據組裝和過濾。

  • Transform 階段: 數據提取轉換過程,把數據轉換成目標數據源要求的類型

  • Load 階段: 數據載入,把數據載入到目標端,如寫入遷移後的數據庫, MQ,ES 等

以上這套基於阿里 otter 改造後的數據服務咱們將它稱爲 DTS(Data Transfer Service),即數據傳輸服務。

搭建這套服務後咱們就能夠經過訂閱 MQ 來實時寫入 ES 讓索引實時更新了,並且也能夠經過訂閱 MQ 來實現寬表字段的更新,實現了上文中所說的寬表字段更新與原表緊藕合的邏輯,基於 DTS 服務的索引改進架構以下:

注意:「build 數據」這一模塊對實時索引更新是透明的,這個模塊主要用在更新或插入 MySQL 寬表,由於對於寬表來講,它是幾個表數據的並集,因此並非監聽到哪一個字段變動就更新哪一個,它要把全部商品涉及到的全部表數據拉回來再更新到寬表中。

因而,經過 MySQL 寬表全量更新+基於 DTS 的實時索引更新咱們很好地解決了索引延遲的問題,能達到秒級的 ES 索引更新!

這裏有幾個問題可能你們比較關心,我簡單列一下

須要訂閱哪些字段

對於 MySQL 寬表來講因爲它要保存商品的完整信息,因此它須要訂閱全部字段,可是對於紅框中的實時索引更新而言,它只須要訂閱庫存,價格等字段,由於這些字段若是不及時更新,會對銷量產生極大的影響,因此咱們實時索引只關注這些敏感字段便可。

有了實時索引更新,還須要全量索引更新嗎

須要,主要有兩個緣由:

  • 實時更新依賴消息機制,沒法百分百保證數據完整性,須要全量更新來支持,這種狀況不多,並且消息積壓等會有告警,因此咱們一天只會執行一次全量索引更新
  • 索引集羣異常或崩潰後能快速重建索引

全量索引更新的數據會覆蓋實時索引嗎

會,設想這樣一種場景,你在某一時刻觸發了實時索引,而後此時全量索引還在執行中,還未執行到實時索引更新的那條記錄,這樣在的話當全量索引執行完以後就會把以前實時索引更新的數據給覆蓋掉,針對這種狀況一種可行的處理方式是若是全量索引是在構建中,實時索引更新消息能夠延遲處理,等全量更新結束後再消費。也正由於這個緣由,全量索引咱們通常會在凌晨執行,因爲是業務低峯期,最大可能規避了此類問題。

總結

本文簡單總結了我司在 PB 級數據下構建實時 ES 索引的一些思路,但願對你們有所幫助,文章只是簡單提到了 ES,canal,otter 等阿里中間件的應用,但未對這些中間件的詳細配置,原理等未做過多介紹,這些中間件的設計很是值得咱們好好研究下,好比 ES 爲了提升搜索效率、優化存儲空間作了不少工做,再好比 canal 如何作高可用,otter 實現異地跨機房同步的原理等,建議感興趣的讀者能夠以後好好研究一番,相信你會受益不淺。

若是你們對方案有些疑問,歡迎你們積極留言探討,共同進步^_^,更多精品文章,歡迎你們掃碼關注「碼海」

巨人的肩膀

  • Elasticsearch簡介及與MySQL查詢原理對比:https://www.jianshu.com/p/116cdf5836f2
  • https://www.cnblogs.com/zhjh256/p/9261725.html
  • otter安裝之otter-node安裝(單機多節點安裝):https://blog.csdn.net/u014642915/article/details/96500957
  • MySQL和Lucene索引對比分析: https://developer.aliyun.com/article/50481
  • 10 分鐘快速入門海量數據搜索分析引擎 Elasticearch: https://www.modb.pro/db/29806
  • ElasticSearch和Mysql查詢原理分析與對比:https://www.pianshen.com/article/4254917942/
  • 帶你走進神同樣的Elasticsearch索引機制:https://zhuanlan.zhihu.com/p/137574234
相關文章
相關標籤/搜索