12.6 二级模式服务设计对于需要评论功能引发用户之间广泛互动的产品,大多采用的是二级模式评论,例如微博、bilibili等常见的亿级用户应用,所以本节将重点讨论二级模式评论服务的设计,同时引入之前尚未讨论的评论审核和按照热度排序的能力。
12.6.1 —级评论和二级评论在二级模式的评论功能中,所有对内容的评论都将作为一级评论,点击打开内容的评论区,会看到若干一级评论的集合。每条一级评论也都有自己的二级评论区,二级评论区由对此一级评论的回复和对回复的回复共同组成。二级评论区默认一般是折叠状态的,只有当用户主动点击打开某条一级评论的评论区时,其二级评论才会被展示出来。
在二级评论区中,对一级评论的回复和对回复的回复一般按照评论发布时间由远及近排序。而一级评论由于相互之间没有互动关系,所以既可以使用传统的按照评论发布时间对其进行排序,也可以使用更为个性化的排序方式来展示一些精彩的评论,比如微博评论区支持按照热度排序和按照时间排序两种规则,默认按照热度排序。所谓热度是一个比较笼统的概念,不同产品一般采用不同的热度定义,比如点赞数、回复数、发布时间等属性都会影响评论的热度(这个话题将在12.6 ...
12.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语 ...
12.4 评论服务设计的初步想法如果某产品的评论功能不在意用户的互动性,或者某评论区中很难有成千上万条评论 ,那么可以基于单级模式来设计评论服务,比如博客的留言、商品的吐槽区等场景。
12.4.1 数据表的初步设计在单级模式下,对内容本身的评论和对评论的回复处于同一层级,所以其数据表设计非常简单。假设数据表名为comment,表结构如表12-1所示。
如果一条评论是对内容的评论,则其数据记录中的reply_user_id和reply_comment_id字段的值都为0;而如果一条评论是对其他评论的回复,则这两个字段都有明确的值。此外,评论ID作为评论的唯一标识,需要在创建评论时使用分布式唯一ID生成器生成并设置到commented字段。
12.4.2 读/写接口与索引假设用户111在内容222的评论区中对内容本身发布了评论333,首先将评论文本以333为Key存储到分布式KV存储系统中,然后执行如下SQL语句保存评论元信息:
1INSERT INTO comment(content_idf comment_id, user_idz reply_user_id, commen ...
12.3 评论服务设计的初步想法一条评论数据至少包含评论文本、发布者、发布时间、评论对象(是对内容的评论 , 还是对其他评论的回复)、回复了哪个用户、评论的点赞数和点踩数等信息。其中,评论文本表示用户评论了什么,其他信息则属于评论元信息,这些信息可以说明评论是谁写的、评论的目标是什么,以及评论间的互动关系。
与第7章中的内容存储设计类似,评论数据被分为评论元信息和评论文本两部分。评论文本作为纯粹的发言内容,很适合被存储到分布式KV存储系统中,其中Key为评论ID,Value为评论文本数据;而评论元信息适合使用数据库存储,不过,在不同的评论列表模式下,数据表设计不同。接下来详细讨论应该如何设计各种评论列表模式下的数据表。
12.2 评论列表模式评论列表是评论服务最重要的场景,它的模式直接决定了应该怎样设计评论服务,所以我们先来讨论当下主流的几种评论列表模式。
对于评论列表模式,其实讨论的是对内容的评论,以及对评论回复的排列。
其中,第一种模式是“单级模式”,在这种模式下所有的评论都处于同一层级,而不管是对内容的评论,还是对其他评论的回复。一个简单的例子如图12-1所示。
这种模式看起来非常简单,评论按照时间顺序像“一问一答”一样排列,可以满足用户评论和回复的基本诉求。但是这种模式也会造成用户之间的互动被割裂开来,如图12-1所示的那样,我们并不能直观地看到这三个用户到底在相互交流什么内容,只能在列表中凭肉眼寻找。所以 ,这种模式仅适合那些社交互动不是那么重要的产品,比如博客、新闻、 视频网站、音乐平台等,或者评论区的评论量级注定不会很大的场景,比如微信朋友圈等偏向于小范围的互动圈子;而对于具有强社交属性,且以内容创建互动场合的产品来说,这种模式并不是很适合,因为它会造成评论区杂乱无章,比如微博、bilibili、知乎、小红书等。
第二种模式是“二级模式”,即将某内容下的评论划分为两级,将所有对内容的评 ...
评论是作为互联网用户的我们最耳熟能详的一个功能,几乎所有面向大众的互联网应用都提供了强大的评论功能。本章我们就来讨论评论服务的设计思想,下面先给出本章的学习路径。
12.1节介绍评论功能的重要性,以及其作为一个通用评论服务应具有的基本能力。
12.2节介绍目前主流的评论列表模式,包括单级模式、二级模式和盖楼模式。
12.3节介绍评论服务设计的初步想法。
12.4节介绍单级模式服务设计,包括存储选型和高并发设计。
12.5节介绍盖楼模式服务设计,合理地获取到完整的盖楼信息。
12.6节介绍二级模式服务设计。二级模式是最常用的评论列表模式,我们对此分别介绍了技术选型、评论审核、热门评论和高并发处理等,这也是本章的重点内容。
本章关键词:索引、盖楼评论、递归查询、图数据库、二级模式评论、热门评论、评论审核、多级缓存。
12.1 评论功能评论功能是当今强调用户的互联网应用中不可或缺的重要部分,它在多方面证明了其不可忽视的重要性。
增加社交互动:评论功能可以为社交产品带来更多的社交互动。用户可以在社交平台就不同的话题、所分享的信息、所发表的文章和其他用户的评论进行交流与互动,增强社交产品的 ...
11.6 实现Timeline Feed服务的关键技术细节前面我们已经介绍了拉模式、推模式的思想和优劣,并得出了两者结合的理论基础。不过,理论毕竟是空洞的,本章介绍的是如何实现Timeline Feed服务,所以必然要把理论转化为实践,我们需要讨论一些关键的技术细节。
11.6.1 内容与用户收件箱的交互在推模式下,当用户发布了一条内容后,系统需要把此内容推送到粉丝的收件箱中,那么如何实现呢?第7章已经介绍过,内容发布流程是由内容发布服务执行的,一种简单粗暴的方法是在内容发布服务的内容发布流程后增加一段逻辑:首先获取内容发布者的粉丝列表,然后从用户登录服务中获取这些粉丝的最近登录时间,从而筛选出活跃粉丝,最后依次遍历这些粉丝并请求Timeline Feed服务,将已发布的内容插入这些粉丝的收件箱中。
然而,这种做法并不可取,Timeline Feed逻辑被严重耦合到内容发布服务中,各个服务的职责边界遭到了破坏。除了这个缺陷,推送的可用性也比较差:内容发布服务的遍历推送行为毕竟发生在服务实例进程中,内容发布服务的升级、扩容或服务质量问题都可能导致实例进程退出,这就会打断正在进行的遍历推送 ...
11.5 推拉结合模式我们可以很清楚地看到,拉模式和推模式的优缺点是互补的:前者无存储压力,但是有读请求压力;后者无读请求压力,但是有写请求压力和存储压力。既然它们可以互补,那么我们的思路就是将两者结合起来,即形成推拉结合模式。
11.5.1 结合思路虽然拉模式的优势是简单、无存储压力,但是拥有海量用户的互联网公司会在意它的优势吗?这些公司既有足够的研发投入,又有充足的存储资源,其更在意的是用户体验,在拉模式下,用户获取Feed流的性能表现是其最无法接受的。因此,我们应该重点分析推模式。采用推模式可以有效地提高用户获取Feed流的性能,但是用户在发布内容时,则会带来存储压力和写请求压力,而且用户的粉丝数越多,这两方面的压力越大。
那么,推模式和拉模式如何结合呢?关键的突破口在于一条内容的发布者有多少粉丝 ,即与第10章介绍的一样,需要根据用户是否是粉丝数超过某个阈值的大V来进行策略选择:当用户发布内容时,如果此用户不是大V,则采用推模式把内容发送到粉丝的收件箱中,这样一来,即使推模式会带来存储压力大和写扩散的问题,也会由于粉丝数较少而使影响可控;当用户获取Timeline Feed流时 ...
11.4 推模式与用户收件箱另一种实现Timeline Feed服务的方式是推模式。与拉模式相反,在推模式下,每个用户都有一个“收件箱”,当某用户成功发布了内容时,系统会将该内容推送到其每个粉丝用户的收件箱中;粉丝用户在获取Feed流时,直接从收件箱中读取内容即可,如图11-2所示。
在推模式下,用户获取Feed流的性能比在拉模式下好,系统不需要获取用户的关注列表,也不需要遍历拉取每个关注者的内容列表。但是推模式也有如下一些核心缺点。
存储压力大:在推模式下,要求每个用户都有收件箱,这势必要为收件箱引入存储系统,用户越多,收件箱占用的存储资源就越多。比如某用户有100万个粉丝,该用户发布了一条内容后,在这100万个粉丝的收件箱中都要存储这条内容,这里消耗了大量的存储空间。
写扩散:某用户发布了一条内容后,需要将此内容推送到100万个粉丝的收件箱中,也就是系统内部会产生100万个写请求,这里产生了粉丝数倍数的写请求放大,如此巨量的写请求可能会击垮收件箱所依赖的数据库。
11.3 拉模式与用户发件箱一种较为符合我们直觉的实现Timeline Feed服务的方式是拉模式。每个内容发布者都有自己的“发件箱”,每当用户发布一个内容时,就把内容存放到发件箱中,由其他用户来拉取内容。
如图11-1所示,当用户获取Timeline Feed流时,系统会先拉取此用户的关注列表,然后遍历每个关注者的发件箱,取出他们发布的全部内容,最后根据发布时间倒序排列后展示给用户。
所谓的发件箱只是一种使描述更形象的代称,我们不需要专门设计发件箱,它实际上就是第7章讲的内容发布系统(服务)所负责存储的用户内容列表,所以拉模式就是从内容发布服务中拉取关注者的内容列表。可见,在拉模式下不需要为Timeline Feed服务单独设计存储系统,实现非常简单。因此,当一个互联网应用在初期阶段用户规模较小时,很适合使用这种方式来实现Timeline Feed流的功能。
不过 ,在拉模式下,用户每刷新一次Feed流 ,系统就需要读取N个用户的发件箱(这里的N是指用户关注的人数),这意味着一次用户请求会放大产生N倍的读请求,故而这种模式也被称为读扩散。如果用户量级较大,那么获取Feed流会是一个 ...