行業案例| MongoDB在騰訊零售優碼中的應用

MongoDB中文社群發表於2022-05-07

本文主要分享騰訊智慧零售團隊優碼業務在MongoDB中的應用,採用騰訊雲MongoDB作為主儲存服務給業務帶來了較大收益,主要包括: 高效能、快捷的DDL操作、低儲存成本、超大儲存容量等收益,極大的降低了業務儲存成本,並提高了業務迭代開發效率。


   一. 業務場景   

騰訊優碼從連線消費者到連線渠道終端,實現以貨的數字化為基礎的企業數字化升級,包含營銷能力升級和動銷能力升級。騰訊優碼由正品通、門店通和會員通三個子產品組成。

騰訊優碼整體檢視

1.1 正品通


騰訊優碼正品通提供防偽 鑑真能力,實現 一物一碼全流程正品追溯,全鏈路資料儲存至區塊鏈,確保真實可信;更可直達品牌私域,實現流量進一步轉化;同時正品通提供微信域內的品牌保護能力,阻斷品牌偽冒網站傳播、幫助消費者識別假冒商品。

產品主要包含如下核心特性:


1.2 門店通


騰訊優碼門店通是 服務品牌方、經銷商、業代員以及終端門店四大零售鏈路核心角色實現基於終端銷售門店的 銷售管理手段升級與銷售額提升

產品主要包含如下核心特性


1.3 會員通


騰訊優碼會員通是面向零售品牌商提供的SaaS+ 定製化服務的產品,以掃碼為切入點,連線線上線下場景。 提供豐富的掃碼/互動活動模型、活動評估體系助力品牌連線消費者。

產品主要包含如下核心特性:


   二.碼儲存選型   

騰訊智慧零售優碼業務 儲存零售商品二維碼資訊,該資訊為智慧零售最核心的資料資訊,提供 “從連線消費者到連線渠道終端,實現以貨的數字化為基礎的企業數字化升級”相關服務。因此碼資料儲存問題是專案最核心的問題。

2.1 需求和方案


要解決碼儲存問題,首先需要 分析碼儲存的特徵。經過分析碼儲存問題的主要特徵是:

  • 海量資料: 騰訊優碼做的商品二維碼,隨著越來越多的商品使用騰訊優碼業務,二維碼資料開始呈現指數級增長。

  • 關聯儲存: 碼與碼之間存在1:1和1:N:N的關聯關係,需要儲存這種關係,並且提供相應的關聯查詢。

  • 多維度查詢: 針對不同的應用場景需要提供不同維度的條件查詢。


在獲取到碼儲存特徵之後,經過多方調研和排查之後,初步選取了2種儲存方案:

1.  MySQL + ES:MySQL 分庫分表儲存碼後設資料,提供需要高效能的讀寫場景;然後根據需求將部分資料同步 ES 以應對各種複雜的查詢場景。
2.  MongoDB:MongoDB 是 全球排名最高的分散式NoSQL資料庫,其核心特性是 No Schema、高可用和分散式,非 常適合分散式儲存。

2.2 方案分析

2.2.1 MySQL + ES方案分析

MySQL + ES 是一個比較常見的儲存解決方案,並且在很多領域內被廣泛應用,如會員或商品資訊儲存領域。此方案的優勢是 能夠提供非常多的查詢方式和不同的效能保障,可以應對各種各樣複雜的業務查詢需求。

MySQL + ES 的常見架構是寫操作直接作用於MySQL,然後通過 canal + Kafka 的方式將資料變更同步到ES,然後再根據不同的查詢場景從MySQL或者ES查詢資料。下圖是在騰訊優碼業務場景下可能的架構圖:


從架構圖可以看出,本方案存在幾個問題:

  • 資料同步和一致性問題: 這個問題在資料量不大的情況下不會有影響。但是如果資料量百億甚至千億時就是一個非常嚴重的問題。

  • 資料容量問題: 一般情況下 MySql 的單表資料最好維持在百萬級一下,如果單表資料量過大之後讀寫都是個問題。那麼如果要儲存千億資料就要幾千上萬張表,如此多的分表需要業務自己維護時開發運維都是幾乎不可行的。

  • 成本問題: 資料冗餘儲存,會增加額外的儲存成本。同時ES 為了保證資料可靠性和查詢效能,需要更多的機器和記憶體。而且 ES 存在資料膨脹問題,對於同樣的資料,需要相當MySql來說更大的磁碟。

  • DDL運維問題: MySql 在分庫分佈之後,因為DDL語句需要操作大量的庫表,因此非常耗時,同時也容易出錯。根據我們以前的專案經驗來說,當有幾百張表,單表幾十萬資料時,一個簡單的增加欄位的DDL語句也需要1小時甚至更久才能完成。

  • 開發成本問題: 此方案需要業務自己維護分庫分表、資料同步和根據需求選取不同的查詢引擎。不僅整個架構複雜,同時在做業務需求時需要慎重考慮,稍不注意使用錯的儲存引擎就可能導致效能問題。

  • 水平擴容問題: MySql 分庫分表要擴容需要業務手動 rehash 搬遷資料,成本非常高,而且很難處理擴容過程中的資料讀寫問題。


2.2.2 MongoDB 方案分析

MongoDB 是非常出名的分散式儲存引擎,具備 No Schema、高可用、分散式、資料壓縮等多方面的優勢。雖然MongoDB 是NoSQL 儲存引擎,但是其 Wired Tiger 儲存引擎和innerdb 一樣底層使用的是B+樹,因此MongoDB 在提供分散式儲存的前提下同時能夠提供大部分MySql 支援的查詢方式。因此,在使用 MongoDB 時,我們不需要MySql冗餘表或者 ES 來支援大部分的分散式查詢。在騰訊優碼的應用場景下,基於MongoDB 的儲存架構如下圖所示:


從圖中可以看出, MongoDB可以避免冗餘儲存帶來的資料同步和一致性問題、儲存成本問題、資源/運維/開發成本。而且在進一步測試和分析MongoDB的功能和效能之後,我們發現MongoDB還具備如下優勢:

  • 無DDL問題: 因為MongoDB 是No Schema 的,因此可以避免MySql的DDL問題。

  • 資料自動均勻: MongoDB 有自動rebalance 功能,可以在資料分佈不均勻的時候,自動搬遷資料,保證各個分片間的負載均勻。

  • 更低的成本: MongoDB 自帶資料壓縮,在同等資料下,MongoDB 需求的磁碟更少。

  • 更高的效能: MongoDB 最大化的利用了記憶體,在大部分場景下擁有接近記憶體資料庫的效能。經過測試MongoDB的單分片讀效能約為3萬QPS。

  • 更多的讀寫方式: 雖然MongoDB沒有ES的倒排索引,其支援的查詢方式略遜於ES。但是,MongoDB在擁有大部分ES的查詢能力的同時,其效能遠高與ES;而且相對MySql 來說MongoDB 的欄位型別支援內嵌物件和陣列物件,因此能滿足跟多的讀寫需求。


2.3 方案對比


通過前面的分析,我們初步判斷MongoDB擁有更好的表現。因此為了進一步確定MongoDB的優勢,我們深入對比了MySQL + ES 與MongoDB在各方面的表現。


2.3.1 儲存成本對比


MongoDB 在儲存上的優勢主要體現在兩個方面: 資料壓縮和無冗餘儲存。

為了更加直觀的看出磁碟使用情況,我們模擬了在騰訊優碼業務場景下,MySQL + ES和MongoDB下的實際儲存情況。

一方面,在MySQL+ES的方案下,為了滿足需要我們需要將冗餘一份ES資料和MySQL的冗餘表。其中碼的核心資料儲存在MySQL中,其磁碟總量僅佔總的38.1%。前面說過 MongoDB的方案是不需要冗餘儲存的,因此 使用MongoDB可以減少這61.9%的總資料容量。


另一方面,經過測試同樣的碼資料,MongoDB snappy壓縮演算法的壓縮率約3倍,zlib 壓縮演算法的壓縮率約6倍。因此,雖然業務為了保證系統的穩定性而選擇 snappy 壓縮演算法, 但MongoDB 仍然只需要 MySQL 三分之一的磁碟消耗。


2.3.2 開發運維成本

  • 無資料同步鏈路 :使用MongoDB不需要資料同步,因此就不需要維護canal服務和kafka佇列,大大減少開發和運維難度。

  • 人力成本收益: 在MySQL+ES架構下每次對MySQL叢集做新增欄位變更,都需運維 一定的人日投入,並且存在業務抖動風險,同時影響業務迭代釋出進度,迭代釋出耗時且風險大。

  • 開發維護成本: MongoDB儲存架構簡單,一份儲存,無資料一致性壓力。

  • 動態擴容: MongoDB 支援隨時動態擴容,基本不存在容量上限問題,而MySQL在擴容時需要業務手動rehash變遷資料,並自己保證資料一致性和完整性。


2.3.3 效能對比


經過壓測,同樣的4C8G的機器配置下,MySQL和MongoDB在大資料量下寫效能基本一致。MySQL的讀性單分片約6000QPS左右,ES的效能只有800QPS左右。而  MongoDB 單分片地讀效能在3萬QPS左右,遠高於MySQL和 ES 的效能。

2.3.4 總結

經過上面的分析和對比之後,可以明顯看出 MongoDB 在各方面都有優勢。為了更加直觀的看出不同方案的差異,這裡列出了從功能、效能、成本、可擴充套件性和可維護性等5個方面的對比資料:


綜上所述,MongoDB 不僅能完全滿足業務需求,同時在效能、成本、可維護性等各方面都優於其它兩種方案,因此騰訊優碼最終選用的是MongoDB 作為業務核心資料碼的儲存方案。

三.MongoDB分片

叢集優化過程


零售優碼業務對成本要求較高、資料量較大,線上真實讀寫流量不是太高(讀3W QPS要求),因此採用低規格4C8G規格(單節點規格)分片模式叢集部署。

3.1 分片叢集片建選擇+預分片


零售優碼資料查詢都是通過碼id查詢,因此選擇碼id作為片建,這樣可以最大化查詢效能,索引查詢都可以通過同一個分片獲取資料。此外,為了避免分片間資料不均衡引起的moveChunk操作,因此選擇hashed分片方式,同時提前進行預分片,MongoDB預設支援hashed預分片,以優碼詳情表為例,預分片方式如下:
    use db_code_xx  sh.enableSharding("db_code_xx")  //n為實際分片數  sh.shardCollection("db_code_xx.t_code_xx", {"id": "hashed"}, false,{numInitialChunks:8192*n})


    3.2 低峰期滑動視窗設定


    由於MongoDB例項節點規格低(4C8G),當分片間chunks資料不均衡的情況下,會觸發自動balance均衡,由於例項規格低,balance過程存在如下問題:

    • CPU消耗過高,遷移過程甚至消耗90%左右CPU

    • 業務訪問抖動,耗時增加

    • 慢日誌增加

    • 異常告警增多


    以上問題都是由於balance過程進行moveChunk資料搬遷過程引起,為了快速實現資料從一個分片遷移到另一個分片,MongoDB內部會不停的把資料從一個分片挪動到另一個分片,這時候就會消耗大量CPU,從而引起業務抖動。

    MongoDB核心也考慮到了balance過程對業務有一定影響,因此預設支援了balance視窗設定,這樣就可以把balance過程和業務高峰期進行錯峰,這樣來 最大化規避資料遷移引起的業務抖動。例如設定凌晨0-6點低峰期進行balance視窗設定,對應命令如下:
      use config  db.settings.update({"_id":"balancer"},{"$set":{"activeWindow":{"start":"00:00","stop":"06:00"}}},true)

      3.3 寫多數派優化


      由於優碼二維碼資料非常核心,為了避免極端情況下的資料丟失和資料迴歸等風險,因此客戶端採用writeConcern={w: “majority”}配置,確保資料寫入到副本集大多數成員後才向客戶端傳送確認。

      鏈式複製的概念: 假設節點A(primary)、B節點(secondary)、C節點(secondary),如果B節點從A節點同步資料,C節點從B節點同步資料,這樣A->B->C之間就形成了一個鏈式的同步結構,如下圖所示:

            
      MongoDB多節點副本集可以支援鏈式複製,可以通過如下命令獲取當前副本集是否支援鏈式複製:
        cmgo-xx:SECONDARY> rs.conf().settings.chainingAllowed  true  cmgo-xx:SECONDARY>

        此外,可以通過檢視副本集中每個節點的同步源來判斷當前副本集節點中是否存在有鏈式複製情況,如果同步源為secondary從節點,則說明副本集中存在鏈式複製,具體檢視如下副本集引數:
          cmgo-xx:SECONDARY> rs.status().syncSourceHost  xx.xx.xx.xx:7021  cmgo-xx:SECONDARY>

          由於業務配置為寫多數派,鑑於效能考慮可以關閉鏈式複製功能,MongoDB可以通過如下命令操作進行關閉:

            cfg = rs.config()  cfg.settings.chainingAllowed = falsers.reconfig(cfg)

            • 鏈式複製好處: 可以大大減輕主節點同步oplog的壓力。

            • 鏈式複製不足: 當寫策略為majority時,寫請求的耗時變大。


            基於寫效能考慮,當業務採用“寫大多數”策略時,直接關閉鏈式複製功能,確保寫鏈路過長引起的寫效能下降。

            關於作者:
            CSIG騰訊優碼團隊、騰訊MongoDB團隊






            來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69961190/viewspace-2892861/,如需轉載,請註明出處,否則將追究法律責任。

            相關文章