1. 程式人生 > >Elasticsearch在物流資料中心的應用

Elasticsearch在物流資料中心的應用

作者簡介

巧爺,餓了麼物流研發部資料中心技術負責人,有豐富的ES、Influxdb使用經驗和資料應用開發經驗,but...文風驚奇,校稿人累覺不愛

前面隨便說一點

Elasticsearch ,簡稱es,主要運用於全文搜尋、資料分析, 底層使用開源庫Lucene,擁有豐富的REST API,開箱即用。分散式的資料儲存、倒排索引等設計,使其可以快速儲存、搜尋、分析海量資料。典型的使用方和應用場景,如github,StackOverflow,elasticsearch+logstash+kibana 一體化的日誌分析。

下面主要從我們如何儲存資料、用這些資料幹什麼兩個方面來展開我們對於es的應用。有啥子不合理、不足之處歡迎大家指正。

背景

隨著餓了麼運單資料的增長,傳統的資料庫很難支撐現有的業務:

1、各種場景的資料查詢、統計,導致資料庫必須加入各種欄位的索引,大大增加了在生成一條運單,插入資料所需要的成本,嚴重影響了生成運單的併發度;大量的索引同時佔用了很大的磁碟空間,同時給資料庫變更帶來的更大的風險;

2、很多場景沒有辦法或者很難實現,如:運單分頁查詢。大體量的資料資料庫必然是sharding的,此時資料在分頁上面必須需要通過別的工具。此時我們引入了ES,來處理允許一定延遲的資料查詢、統計的業務。

資料儲存

如大家所知的那樣,ES不支援事務、複雜的資料關係(後期版本稍有改善,但是仍然支援的不是很好),利用_version (版本號)的方式來確保應用中相互衝突的變更不會導致資料丟失,那麼我們是如何儲存我們的資料,資料結構是什麼樣子,如何保證資料的完整性和一致性的呢?

一、資料結構

首先說下我們的運單資料索引的資料結構。

1、合適的分片數和副本數。網上有很多關於如何規劃分片數的文章,本人感覺可以作為參考, 在機器效能、資料量的大小、使用場景等等的不同,分片、副本的數量最好可以通過壓測或者是線上實際流量來做調整。

2、我們會盡量減少我們所需要的欄位,做到夠用就好,mapping設定方面:設定"_all"為false,String型別"index"儘量設定為不分詞("not_analyzed",根據需要設定analyzed),商家名稱這類String型別欄位只儲存索引結構,不儲存原始文件(後面會聊到如何拿到原始文件)。

起初我們在建運單索引的時候,我們是儘量冗餘運單上面的所有資訊,導致一個星期的運單資料達到一個T的大小,而上面大部分的欄位都是不需要的,磁碟利用率很低,而用於該叢集的都是ssd盤,常常由於磁碟存不下,而需要新增機器,導致大量的資源浪費。這也需要我們支援一個額外的能力,萬一需要新增某個運單欄位,我們需要在需求上線之前迅速將歷史資料補齊這個欄位,同時不影響線上。(我們現在可以一個晚上重刷我們需要週期內的歷史資料)。

"mappings": {
	"index_type_name": {
    "_all": {
      "enabled": false
    },
    "_source": {
      "excludes": ["merchant_name"]
    },
    "properties": {
      "order_id": {
        "type": "long"
      },
	  "merchant_name": {
        "type": "string",
        "index": "not_analyzed"
      }
     ...
    }
  }
}
複製程式碼

3、以一天為一個索引(根據業務場景,因為我們的業務場景大部分要的都是某天的資料),這也為我們根據實際線上流量調整我們分片、副本數量提供了方便,修改完索引的模板("_template")之後,第二天會自動生效,而查詢多天不同分片數量的運單索引的聯合查詢不會影響查詢結果。

4、儘量避免Nested Objects資料型別(nested資料結構)。每一個nested object 將會作為一個隱藏的單獨文字建立索引,雖然官網上說在查詢的時候將根文字和nested文件拼接是很快的,就跟把他們當成一個單獨的文字一樣的快。但是其實還是有一部分的額外的消耗,尤其是在aggs聚合的時候,它會使一層聚合其實變成了兩層聚合:需要先聚合隱藏檔案,再對實際需求進行聚合。如果真的需要放入陣列型別的資料,可以根據實際需求,轉化為一個欄位,直接建在主資料上面(有必要的話,可以對nested object直接建一個新的索引)。

比如:我們現在有一個索引,裡面有某個學校每天每個學生的學習、生活情況,每個學生每天會產生一條資料。現在我們想統計每個班級某天 中午在校吃飯的人數、以及一天在校的用餐次數,我們可以設計一個nested Objects資料結構來儲存一天三餐的情況,也可以在主資料上新增四個欄位:早上是否在校吃飯,中午是否在校吃飯、晚上是否在校吃飯,三餐在校用餐次數,這樣就可以直接對著這四個欄位進行資料統計。

5、儘量減少script line的使用。同樣的道理,我們可以預先將需要用script line 的中間值先存到主資料上面。避免查詢、統計時候的額外消耗。

二、資料如何儲存的

1、考慮在不影響已有的業務情況下,我們採取解析運單資料落庫產生的binlog日誌來建索引(binlog日誌公司有一套解決方案,不一定非要使用binlog日誌,運單狀態變化的mq訊息也是可以的),使其與運單正常業務解耦

2、此時我們不會直接拿這條資料插入ES,因為運單狀態變化在同一個時刻可能會發生多次,每次的資料插入不一定是資料庫當前的狀態,而且不論binlog日誌、還是運單狀態變化訊息都只是涵蓋了部分資料,如果要運單在發訊息的時候,把所有需要的資料補齊,對於運單的業務來說,會面臨經常修改訊息結構的問題,這已經違背了我們要使其與運單正常業務解耦的初衷, 所以我們在收到這條資料變更的時候,會通過運單id反查運單資料,運單肯定會時時刻刻保持有最新的通過運單id查詢運單全部資訊的介面,這樣我們就可以拿到我們想要的任何最新資料。

3、通過es建索引的 bulk api 減少與es叢集的互動次數,提高資料寫入的吞吐量。

4、同一條運單資料,在同一個時刻可能會在機器A和機器B中同時發生更新操作,機器A查詢到的是舊資料,機器B查詢到了新資料,但是寫入索引的時候機器B先寫入ES叢集,機器A後寫入叢集,導致資料錯誤。解決方案:每條資料寫入的時候,新增一個分散式鎖,相同運單號的資料在同一個時刻只能有一條發生寫索引的動作,沒有獲得分散式鎖訊息,丟入延遲佇列,下次再消費。

5、資料的補償(此處就不展開了)。

資料的查詢和統計

一、運單資料查詢

前面我們講述了我們ES中的索引結構遵循的一些原則,其中有一條是,我們不會在ES中儲存原始文件,那麼我們是如何支援查詢運單的具體資料的呢?其實這就是一個ES叢集的定位問題,我們的ES叢集僅僅是用來豐富運單查詢、支援資料統計的功能,我們並不支援資料的實際儲存,我們儲存的僅僅只是每個欄位的索引而已,通過每個欄位的索引支援各種各樣的運單查詢、資料統計,如果需要查詢運單的詳細資訊,我們通過ES查詢得到運單id後,會去運單的查詢服務查詢到該資訊,再吐給需求方,我們會將這個步驟包掉,需求方無感知,且返回的資料只是將運單的查詢服務的資料包了一層,儘可能減少其他方的接入成本。

二、基於ES的資料的統計

ES在做資料統計的時候往往會很消耗ES叢集的資源,所以我們通常不允許需求方直接通過介面訪問ES,我們會將各個維度的資料提前算好放入其他型別的資料庫中,供業務方使用,此處也不進行展開了。

我們踩過的一些坑

(此處想到什麼講什麼了。)

1、ES資料統計查詢的時候,同樣的查詢條件,兩次查詢出來的資料結果可能會不一樣,這是因為副本分片和主分片資料不一致(ES只保證最終一致),ES在寫操作的時候有個consistency的引數來控制寫入的一致性,具體值為one(primary shard),all(all shard),quorum(default)。

one:要求我們這個寫操作,只要有一個primary shard是active活躍可用的,就可以執行

all:要求我們這個寫操作,必須所有的primary shard和replica shard都是活躍的,才可以執行這個寫操作

quorum:預設的值,要求所有的shard中,必須是大部分的shard都是活躍的,可用的,才可以執行這個寫操作

但是就算設定成了all之後,查詢還是有不一致的情況,這是使用lucene索引機制帶來的refresh問題,徹底解決該問題就勢必會增加寫入的成本,我們選取了另一種方式:對於會短時間內出現前後兩次查詢的需求指定從primary shard讀。

2、ES查詢成功,部分shard失敗;這個問題很尷尬,因為我在前期很長時間都沒注意到這個問題,發現查詢成功後,就直接把結果丟出去了,後來一次ES叢集異常,發現查詢出來的資料要比正常小很多,不可能是ES主、副本分片資料不一致的問題,才發現時該問題。

3、新增欄位的時候,一定要先更新所有已存在的索引的Mapping,再更新template,最後才能發更新後的程式。由於ES叢集寫操作在預設情況下,Mapping中沒有的欄位,會被自動識別,而自動識別的欄位可能不是我們想要的欄位型別,而這個時候想要不斷服務的修改,會很複雜。所以一定要在發新的程式之前修改好Mapping、template。

4、有時候為了提高ES叢集的效能,我們會定期的手工做一些段合併,此時要注意設定段合併的執行緒數,防止影響到正常業務。

5、監控ES的慢查詢,雖然ES叢集是分散式的,但是一樣會由於過度的慢查詢而打爆叢集的情況。

6、做好ES叢集的監控很重要,網上有很多教程,此處也不再重述了。

最後

在ElaticSearch裡面,路由功能算是一個高階用法,大多數時候我們用的都是系統預設的路由功能,ES的_routing欄位的取值預設是_id欄位,而現實在我們的業務中,有太多的欄位可以且需要作為路由欄位。如果有機會,後續篇中,我將會介紹餓了麼物流資料中心是如何通過公司已有的多活系統來支援我們的ES路由功能的相關話題。




閱讀部落格還不過癮?

歡迎大家掃二維碼通過新增群助手,加入交流群,討論和部落格有關的技術問題,還可以和博主有更多互動

部落格轉載、線下活動及合作等問題請郵件至 [email protected] 進行溝通