高并发系统设计 --基于MySQL构建评论系统

如何用MySQL来实现评论系统

为什么我不用mongodb?

  1. 社区成熟度不如MySQL,redis
  2. 需要学习的东西很多,迁移,扩容,片建,集群
  3. redis > mongodb

架构设计

使用MySQL进行存储的话,就必须要用到Redis来做缓存,后台admin需要接通ES来进行查询,comment-service通过异步来进行写Redis和MySQL评论数据,MySQL和ES通过Canal进行binlog同步。

缓存模式

首先我们需要预读,我们读第一页的时候,也需要把第二页的内容加载出来。读第二页的时候,我们预先读第三页,这样可以避免大量的cache miss。

但是这里有一个致命的问题就是:当缓存抖动的时候,会触发大量的cache rebuild,因为我们使用了预加载,容易造成OOM(内存溢出)。因此我们需要使用消息队列来进行逻辑异步化,对于当前请求,只返回MySQL中的部分数据即可。

写的逻辑

至于写的操作,我们要穿透到存储层,因此最好使用消息队列异步削峰。例如我的评论发布出去了,用户过100ms才看到评论,这是无所谓的。

存储设计

comment_subject表

id int 主键
obj_id int 对象id
obj_type int 对象类型
member_id int 作者id
count int 评论总数
root_count int 跟评总数
all_count int 评论+回复总数
state int 状态 0:正常 1:隐藏
attrs int 属性 0:置顶 1:不置顶
create_time+update_time datatime 创建时间,修改时间

obj_id+obj_type把评论系统设计成中台。 一般是指搭建一个灵活快速应对变化的架构,快速实现前端提的需求,避免重复建设,达到提高工作效率目的。

obj_id+obj_type形成了一个业务键,比如微博,发帖,发视频,你可以发视频,你也可以发文章,评论系统设计成中台。

comment_index

id int 主键id
obj_id int 对象id
obj_type int 对象类型
member_id int 发表者id
root int 根评论id,不为0是回复评论
parent int 父评论id,为0是root评论
floor int 评论楼层
count int 评论总数
root_count int 根评论总数
like int 点赞数
hate int 点踩数
state int 状态,0:正常;1:隐藏
attrs int 属性
create_time datetime 创建时间
update_time datetime 修改时间

parent:父评论id,其实就是记录是否是回复评论。

comment_content表:

comment_id int 主键
at_member_ids varchar 对象id
ip int 对象类型
platform int 发表者id
device varchar 跟评论id,不为0是回复评论
message varchar 评论内容
meta varchar 评论元数据:背景,字体
create_time datetime 创建时间
update_time datetime 修改时间

index是索引表,content是内容表。

数据写入:事务更新comment_subjectcomment_indexcomment_content三张表,其中content是非强制性需要一致性考虑的。因此可以先写入content,之后事务更新其他表。即便content更新成功,后续失败仅仅存在一条ghost数据。

数据读取:基于obj_id + obj_typecomment_index表找到评论列表,where root=0 order by floor。之后根据comment_index的id字段捞出comment_content的评论内容。对于二级的子楼层,where parent/root in(id...)

为什么要把index和content分成两个表

comment_index:评论楼层的索引表,实际并不包含内容。comment_content:评论内容的表,包含评论的具体内容。其中comment_index的id字段和comment_content是1对1的关系,这里面包含了几种设计思想。

  • 表都有主键,comment_content没有id,是为了减少一次二级索引查找,直接基于主键检索,同时comment_id在写入要尽可能的顺序自增。
  • 索引,内容分离,方便mysql_datapage缓存更多的row,如果和content耦合,会导致更大的IO。长远来看content信息可以直接使用KV storage存储。

缓存设计

comment_subject_cache【string】

key string oid_type
value int subject marshal string
expire duration 24h

comment_index_cache【sorted set】

key string cache key:oid_type_sort其中sort为排序方式,0:楼层,1:回复数量
member int comment_id:评论id
score double 楼层号,回复数量,排序得分
expire duration 8h

comment_content_cache

key string comment_id
value int content
expire duration 24h

comment_subject_cache:对应主题的缓存,value使用protobuf序列化的方式存入,这样调用rpc的时候速度可以更块一点。

comment_index_cache:使用redis sorted set进行索引的缓存,索引即数据的组织顺序,而非数据内容。通过预加载少量数据,通过增量加载的方式逐渐预热填充缓存,而redis sorted set skiplist的实现可以做到O(logN) + O(M)的时间复杂度,效率很高

sorted set是要增量追加的,因此必须判定key存在,才能zadd

comment_content_cache:对应评论内容数据,使用protobuf序列化的方式存入。

增量加载(目标表仅更新源数据表中变化的内容)+lazy加载(延迟加载,种将资源标识为非阻塞(非关键)资源并仅在需要时加载它们的策略)

可用性设计

其实就是各种缓存问题,消息队列消息等问题。

如果这个key是热点的key的话可以使用本地缓存+分布式缓存。

那么如何统计是否是热点呢?

这里,我们就移步到我们的下一篇文章了,谢谢大家。

你可能感兴趣的:(高并发系统设计,mysql,数据库,mongodb)