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

本文主要分享騰訊智慧零售團隊優碼業務在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 = false
rs.reconfig(cfg)  

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

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

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

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

社區招募

爲了讓社區組委會成員和志願者朋友們靈活參與,同時我們爲想要深度參與社區建設的夥伴們開設了“招募通道”,如果您想要在社區裏面結交志同道合的技術夥伴,想要通過在社區沉澱有價值的乾貨內容,想要一個展示自己的舞臺,提升自身的技術影響力,即刻加入社區貢獻隊伍~ 點擊鏈接提交申請:
http://mongoingmongoing.mikecrm.com/CPDCj1B

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章