本文源地址: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
快速启动你的应用