本文源地址:http://www.mongoing.com/blog/retail-reference-architecture-part-2-appr...mongodb
在電商參考架構系列的第一部分中,咱們介紹了一個大數據量電商如何使用MongoDB做爲一個龐大產品目錄持久層的一些最佳實踐。第一部分中包括了索引、模式以及查詢優化以保證咱們的目錄可以支持相似於搜索、單店價格以及在高效率方式下多方面檢索及瀏覽等特性。在接下來的兩篇博客中,咱們將介紹類似類型的優化方法,而且將其應用到一個電商業務中徹底不一樣的方面——庫存。數據庫
一個能夠經過電商的店鋪及應用訪問到的、可靠的、集中的庫存系統是提升和豐富用戶體驗中一個很是龐大的基礎部分。下面列舉了一個電商或許想要獲得的一些特性:數組
上面這些都是一些看似基礎的特性,可是實際上也是目前大多數電商廣泛使用的傳統庫存系統類型所面臨的真實問題。在這些系統中,單個店鋪維護他們各自的庫存,而後在某個特定的時間間隔以後(一般是晚上)將數據返回關係型數據庫管理系統中心。接着,關係型數據庫管理系統將當天接收到的全部數據整合和分類以後,用於分析、報表等操做,而且將其提供給外部及內部應用。在關係型數據庫管理系統和其它應用之間,一般會有一個緩存層,由於在不少狀況下,關係型數據庫並非很適合處理該客戶端請求的事務數量,特別是面向用戶的移動或者網頁應用。緩存
所以,如今的問題很是清晰了。這些系統基礎的建立並不適用於針對咱們擁有多少庫存以及庫存位置提供一個連續精確的映射關係。此外,還可能帶來維護多個系統而致使的複雜性上升的狀況,例如:緩存以及持久性等等。而MongoDB則是對這些場景的最好選擇 -即便在電商店鋪在地理上分佈很散,MongoDB仍然能夠實現到產品信息的高精確度和系統的高可靠性。架構
首先,咱們肯定好在電商參考架構中的庫存系統應該要作的事情:app
簡而言之,咱們須要的是構建一個高性能、可水平擴展的系統,在一個龐大的、地理分佈的區域中的店鋪和用戶都可以與MongoDB進行實時交互來查看和更新目錄。post
用戶案例的一個基本需求是爲每一個店鋪維護一個關於全部庫存的、集中的、實時的視圖。咱們首先須要爲店鋪集合建立視圖,從而將咱們的庫存與地理位置相聯繫起來。結果是:每一個店鋪都使用一個至關直接的文檔。性能
{ 「_id」:ObjectId(「78s89453d8chw28h428f2423」), 「className」:」catalog.Store」, 「storeId」:」store100」, 「name」:」Bessemer Store」, 「address」:{ 「addr1」:」1 Main St.」, 「city」:」Bessemer」, 「state」:」AL」, 「zip」:」12345」, 「country」:」USA」 }, 「location」:[-86.95444, 33.40178], … }
而後,咱們能夠建立下列的索引來優化在店鋪數據中最常用讀取類型:大數據
{「storeId」:1},{「unique」:true}
: 獲取某個特定商店的庫存{「name」:1}
:根據名字獲取商店名稱{「address.zip」:1}
: 獲取一個郵編內的全部店鋪,例如:店鋪定位程序{「location」: 2dsphere}
:獲取某一個特定地理位置周圍的全部商店在上面全部的索引中,位置索引對咱們來講很是有用,由於它容許咱們基於某個位置近似查詢商店。例如,一個用戶尋找某個產品有現貨的最近商店。爲了在分片環境中利用這個優點,咱們使用一條geoNear的命令來檢索獲得那些「位置」屬性在給定點必定距離以內的文檔,而後對最近的店鋪進行排序:優化
db.runCommand({ geoNear:「stores」, near:{ type:」Point」, coordinates:[-82.8006,40.0908], //GeoJSON or coordinate pair maxDistance:10000.0, //in meters spherical:true //required for 2dsphere indexes } })
這種模式給了咱們定位對象的能力,可是同時也給在這些店鋪中追蹤和管理庫存帶來了更大的挑戰。
既然咱們已經將商品和店鋪聯繫了起來,咱們須要建立一個庫存集合來跟蹤每個商品以及它們全部商品系列的真實庫存量。然而,咱們須要在其中進行必定的取捨。爲了同時最小化對數據庫的來回讀取數目,同時下降應用級的鏈接,咱們決定將數據從店鋪集合複製到庫存集合。咱們提出的文檔是這樣的:
{ 「_id」:」902372093572409542jbf42r2f2432」, 「storeId」:」store100」, 「location」:[-86.95444, 33.40178], 「productId」:」20034」, 「vars」:[ {「sku」:」sku1」, 「quantity」:」5」}, {「sku」:」sku2」, 「quantity」:」23」}, {「sku」:」sku3」, 「quantity」:」2」}, … ] }
首先注意到:咱們在庫存文檔中同時包括了storeId
和location
屬性。很明顯,storeId
對於咱們知道哪一個商店有什麼商品是很是必要的,可是——當咱們查詢離用戶附近的庫存時會發生什麼呢?須要同時使用到庫存數據以及店鋪位置數據才能完成這個請求。經過在庫存文檔中添加地理位置數據,咱們消除了在店鋪集合中執行一個單獨查詢的需求,也減小了店鋪集合和庫存集合的一個鏈接操做。
此外,在咱們的模式中,咱們還決定在商品級別文檔中表示庫存。正如咱們在電商參考架構系列第一部分中提到的,每一個產品可能會擁有成百上千的商品系列/型號,包括尺寸、顏色、風格等等,全部這些系列必須在咱們的庫存中表示出來。那麼,問題就是:咱們是否應該支持包含一個更大系列集合的更大文檔,仍是在具體商品型號上表示庫存的更多文檔呢?在這種狀況下,咱們比較傾向於更大的文檔以下降數據冗餘度,這樣作也能夠減小在庫存集合中須要查詢或者更新的文檔總數。
接下來,咱們建立索引:
{storeId:1}
: 獲得某一個指定商店庫存中的全部商品{productId:1},{storeId:1}
: 獲取一個指定店鋪中某個產品的庫存{productId:1},{location:」2dsphere」}
:獲取在必定距離以內的某個產品的全部庫存值得注意的是:咱們並無選擇建立一個包含vars.sku
的索引。沒有這樣作的緣由是:這並不會給咱們帶來很是多的好處,由於咱們已經能夠基於productID
查詢咱們的庫存了:
db.inventory.find( { 「storeId」:」store100」, 「productId」:「20034」, 「vars.sku」:」sku11736」 }, {「vars.$」:1} )
實際上,咱們並不會從vars.sku
索引上受益多少。在這種狀況下,在productID
上的索引已經能夠獲得文檔了,所以在該變量上的索引是沒必要要的。此外,因爲系列數組有可能有成千上萬的條目,在上面的索引可能會佔用一大塊內存,從而減小在內存中存儲的文檔數目,這就意味着會下降查詢速度。考慮全部的事情,在給定目標的前提下,這是一個不中意的取捨。
那麼是什麼使得咱們的模式如此合適呢?咱們將在下一篇博客中討論這個方法爲庫存系統提供的一些特點。
爲了進一步瞭解如何使用MongoDB從新開啓你的零售商之旅,請閱讀咱們的白皮書。在這篇文章中,你將會了解新的零售挑戰以及MongoDB如何解決它們。
本文譯自:https://www.mongodb.com/blog/post/retail-reference-architecture-part-2...
翻譯:周穎敏
審稿:TJ
快速啓動你的應用