第12章评论服务——12.5 盖楼模式服务设计

第12章评论服务——12.5 盖楼模式服务设计
John Yaml12.5 盖楼模式服务设计
盖楼模式的最大特点是可以展示每条评论的完整楼层,楼层由初始评论和回复组成。假设在一条内容下,用户1发布了评论,用户2回复了用户1,用户3回复了用户2…… 用户100回复了用户99,那么对于用户100的回复来说,用户1-用户100的评论组成了一个层数为100的楼层——不仅展示了用户100的评论,而且展示了用户1-用户99的完整回复链路。所以,在盖楼模式下,通过每条评论都能回溯到完整的回复链路,以组成楼层。
12.5.1 数据库方案:递归查询
回顾单级模式的content_comment数据表设计,comment_id字段和reply_comment_id字段记录了一条评论与另一条评论的回复关系。如果要展示一条评论的盖楼情况,则需要从此评论的记录开始,根据reply_comment_id字段不断递归查询上一层的评论记录,直到遍历到此字段为0的评论记录才停止,此时表示已查询到顶层评论。在递归过程中,查询到的每条评论自底向上组成楼层,其过程大致如图12-6所示。
数据库一般都支持这种递归查询方式,仍以MySQL为例,我们可以通过创建自定义函数、编写带变量的复杂SQL语句,或者使用高版本支持的WITH RECURSIVE语句来实现递归查询。假设content_comment数据表中有表12-2所示的数据。
可以看到,评论99是一个层数为9的楼层回复。为了展示完整的楼层,需要从评论99开始递归查询所有的上一层评论。我们可以执行如下SQL语句来实现:
1 | SELECT ID.level, DATA.* FROM ( |
上述语句的执行结果如下:
另一种递归查询方式是使用WITH RECURSIVE语句,它是MySQL 8.0版本中引入的新功能。WITH RECURSIVE是一个面向树形结构的递归查询语句,相较于传统的SQL查询语句,它能够帮助用户查询具有层级依存关系的数据结构,在一定程度上提高了数据查询效率。例如,展示评论99的完整楼层的语句如下:
1 | WITH RECURSIVE temp AS ( |
MySQL使用WITH RECURSIVE语句进行递归查询,虽然性能相对较好,但是递归操作毕竟是一个深度遍历的过程,如果一条盖楼评论的楼层过高,则意味着递归查询的深度大,于是需要循环查询同一个数据表的次数就增加了,时间和内存的开销也会增加,进而影响查询效率。总的来说,基于comment_id和reply_comment_id字段,递归查询评论的完整回复链路的时间复杂度至少为O(N),评论的楼层越高,查询效率越低。
12.5.2 数据库方案:保存完整楼层
为了避免递归查询评论的完整回复链路,我们还可以在content_comment数据表中增加一个building字段,设置此字段的类型为Blob,用于存储各楼层的评论ID列表。比如评论88的各层盖楼评论ID列表为[11,22,33,44,55,66,77],我们先把此列表序列化为字节流,然后压缩成占用空间更小的二进制数据,最后存储到building字段中。如果评论99是对评论88继续盖楼,则先读取评论88的 building字段,解压缩、反序列化得到盖楼评论ID列表,然后把评论88加入列表中,并将此列表作为评论99的各楼层评论ID列表,即[11,22,33,44,55,66,77,88],最后将其序列化、压缩存储到评论99的building字段中。
之所以选择Blob类型,是因为此类型适合保存占用空间较大的二进制数据,而压缩后的评论ID列表正好是二进制数据,而且楼层越高,压缩后的二进制数据越大。通过在content_comment数据表中增加Blob类型的字段,可以让我们只访问一条数据记录就能获取到评论完整楼层,完全避免了递归查询情况。
Blob类型的数据被存储在MySQL数据表的一个单独的数据页中,数据记录仅存储指向这个数据页的指针,这就使得数据库可以更快地访问到这些数据。但是相比于没有Blob类型数据的数据表来说,Blob类型数据的存在会使得数据表的读/写效率降低,而且Blob类型的数据越大,对数据库的数据传输、数据查询的负面影响就越大。所以,Blob类型并不是万能的。如果产品的评论功能不限制最多盖楼层数,那么可能会有数万层的“摩天大楼”级盖楼评论出现,使用Blob类型存储这种数据依然会给数据表带来巨大压力。实际上,这种方案更适合天然限制了最多盖楼层数(比如最多让盖1000层),几乎只存在“小矮楼”式盖楼评论的场景。
12.5.3 图数据库方案
既然关系型数据库不适合存储较长的评论回复链路,那么我们选择适合做这件事情的图数据库,将评论间的回复关系存储到图数据库中。假设有一些评论并且创建了评论间的回复关系:
1 | // 创建评论节点,节点属性包括commented (评论工D)和user_id (评论发布者工D) |
此时评论的回复链路如图12-7所示。
如果要展示评论88的盖楼情况,则只需要执行如下CQL语句即可:
1 | MATCH (c:Comment{comment__id:88})-[r*..](building) return building |
这条SQL语句会对comment_id为88的评论节点按照回复关系进行深度遍历,遍历经过的每个节点都被作为上一个商层形成盖楼效果。此语句的执行结果形成的图如图12-8所示。
评论间的回复关系本质上就是一个树形结构,所谓盖楼评论无非就是在此树形结构中对某个评论节点进行深度遍历,所以在盖楼模式下非常适合使用图数据库。