实际上,90%的app采用简单的评论设计就能够了,也就是采用一问一答
,相似于以下的设计。mysql
这种设计十分简单、直接,也知足了用户评论、回复的基本要求,对于没有大量用户评论或者评论不是核心功能的app来讲就够用了。暂且把这种场景称之为场景A
。sql
若是你是新闻类或者咨询类的app,有着大量的用户评论,那么设计“盖楼”的效果仍是可取的,这样能帮助用户找到该条评论或者回复的上下文情景。可是根据“盖楼”的显示效果不一样,设计上也是有很大的差异的。若是是以评论为主
的显示方式,相似于下面的显示方式。数据库
这里能够把评论分为评论
和回复
,全部的回复
均挂在评论
下面,相似于树状结构。把这种场景称之为场景B
json
最后就是相似于网易新闻的评论设计了,贴一张截图后端
这种场景下设计最为复杂,由于回复和评论是同等级的,回复还能够引用完整的回复路径,就是能够溯源到最开始的评论上。这种场景我将至称为场景C
。缓存
数据库设计
因为我 一直使用mysql
,我就以mysql
为例谈一下针对上面三种场景的设计。微信
场景A
这种场景下通常评论数量较少,评论不为活跃,能够把不区分评论和回复,而统一当作评论。区别在于有的评论是直接评论主题
(每一个评论都挂在某个主题下,如文章、帖子等),而有些评论是@
其余用户的,为了能cover这两张场景,使用一张表就能够达到效果,评论表以下设计:数据结构
表字段 | 字段说明 |
---|---|
id | 主键 |
topic_id | 主题ID |
topic_type | 主题type |
content | 评论内容 |
from_uid | 评论用户id |
to_uid | 评论目标用户id |
为了能复用评论模块,咱们引入一个topic_type
字段来区分主题的类别。闭包
from_uid
表示评论人的id,经过该id咱们能够检索到评论人的相关信息。架构
to_uid
是评论目标人的id,若是没有目标人,则该字段为空。
出于性能的考虑,每每咱们会冗余评人的相关信息到评论表中,好比评论人的nick、头像等,目标用户也是如此。这样一来咱们就只用查询单表就能够达到显示的效果。
有时,目标用户有多个,那么能够将to_uid
字段修改成to_uids
,保存时用分隔符来分割用户id,而目标用户的信息再去查询缓存或者数据库。也能够简单的将多个目标用户的信息一块儿存成json格式,能够应付简单的展示需求。
场景B
在以评论为主的树形显示的状况下,数据库的设计十分灵活,可使用单表,添加一个parent_id
字段来指向父评论。若是数据库自己支持嵌套查询,那么仍是比较方便的,SqlServer、Oracle都支持,可是mysql不支持,那就只能经过存储过程来实现。在互联网应用中,能不使用触发器
`存储过程`的话,尽可能不要去使用,由于其对性能有影响。
咱们还能够将评论拆分为评论表
和 回复表
,评论
挂在各类主题
下面,而回复
都挂在评论
下面。
评论表
的设计以下:
表字段 | 字段说明 |
---|---|
id | 主键 |
topic_id | 主题ID |
topic_type | 主题type |
content | 评论内容 |
from_uid | 评论用户id |
回复表
的设计以下:
表字段 | 字段说明 |
---|---|
id | 主键 |
comment_id | 评论ID |
reply_id | 回复目标id |
reply_type | 回复类型 |
content | 回复内容 |
from_uid | 回复用户id |
to_uid | 目标用户id |
因为咱们拆分了评论和回复,那么评论表就再也不须要目标用户字段了,由于评论均是用户对主题的评论,评论表的设计更佳简洁了。
回复表我添加了一个comment_id
字段来表示该回复挂在的根评论id,这样设计也是出于性能方面的考虑,咱们能够直接经过评论id一次性的捞出该评论下的全部回复,而后经过程序来编排回复的显示结构。经过适当的冗余来提升性能也是经常使用的优化手段之一。
reply_type
表示回复的类型,由于回复能够是针对评论的回复(comment),也能够是针对回复的回复(reply), 经过这个字段来区分两种情景。
reply_id
表示回复目标的id,若是reply_type是comment的话,那么reply_id=commit_id,若是reply_type是reply的话,这表示这条回复的父回复。
在数据结构的设计上,我在replyDTO中设计了一个List<ReplyDTO> next
属性,这样在造成了一个树形的结构,相似以下结构。
树形结构
客户端能够直接根据该结构来进行树形结构的显示。
场景c
要达到网易新闻中评论的效果我尚未特别好的建议。这种场景中评论和回复是同级显示的,回复不在显示结构上不用挂在一个评论下面。双表的设计在这里就不太合适了,由于涉及到评论和回复的混排,使用双表则会致使查询的逻辑过于复杂。因此建议仍是采用单表的设计,不区分评论和回复会简化应用层的逻辑。咱们统一都当作评论,而有些评论是能够引用其余评论的。本人推荐采用闭包表的设计,例如:
comment表设计
表字段 | 字段说明 |
---|---|
id | 主键 |
topic_id | 主题ID |
topic_type | 主题type |
content | 评论内容 |
from_uid | 评论用户id |
parent_children表
表字段 | 字段说明 |
---|---|
id | 主键 |
parent_id | 父id |
child_id | 子id |
comment表保存全部评论内容,而parent_children表则记录评论表中各个评论的父子关系。
查询时每每会按照时间排序,咱们能够直接按id或者建立时间降序排列查询comment表便可。若是用户想查询一条评论的完整引用,则能够经过parent_children来找到对应的路径。向上查找到评论只须要可执行:
select parent_id from parent_children where child_id=${id} and parent_id != ${id}
向下查找全部的子孙评论可执行:
select child_id from parent_children where parent_id = ${id} and parent_id != ${id}
闭包表在查询时很是方便,可是插入的性能稍差,由于除了插入评论表之外,还须要把该条评论全部的父子关系插入到父子关系表中。插入性能会随着评论层级的加深而线性降低。
海量数据优化
若是你的系统天天都会有成千上万条评论,那么单表的设计确定是不行,优化的方式也有不少。
分库分表。分库分表是最为经常使用也最有效的优化方式,建议按照主题来分库分表。这样同一个主题下面的评论就会落到同一张表里,避免了跨表查询。
适当的数据冗余。若是你须要显示评论人的相关信息,那么在插入评论时就把这些信息写入评论表中,避免屡次查询。实际上,若是是纪录数据,均可以冗余对应的数据信息,由于它们的数据的实时行和一致性要求并不高,用户不会由于评论中的头像没更新而撕了你,哈哈。
附加幂等数据只容许单项操做。若是pd要求你能给评论点赞,那么你能够告诉他只能点赞,不能取消。由于从幂等性的要求来讲,每一个赞都是一条记录。评论的赞数若是都从点赞表中统计得出,那么性能开销会十分巨大,并且点赞如此轻量级的一个操做必定会加重点赞表的竞争操做。因此建议直接在评论表中添加一个
like_count
的计数器,该字段只增不减。热门评论加缓存。相似于网易新闻的热门评论,读取频度很是高,能够专门开接口给客户端,同时该接口作缓存。
Java后端架构,关注获取更多技术分享
欢迎加入互联网后端架构
联系QQ:517894513
QQ群号码:217023887
本文分享自微信公众号 - 互联网后端架构(fullstack888)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。