本文源地址:http://www.mongoing.com/blog/retail-reference-architecture-part-1
如今,产品目录数据管理对零售商而言是一个非常复杂的问题。经过多年对多个庞大、由供应商提供的系统的依赖之后,零售商目前正在重新考虑他们的选择,并且开始展望未来。
在如今供应商提供的系统中,产品数据必须得频繁地使用 ETL 工具来回迁移,以保证所有的系统均在相同数据集上进行操作。这个方法就开发和管理而言是非常缓慢、容易出错,并且非常昂贵的。因此,零售商目前正在努力将数据服务单独作为一个集中的、面向服务架构(SOA)的一部分。
这是我们在MongoDB中通常看到的一个模式,因此我们开始定义一些最佳实践以及专门面向于电商空间的参考架构。作为该成果的一部分,今天我们将开始介绍如何使用MongoDB实现一项目录服务,并将其作为在零售商架构系列(共三部分)的第一部分。
为什么选择MongoDB?
许多不同的数据库类型都可以实现我们的产品目录用户案例,那么为什么要选择MongoDB呢?
- 文档灵活性:每个MongoDB文档都可以将数据存储为丰富的JSON结构。这就使得MongoDB对于存储任何对象都非常理想,包括拥有每个商品都有成千上万系列的庞大目录。
- 动态的模式:每个文档中的JSON结构可以随时进行调整,保证了需要修改时数据的灵活性以及易重构性。在MongoDB中,这些多重模式可以存储于一个单一的集合中,也可以使用共享索引,保证了新、旧格式的同步高效搜索。
- 有表现力的查询语言:能够在多个文档属性之间进行查询的能力简化了许多任务。这也可以通过减少数据库必要请求次数来提高应用的性能。
- 索引:MongoDB从一开始就提供了强大的二级、复合及地理索引选项,保证了像排序以及基于位置的查询之类的特色。
- 数据一致性:默认地,所有的读写操作都会被送到一个MongoDB复制集的主节点上。这样就保证了强一致性——一个对零售商而言非常重要的特性。因为他们可能会有许多顾客对相同的商品目录进行多次请求。
- 地理分布的复制集:由数据源与用户之间的地理距离带来的网络延迟是一个难题,尤其对于一个期望维持大量低延迟读取的目录服务而言。MongoDB的复制集可以是地理上分离的,因此它们距离用户非常近,在很多情况下可以保证快速存取、减轻内容分发网络的需求。
这些只是MongoDB成为对电商而言很好的选择的一些特性。接下来,我们将介绍一下如何将其中的一部分特性运用于我们的零售商参考架构,来支持许多特色,包括:
- 对商品及商品系列的搜索
- 对商品在每个店铺价格的检索
- 允许目录的多方面搜索和浏览
- 商品数据模型
我们需要考虑的第一件事就是商品的数据模型。在下面的例子中,我们只展示了对每件商品而言最重要的信息,例如类别、品牌以及描述:
{
“_id”: “30671”, //main item ID
“department”: “Shoes”,
“category”: “Shoes/Women/Pumps”,
“brand”: “Calvin Klein”,
“thumbnail”: “http://cdn.../pump.jpg”,
“title”: “Evening Platform Pumps”,
“description”: “Perfect for a casual night out or a formal event.”,
“style”: “Designer”,
…
}
这种简单的数据模型允许我们非常容易基于最重要原则对商品进行查询。例如,使用db.collection.findOne
,将会返回一个满足一个查询的单一文档:
- 通过ID得到商品:
db.definition.findOne({_id:”301671”})
- 通过一系列产品ID得到商品:
db.definition.findOne({_id:{$in:[”301671”,”452318”]}})
- 通过类别前缀得到商品:
db.definition.findOne({category:/^Shoes\/Women/})
注意第二个和第三个查询分别是如何使用$in操作符以及一个正则表达式的。当在正确索引的文档中执行这些类型的查询时,MongoDB可以为这些类型的查询提供高吞吐量以及低延迟的能力。
系列数据模型
对产品目录而言另一个重要的考量是商品系列,例如现有尺寸、颜色以及风格。上述的数据模型只能获取到关于每个目录商品一小部分的数据。因此,对于所有现有的、我们也许需要检索的商品系列(例如大小和颜色)而言又该怎么处理呢?
一个选择是在一个单一文档中存储一个商品以及它所有的系列。这种方法拥有能够在一个单一查询中检索一个商品以及其所有系列的优点。然而,它并不是在所有情况下都是最好的方法。避免无限制的文档增长是一个非常重要的最佳实践。如果产品系列的数据以及它们相关数据非常小,在商品文档中存储这些数据也许会有意义。
另一个选择是创建一个能够关联到主商品的、单独的系列数据模型:
{
“_id”: ”93284847362823”, //variant sku
“itemId”: “30671”, //references the main item
“size”: 6.0,
“color”: “red”
…
}
这个数据模型允许我们通过它们的商品编号来快速检索到特定的商品系列:
db.variation.find({_id:”93284847362823”})
也可以通过对itemId 属性的查询获得某个特定商品的所有系列:
db.variation.find({itemId:”30671”}).sort({_id:1})
通过这个方法,我们同时维护了在目录中展示主商品以及当用户请求一个更详细的产品视图时对每个系列的快速查询。我们也可以保证商品以及系列文档的一个可预测大小。
不同店铺不同价格
在定义产品目录的参考架构时另一个考虑是价格。我们已经看到了一些方法,能够结构化我们的商品,以直接或基于特定属性快速检索商品。价格有可能受很多因素影响,例如店铺的位置。我们需要一个方法快速检索出任何一个给定商品或者商品系列的特定价格。这对于大型零售商而言是非常困难的,因为一个拥有一百万商品以及一千个商店的商品目录意味着我们必须在一个十亿文档集合中进行查询以获得任意一个给定商品的价格。
当然,我们也可以将每个系列的价格作为一个嵌套文档在商品文档中存储起来,但是一个更好的解决方法是再次利用MongoDB可以对_id 进行快速查询的优点。例如,如果产品目录中每个商品都被一个商品ID引用,同时它的每个系列都被一个商品编号(SKU)索引,那么我们就可以将每个文档的_id设置为商品ID或者商品编号(SKU)的一个级联,并且将商店ID与价格变量相关联。通过使用这个模型,上面提到的每双单鞋的_id以及它的红色种类应该看起来是这样的:
- 商品:
30671_store23
- 某个特定规格的商品:
93284847362823_store23
这种方法也为处理价格提供很大的灵活性,因为它允许我们在商品或者系列级别对商品进行定价。我们可以查询所有价格或者只是某个特定店铺的价格:
- 所有价格:
db.prices.find({_id:/^30671/})
- 某个特定店铺的价格:
db.prices.find({_id:/^30671_store23/})
我们甚至可以添加其他组合,例如每个店铺群的价格,然后在单个查询中使用$in操作符获取对于一个商品而言所有可能的价格:
db.prices.find({_id:{$in:[ “30671_store23”,
“30671_sgroup12”,
“93284847362823_store23”,
“93284847362823_sgroup12” ]}})
浏览和搜索商品
对我们的产品目录而言,最大的一个挑战就是能够提供多方面的搜索和浏览。尽管许多用户想要使用某个特定商品或者他们正在寻找的条件来搜索我们的产品目录,但是更多的其他用户想要的是浏览,然后通过一系列属性来限制返回结果。因此,给定创建一个像下面这个页面一样的需求:
我们有许多的挑战:
- 响应时间:在用户浏览的同时,结果的每个页面应该在毫秒内返回。
- 多个属性:伴随着用户选择不同的方面(例如,品牌、大小、颜色等),新的查询必须能够在多个文档属性中运行。
- 系列级别属性:一些用户选择的属性将会在商品级别进行查询,例如品牌,但是其它的查询则有可能运行于系列级别上,例如尺寸。
- 多个系列:每个商品都有可能有成千上万个系列,但是我们只希望每个商品只展示一次,因此,结果必须消除重复项。
- 排序:用户需要能够在多个属性上进行排序,例如价格、尺寸,此外排序操作必须能够高效运行。
- 分页:每个页面只返回少量结果,这就要求确定性排序。
许多零售商也许会想要使用一个专用的搜索引擎作为这些特色的基础。MongoDB就提供了一个开源的连接件项目,它允许MongoDB和Apache Solr 以及Elasticsearch同时使用。然而,对于我们的参考架构,我们想完全在MongoDB中实现一个多方面搜索。
{
“_id”: “30671”,
“title”: “Evening Platform Pumps”,
“department”: “Shoes”,
“Category”: “Women/Shoes/Pumps”,
“price”: 149.95,
“attrs”: [“brand”: “Calvin Klein”, …],
“sattrs”: [“style”: ”Designer”, …],
“vars”: [
{
“sku”: “93284847362823”,
“attrs”: [{“size”: 6.0}, {“color”: “red”}, …],
“sattrs”: [{“width”: 8.0}, {“heelHeight”: 5.0}, …],
}, … //Many more SKUs
]
}
为了实现这个功能,我们创建了另一个集合,用于存储所谓的摘要文档。这些文档包含了我们需要基于多个搜索方面对产品目录中商品进行快速检索的所有信息。
注意:在这个数据模型中,我们定义了属性以及辅助属性。尽管一个用户也许会希望能够在某个商品或者商品系列的许多不同属性上进行搜索,但是我们只会保存一个最经常使用的核心集合。例如,给定一双鞋,对于一个用户而言,基于现有尺寸大小的查询会比基于后跟高度查询更普遍。通过在我们的数据模型中同时使用attr和sattr属性,我们可以将所有商品属性提供给搜索,但是也会带来只索引最经常使用的属性attr花费的提高。
通过使用这个数据模型,我们可以创建以下复合索引:
- 部门+属性+类别+ _id
- 部门+变量属性+类别+ _id
- 部门+类别+ _id
- 部门+价格+ _id
- 部门+评分+ _id
在这些目录中,我们经常从部门开始,然后我们假设用户将会选择部门来重新定义他们的搜索结果。对于没有部门的一个产品目录,我们可以非常轻易地从另一个像类别或者类型等比较普遍的方面开始。然后,我们可以执行需要进行多方面搜索的查询,并且快速将结果返回到页面:
- 从商品ID获取摘要
db.variation.find({_id:”30671”})
- 获取特定商品系列的摘要
db.variation.find({vars.sku:”93284847362823”},{“vars.$”:1})
- 通过部门获取所有商品的摘要
db.variation.find({department:”Shoes”})
-
使用一系列混合的参数获取摘要
db.variation.find({ “department”:”Shoes”,
“vars.attr”: {“color”:”red”},
“category”: “^/Shoes/Women”})
概要重述
今天我们了解了一些多功能商品目录系统的建模和索引的最佳实践,包括商品及商品系列的查询、店铺价格以及支持多样化搜索的目录浏览。使用这些方法作为一个起点,将会帮助你找到对于你自己的项目而言最好的设计。
了解更多
为了进一步了解如何使用MongoDB重新开启你的零售商之旅,请阅读我们的白皮书。在这篇文章中,你将会了解新的零售挑战以及MongoDB如何解决它们。
为了了解MongoDB的咨询团队如何可以帮助您的应用更快起步,探索我们的开始启动指南。
快速启动你的应用
本文译自:https://www.mongodb.com/blog/post/retail-reference-architecture-part-1...