有贊億級訂單同步的探索與實踐

1.1 同步現狀

當前有贊訂單同步流程及業務現狀如圖所示,採用了 ES+HBase(tip1)架構體系去解決搜索和詳情的需求,利用 canal(tip2)將數據庫變動寫入到 mq 中,而後利用同步系統來解決相關數據同步問題,然後續下文中將敘述有贊訂單同步面臨的問題及應對方案。java

2、同步

2.1 實現同步基礎 - 單表同步

2.1.1 亂序問題

單表同步如圖所示:mysql

業務場景中,在這任何一個序號鏈路中,若是併發出現同一主鍵的兩條消息,同時在 Nosql 中沒有作版本控制,都有可能形成消息亂序問題,而相反,若是經過順序解析 binlog 的同時,爲每條 statement sql 執行的結果分配一個順序 SeqNo,該 SeqNo 保證有序(tip3),再經過 Nosql 中控制樂觀鎖就能夠解決順序性問題。程序員

2.1.2 HBase 同步

Hbase 同步相對來講比較簡單,Hbase 內部擁有 timestamp 協助控制每一個 qualify 的版本,只要讓 timestamp 傳入上文所說的順序 SeqNo,那麼就能夠保證每一個字段讀取出來的數據是最終一致性的。sql

2.1.3 ES 同步

ES 同步針對單表場景能夠經過 index 的操做來進行寫入,index 能夠採用 exteneral 版本也稱做外部版本號來進行控制,一樣適用上述的 SeqNo 來作樂觀鎖去解決該問題。數據庫

2.1.4 同步過程圖

2.2 實現同步進階 - 多表同步

2.2.1 亂序問題

多表同步基於單表同步的基礎上作相關的同步,如圖所示:網絡

當數據庫的兩張表(不關心是否在一個實例上,若是不在,還更慘,SeqNo 還不必定可以保證有序)觸發了更新操做,假設 t1 生成 binlog 的 SeqNo 小於 t2 生成 binlog 的 SeqNo,若 t1 這條消息因序號鏈路中的網絡抖動或其它緣由形成消費晚於 t2,也就是 t2 的 binlogSeq 先寫入 Nosql 中,那麼就會形成一個 t1 的數據沒法寫入到 Nosql 中。多線程

2.2.2 HBase 同步

其實上述多表同步亂序的問題並非絕對的,針對 Hbase 這種自帶列版本號的將會自動處理或丟棄低版本數據,同時針對這種狀況,設計成每一個 table 表中的字段都會列入到 Hbase 中。舉個例子,針對訂單的情形存入訂單主表和訂單商品表架構

場景 1:1

針對訂單主表,咱們寫入的數據以訂單號作 hash,而後以 hash 值: 訂單號做爲主鍵下降熱點問題,同時定義單 column family,qualifiy 格式爲 表: 字段 value 爲對應的 value 值,timestamp 爲 SeqNo,如圖所示:併發

場景 1:n

針對訂單商品表,咱們寫入的數據一樣以訂單號生成相應的 rowkey,同時定義單 column family,qualifiy 格式爲 表: 字段: 對應記錄的 id 值 value 爲對應的 value 值,timestamp 爲 SeqNo,如圖所示:nosql

經過上述寫入,可以針對具體到某個字段都有對應的 timestamp 值的更新,爲後期寫入更新數據可以更新到具體字段級別。

2.2.3 ES 同步

針對上面的同步亂序問題,ES 沒有 HBase 這種列版本號,ES 只有 doc 級別的 version,若是上述真的出現 SeqNo2>SeqNo1, 且 SeqNo2 早於 SeqNo1 寫入到 ES 中,則就會出現 SeqNo1 的內容沒法寫入,也就會形成順序不一致的狀況。那如何去解決這個多表同步問題呢?

既然會亂序,那讓它有序就行了,數據保證有序不就可以解決這個事情嘛,讓整個鏈路有序也就表明 canal 消費 binlog 數據保證有序且丟到 MQ 中有序,MQ 而後保證順序投遞到 Sync 消費處理程序中,經過消費一條消息而後 ack 告訴 MQ 是否成功,已達到保證全部數據所有有序(若多線程或多機器處理 MQ 中的多個分區都是會存在問題)。如圖所示:

如此只要保證 t1 表的數據和 t2 表中的數據在 ES 不互相關聯,每一個數據寫入的時候按照 update 方式寫入(若是不存在須要作一次 create 操做), 這樣就能保證全部數據按照順序執行。

2.3 配置化同步

上文已經講到數據同步是由一個數據源(這個數據源能夠來自於 MQ、Mysql 等)同步到另一個數據源(Mysql、ES、HBase、Alert 等),也就是一個管道的過程。借鑑了一下 logstash 官網,一樣處理流程分爲 input、filter、output 組件,這些流程稱之爲 task 任務,如圖所示:

經過這些組件,抽象化出每一個組件都有對應的配置,由這些配置來進行初始化組件,驅動組件去執行流程。簡單來講,只須要在頁面中配置一些組件,無需開發任何一行代碼就能實現同步任務。如圖所示:

經過一系列的配置,就能配置出一個任務,針對業務邏輯,能夠採用動態語言 groovy 來實行腳本化處理(複雜業務場景能夠經過 UDF 函數來作支持),針對 mqinput 拿到的字段而後通過處理,通過過濾 filter 等,能夠直接拿到相關的數據進行組裝,而後配置化的寫入到 ES 中,無需開發任何一行 java 代碼便可實現流程自動配置化,針對複雜的需求也可以高效率支持。配置界面如圖所示:

2.3.1 性能瓶頸

上述就能解決 ES 多表同步的問題,可是一樣會存在一些問題:

  • 性能瓶頸問題
  • 失敗堆積問題

性能瓶頸:好比寫入量超級大的場景狀況下,而 Sync 消費程序只能針對 MQ 中的分區(kafka 的 partition 概念)消費,每一個分區只能有一個線程去執行,消費速率與消費分區成正比,與消費 RT 成反比,尤爲是大促場景下就會形成數據消費不過來,數據堆積嚴重問題。

失敗堆積:由於是順序消費,只要某個分區的某條消息消費失敗,後續消息就會所有堆積,形成數據延遲率超高。因此建議用順序隊列的場景除非是業務量沒有性能瓶頸的狀況下能夠採起使用,而怎麼去解決順序隊列或者去掉順序隊列呢?

用順序隊列無非就是保證有序,由於 ES 沒有 HBase 的字段級別版本號,目前訂單採用的是用 HBase 作一層中間處理層,解決該問題,如圖所示:

經過藉助 HBase 字段級別版本號幫助每一個表保證表內部字段有序,同時 put 寫入完數據以後,經過額外字段 version 作 increment 操做,當這兩個寫入動做完成以後立馬 get 操做拿到 HBase 的數據寫入到 ES 中,不管併發程度如何,最終至少有一次的 get 請求拿到的版本 version 字段是最大的,用該 version 做爲 ES 的外部版本號解決 ES 版本號問題。

用此方案會有好處:

  • HBase 協助管理內部字段版本,同時根據內部操做,協助 ES 拿到對應的版本,且數據能拿到最新數據;
  • 去掉了順序隊列,HBase 具備良好的吞吐,相對於順序隊列擁有更大的吞吐量;
  • 橫向拓展增大消費速率;
  • ES 能夠採用 index 操做,性能更好。

固然也有弊端:HBase 存在抖動的狀況,以及主備切換問題。

由於存在抖動或者準備切換問題,會形成數據不一致,咱們該怎麼去解決這個事情呢?

2.4 將來擴展

目前訂單同步是經過加載配置文件形式來作的,也就是橫向拓展的機器都會去加載同一份配置文件,各個任務經過異常解耦,理論上不會有影響,可是會存在加載任務的重要度的問題。

舉個例子:

  • 我須要一臺機器臨時去消費數據解決線上問題;
  • 有個量級很大但又不是很重要的任務,想不影響其餘任務的進行;
  • 要作對比,增量延遲對比或全量對比數據,但又不但願影響其餘數據;
  • 查詢日誌須要全部機器查看查詢(固然,公司有內部日誌系統,可直接上去查看) 如此,可讓同步系統無狀態化,每一個任務的配置加載有任務配置平臺來進行配置,指定相關的機器作相關的處理,擴容也能夠動態申請擴容,如圖所示,能夠自由分配機器處理不一樣的任務。

3、一致性保障

上文講了有贊在處理訂單的時候怎麼講數據同步到 ES 或 HBase,數據來源於 binlog,寫入到 MQ,也就是說處理的來源來自於 MQ。簡單一句話來說:咱們不生產消息,咱們是消息的搬運工。「搬運工」的角色能夠作一些事情,一樣有贊在處理數據對比也是如此,這章講講「搬運工」能夠作什麼:

3.1 數據對比

上述通常狀況下不會出問題,那若是出問題了怎麼辦,須要作數據對比,而數據來源就是咱們剛剛拋棄的順序隊列,順序隊列有個缺點就是堆積,一樣咱們也能夠利用堆積的特性,讓其第一條消息堆積十分鐘,那麼後續消息基本上也會堆積十分鐘,而後就能夠消費這個消息進行數據拉取,拿到最新的數據進行數據對比,如圖所示:

經過對比結果發送到 alert 中,就能夠知道哪些數據不一致,頻率多少,這也是一種同步(mq->filter->alert)!

3.2 全量對比 / 數據刷入

上述咱們講到數據同步到 Nosql 中,可是隻是講了增量的一個過程,涉及到歷史數據,就須要對歷史數據進行遷移,一樣,這也是一種數據同步,後面將會出相關博文怎麼去作全量數據同步。

4、Tips

Tip-1:爲何採用 ES + HBase 處理搜索和詳情?

通常狀況下,公司達到必定規模,有相似全文檢索的需求或者高頻 key:value 的時候,你們會推薦 ES+HBase 的架構體系去完成搜索和詳情的需求,而現實中,絕大多數狀況下生產環境不會將數據直接寫入到 ES 或者 Hbase,你們都會優先寫入數據庫,不進行雙寫的操做是由於增長鏈路影響業務。固然 Hbase 可能還好一點,ES 自己就是非實時查詢系統(爲何是非實時,有興趣的能夠去看看 ES 讀寫流程),這種狀況下也造就了 ES 和 HBase 的一個準實時系統。針對業務來講,準實時是能夠知足相關需求的,好比商家搜索訂單並不要求實時。

Tip-2:爲何有贊選 canal 解析 binlog,而不是採用業務消息進行數據同步?

  • 數據表被更改,好比修數據狀況,業務消息不會觸發,致使沒法寫入到對應的 Nosql 中形成。數據不一致
  • 順序性問題沒法獲得相關保障;
  • 業務消息並不能拿到全部相關的數據進行寫入到 nosql 中。

Tip-3:SeqNo 實現方式,爲何不用 binlogoffset?

由於 cana 實例與 mysql 實例是 1:N(推薦 1:1), 而大部分業務場景同一種數據通常會落在同一個實例上,canal 就能夠經過該臺實例的時間與每秒處理的個數相結合。如:timestamp*10000+counter++,而不用 binlogoffset 的緣由是 mysql 的實例掛了話,binlogoffset 可能會亂序。

5、結語

有贊交易訂單管理承接了億級流量的同步任務,面臨着衆多的需求挑戰,從最開始的 mysql 到現在的產品化的同步任務,從單表同步到多表同步,從單索引到多索引,從增量到全量,都有不一樣的解決之道,現在新興搜索中臺更是承接億級搜索和同步流量,若有興趣,歡迎加入,咱們一塊兒共同探討。

歡迎工做一到五年的Java工程師朋友們加入Java程序員開發: 721575865 羣內提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用本身每一分每一秒的時間來學習提高本身,不要再用"沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代! 

相關文章
相關標籤/搜索