1. 程式人生 > >Java架構-蘇寧 11.11:蘇寧易購訂單搜尋系統架構及實現

Java架構-蘇寧 11.11:蘇寧易購訂單搜尋系統架構及實現


背景

隨著蘇寧易購平臺規模的飛速發展,平臺的訂單量呈現指數級的增長,儲存容量已達 TB 級,訂單量更是到了萬億級別,尤其在雙 11 大促流量洪峰的場景下,面臨兩個挑戰:

1、如何儲存如此巨大的資料量
2、如何提供高併發、低延遲、多維度的檢索服務
傳統關係型資料庫無法支撐多維度的模糊檢索,為此,我們選用了 elasticsearch 來提供索引服務,原因如下:

1、技術及配套元件成熟
2、有較大的使用者群體,且社群活躍
3、提供簡便易用的 api 服務,易上手
4、具有快速的水平及垂直擴容能力,具備高可用,高效能的特徵

叢集整體架構

按查詢維度以及目標使用人群,分為以下叢集
1:全量訂單欄位叢集:儲存了全部訂單資料,目前主要用於:1)其他索引叢集欄位初始化時提供資料來源。2)搜尋出訂單 ID 時,根據 ID 取出該訂單所有欄位詳情,由於訂單號即為 docId,所以直接 get 速度很快。數以億計的訂單,不可全由一個索引承載,應進行分索引處理。由於訂單號本身就是分段使用的,根據訂單號生成規則,我們將這些訂單均勻分配到多個索引中,這樣可以控制索引大小並有效分散資料。索引規則定下來了,shard 數按照每個 shard 不超過 30G 的原則來分。如果單個 shard 的容量突破 30G 時,可以根據訂單號生成的時間維度,建立新的叢集,在應用層路由到不同的叢集和索引。

Elasticsearch 的正確使用姿勢應該只是用於建索引,而不是儲存資料,但是該叢集由於歷史原因一直儲存了下來,我們後續會將該部分資料遷移到公司大資料平臺上。

2:會員搜尋叢集: 該叢集搜尋欄位相對較少,每次搜尋請求需要附帶會員號,主要用於提供給網際網路使用者搜尋“我的訂單”時使用。在索引設計上,我們按日期段分索引,以便橫向擴充套件,備份數量根據查詢請求量來設計。查詢時會帶上日期及會員號,根據日期即可定位到索引,按照會員號 routing,能直接定位到某個 shard。由於是按日期分索引,所以當叢集規模變得很大時,可以水平無限擴充套件叢集。

3:客服搜尋叢集:該叢集搜尋欄位相對較多,查詢條件不定,為了避免寬泛的搜尋條件而對線上顧客查詢造成影響,我們單獨為客服訂單查詢建了一套叢集。該叢集類似會員叢集,按日期段分索引,由於該叢集對搜尋時效沒有那麼高的要求,所以備份數可以少些。

4:頭行關係叢集:訂單頭和訂單行關係,快取記憶體。

5: Redis 叢集:流量高峰時做削峰處理,線性輸出且做到對上游系統無感知,以保護 ES 叢集

6: wildfly 叢集:對外提供 RPC 服務,外界對 ES 發起的查詢和資料初始化時一律經過此叢集,將查詢或寫入指令轉換成 ES 操作指令,這樣遮蔽底層實現,做索引或叢集調整時可以做到對上游系統透明,而且可以在介面層靈活控制訪問流量。

7: Nginx 叢集:提供 ES 外掛鑑權服務,防止不受控制的訪問 head,kopf 外掛及呼叫 REST 服務,該叢集還提供反向代理服務,遮蔽 master 節點 IP(提供 http 服務)。

8: DB 叢集:發生錯誤時,錯誤指令入 DB,後續做補償處理。
整體叢集示意圖如下:

擴容

當系統能力不足時,可選的擴容方案如下:

  1. 副本數,shard 數都不變,直接新增機器,讓 ES 自動再平衡資料,適用於單個節點上有多個分片時。機器數量增加後,單個機器上的索引分片數就相應減少,可以有效降低單個機器的 IO 壓力。

2)副本數增加,shard 數不變, 副本數增加後,對寫入 tps 會有一定的影響,但是能有效提升讀 tps。

3)副本數不變,shard 數增加。
此方案需要重建索引,所以在先期建索引時就需要考慮好資料量及增長速度。
由於叢集規模大,擴容時機器數量多,所以使用指令碼搭建機器環境,在一臺機器上操作所有機器的 JDK,ES 引數的配置。SSH 配置,ES 引數配置及服務啟動指令碼都是通用的,此處不再贅述。

增加搜尋欄位處理邏輯

在實際系統執行中,經常會發生需要增加搜尋條件的場景(會員搜尋叢集或客服搜尋叢集都可能增加索引欄位),這時候就需要重新灌資料,需要做好初始化和實時更新的順序邏輯。

1:當要初始化時,開啟初始化模式開關(基於 ZooKeeper 實現的實時配置中心)。

2:從全量叢集 scroll 資料集到其他搜尋叢集。

3:有某個文件的 update 報文過來時,不直接更新搜尋叢集的目標索引,而是從全量叢集 get 出所有目標欄位,然後全量覆蓋搜尋叢集中該文件。
這麼做是因為初始化灌資料和實時接收報文並更新是不同的執行緒,如果初始化過程中又接收到更新資料的指令,如果先更新了索引叢集,然後再拿到全量叢集的初始化資料,而拿到後全量叢集又發生了更新,則拿到的初始化資料是舊版本的資料,導致搜尋叢集和全量叢集的資料不一致。

引入 redis 叢集

為應對寫入高峰,在 wildfly 叢集前置了一組 redis 叢集,填谷削峰, 用於降低瞬時寫壓力。

為解決非同步問題,在寫入請求到來時,先入全量叢集再入 redis,成功後再返回上游系統成功,上游系統只有在拿到這個成功標識後才會再次寫入後續指令,這樣就能保證全量叢集的資料正確性。目前這個方案的效能可以滿足需求,如果需要進一步提升效能,則寫入報文全部入 redis 然後直接返回上游系統成功或失敗標識,再開啟新執行緒讀取報文到全量叢集及其他搜尋叢集,當然用此方案時需要處理好非同步執行緒之間的關係及快取中的資料順序。

在寫入 redis 時既要防止熱點分片,也要防止亂序,還要防止資料遊離沒有執行緒去消費,為此我們處理邏輯如下:

1:報文先寫入全量叢集。

2:由於有 10 個 redis 分片,所以取訂單號的末位數字,根據此數字找到位於某個分片上的待處理集合(集合名:pending_X),並將訂單號塞入該集合。這樣可以防止待處理集合產生熱點。

3:在 redis 中建立以該單號為 key 的列表,列表中存放的是報文指令(如果列表已存在則直接將報文追加到列表最後)。到此步,上半部分寫入就完成了,可以返回上游系統成功標識。

4:新開執行緒,根據指令對應的訂單號,取出待處理集合中的該訂單號的 key 並執行 setNx, 如果取到鎖,則一直處理該列表中的報文,直到拿不到資料再退出迴圈。最後刪除待處理集合中該訂單號,刪除後再做一次檢驗是否有該訂單號的列表,防止刪除待處理集合中該 key 後又有新的請求過來。

5:定時任務巡檢待處理集合中的訂單號,如果有某個訂單號且 setNx 成功,則說明之前執行佇列消費的執行緒掛掉了,此時定時任務檢漏消費。

6:定時任務巡檢所有列表,如果某個列表對應的訂單號不在待處理集合中,則撿漏消費,防止以上步驟 3 中的最後一步刪除了待處理集合中該訂單號後又有新資料進來時且消費執行緒又突然掛掉了。
寫入各個叢集的示意圖如下:

監控及管理

前臺應用提供 RPC 服務,當然後端需要有監控管理措施,我們主要做了以下幾方面:

安裝必要管理外掛,包括 marvel,head,kopf,並將外掛入口統一整合到 admin 系統,下文有詳述。

機器資源使用監控:定時任務請求 ES 自帶系統狀態服務,拿到各個節點資源使用情況,如有即將達到閾值的資源會及時告警。

快取監控:監控 redis 中有多少待處理資料,依此判斷系統是否有資料積壓,以便動態調整消費執行緒數。
資料修復:如果有資料狀態不一致,丟欄位的現象發生,則請求上游系統重新下傳錯誤訂單資料。

壓測資料清理:壓測,各個大促節點前必做事項,檢測出系統極限能力,判斷瓶頸點,以便有針對性的改進。這些資料量大的垃圾資料需要及時清理,釋放寶貴系統資源。

此外,為方便運維,減少登入 head 外掛的頻率,以防誤操作,在後臺管理系統開發了查詢功能。


許可權控制

日常運維必用的 head/kopf 外掛的安全機制: 預設的 head/kopf 外掛是不帶許可權管理的,任何人只要知道域名就能訪問(不能直接訪問到 ES 機器,生產辦公網段是隔離的),這給生產系統帶來極大隱患。在後臺管理及外掛管理我們先後做了兩套方案:

最初方案:在外掛域名所在的 nginx 上我們配置了訪問許可權控制,這個方案執行過一段時間,但是後來發現,許可權難免會洩露,對於 head 和 kopf 外掛來說還是有一定的隱患,所以用下面的替代方案。

優化後方案:把 head/kopf 外掛的原始碼拿到應用的後臺管理系統,訪問外掛頁面時需要輸入動態密碼(公司內部應用提供的服務),只有配置了認證許可權的工號才能訪問外掛所在頁面,對外掛頁面的請求通過應用伺服器發起 http 請求到原先外掛域名所在的 nginx 伺服器,拿到資料後再在本地展現,原先外掛所在域名的 nginx 只有配置了白名單的伺服器才能訪問,白名單機器限定為應用後臺系統的伺服器,這樣徹底杜絕了許可權洩露帶來的隱患。

進入叢集連結初始頁:

點選 marvel 連結後,由於不能操作叢集配置,所以還是用原先的 nginx 靜態許可權:

點選 head 或 kopf 連結後則需要輸入動態令牌:

一些需要注意的地方:

ES 對記憶體的需求較大,設定 java 最大堆時,不要超過 32G,因為一單超過 32G,會有指標壓縮問題,不同機器具體閾值不一樣,為保險起見,我們設定 -Xmx31g,垃圾回收器我們選擇了更適合於大堆記憶體的 G1。以下是一些我們 的 ES 配置項:

我本人邀約各大BATJ架構大牛共創Java架構師社群群,(群號:673043639)致力於免費提供Java架構行業交流平臺,通過這個平臺讓大家相互學習成長,提高技術,讓自己的水平進階一個檔次,成功通往Java架構技術大牛或架構師發展。

為什麼某些人會一直比你優秀,是因為他本身就很優秀還一直在持續努力變得更優秀,而你是不是還在滿足於現狀內心在竊喜!

合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

希望此文能幫到大家的同時,也聽聽大家的觀點。歡迎留言討論,加關注,分享你的高見!持續更新

  • To-陌霖Java架構

分享網際網路最新文章 關注網際網路最新發展