楼主: jieforest

[转载] Cassandra数据模型设计最佳实践

[复制链接]
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
11#
 楼主| 发表于 2013-10-31 22:13 | 只看该作者
实战:User和Item中间的’Like’关系

这个示例是关于电子商务系统的一个功能,一个user可以喜欢多个item,同时一个item可以被多个user所喜爱,在关系型数据库中这个关系是通过many-to-many实现的,如下图所示:

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
12#
 楼主| 发表于 2013-10-31 22:13 | 只看该作者
通过上面的模型,我们可以进行如下查询:

1)通过user id获取user

2)通过item id获取item

3)获取指定user喜欢的所有item

4)查看指定item被那些user所喜爱

下面将介绍几个通过Cassandra建模解决上面问题的现方案,反范式的顺序从低到高。你会发现最佳方案依赖于查询模式。

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
13#
 楼主| 发表于 2013-10-31 22:14 | 只看该作者
方案1:完全按照关系数据库模型设计

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
14#
 楼主| 发表于 2013-11-1 09:15 | 只看该作者
这个模型支持通过user id查询user和通过item id查询item。但无法简单查询某个user喜爱的所有item或者某个item被那些user所喜爱。

对于这个用例来说,这是最糟糕的设计,主要是因为User_Item_Like没有设计好。

注意:为了简单起见,关系型数据模型中的timestamp字段没有体现到Cassandra模型中(这个字段用于存储user何时喜爱某个item),我会在后面介绍它。

方案2:使用自定义索引范式化实体

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
15#
 楼主| 发表于 2013-11-1 09:15 | 只看该作者
这个模型中User和Item是范式化实体,user id 和item id被映射存储两次,第一次是通过item id存储user id(User_By_Item),第二次通过item id存储user id(Item_By_User)。

这样,我们很容易可以通过Item_By_User查询某个user喜欢的全部item,还可以通过User_By_Item查询某个item被哪些user所喜爱。这里我们使用了,Item_By_User和User_By_Item这两个column family作为自定义二级索引。(译者注:Cassandra column family也有二级索引功能,它的作用是通过创建column key索引快速查询到column value)。

有这样一个场景,我们总是希望通过指定user查询其喜爱的item,同时要获取item title信息。在当前模型下,我们首先要通过Item_By_User获取指定user关联的item id,然后根据这些item id依次查询Item模型获取title信息,反之亦然。一个item有可能被几百个user所喜爱,或者一个活跃user可能喜爱许多item,基于当前的模型设计,将会导致很多额外的查询。所以,最好通过反范式‘Item_by_User’ 中的itemtitle和’ User_by_Item’中的username信息来优化查询,方案3将会向大家展示。

注意:即使你可以批量读取(译者注:在Cassandra Java客户端hector中可以MultigetSliceQuery类实现一次查询传入多个rowkey),但它们将仍然很慢,因为Cassandra底层仍然会单独查询每个rowkey,然后通过Coordinator 节点(译者注:Coordinator 节点为Cassandra客户端直接请求的节点,可以理解为它是一个代理)汇总到客户端。批量读取可以避免请求的往返耗时,它是个不错的选择,你可以去尝试它。

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
16#
 楼主| 发表于 2013-11-1 09:16 | 只看该作者
方案三:范式化实体,并将它们反范式化到自定义索引

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
17#
 楼主| 发表于 2013-11-1 09:16 | 只看该作者
在这个模型中,title和username被分别反范式到User_By_Item和Item_By_User。这样将允许我们高效查询指定user喜爱的所有item,以及喜爱指定item所有的user。这样我们就为整个用例做了很大的反范式化工作。

问题又来了,如何获取指定user喜爱item的具体信息(title,desc,price等等)?首先我们要问问自己我们是否真的需要这个查询。还是上面的例子,当用户希望获取item额外信息的时候,我们可以在页面上展示所有的item title,当点击item title时,在打开的新页面显示这个item的具体信息。所以,在这个用例中我们最好不要极端反范式化。(item title列表中通常还会显示title和price信息,这也很容易实现,这个就留给大家做练习)

让我们考虑下面两个查询:

1) 通过所给item id,获取具体item信息(title, desc等等),并一同查询喜欢这个item的user name

2) 通过所给的user id,获取具体user信息,并一同查询user喜欢的所有item titile

上面两个查询出现在查询item和user的详情页面是很正常的,这些在当前模型中可以很好的实现。两者都需要两次查询,一次查询item(或者user)信息,另一次查询user name(或者item title)。User变得更加活跃的(喜欢上千个items)或者item变得很热门(被几百万user喜爱),查询的次数不会随之增加,仍然为两次。这很好,当我们从方案2到方案3,反范式化并没有让我们变糟糕。让我们看看方案4如何做更进一步的优化。

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
18#
 楼主| 发表于 2013-11-1 09:17 | 只看该作者
方案4:范式化部分实体

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
19#
 楼主| 发表于 2013-11-3 00:54 | 只看该作者
很明显,方案4看起来有些凌乱。在数据存储结构上,它与方案3也不同。

如果User和Item之间是高度关联的实体(类似ebay),相比当前方案我将更倾向于方案3。

因为我们不打算反范式化所有item属性到User实体或者反范式化所有user属性到Item实体,所以这里我们使用了部分范式化。我不会打算进行极限反范式化(让所有time属性到User实体和所有user属性到Item实体),因为在这个用例中那样做是没有意义的。

注意:这里我使用Super Column只是为了给展示。大多情况,应该倾向于使用composite columns,而不是Super Column。

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
20#
 楼主| 发表于 2013-11-3 00:55 | 只看该作者
最佳模型

在本文的用例中方案3是优胜者。上面的方案中我们忽略了timestamp信息,下面我们将把它以timeuuid(type-1 uuid)形式添加到最终模型上。注意,在User_By_Item实体中timeuuid和userid合并为一个composite column key,在Item_By_user实体中timeuuid和item id合并为一个composite column key。

回想一下,column key是有序存储的。这里我们的User_By_Item 和 Item_By_User两个实体的column keys通过timeuid排序后被存储到磁盘,这使得基于时间的范围查询非常高效。在这个模型中,我们不需要读取一个row中所有column,就可以高效的查询某个item最近被哪些user所喜爱,以及某个用户最近喜欢了哪些item。

最终模型如下:

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表