一、背景
最近一朋友做社区重构,社区主要功能有发帖、回帖、查看帖子详情,详情页按不同条件展示回帖(除了预先定义的顺序外,可能每个用户看到的顺序都不一样,组合超过100个),大概的效果如下:
以前用的是开源的代码,存储用的是Mysql,系统也过于臃肿,稍微有点流量系统响应就很慢,所以准备重构。
重构后的方案如下
1、存储还是Mysql;
2、为了提高访问速度,引入MongoDB作为缓存(为什么不用Redis,因为MongoDB多线程,可扩充性好,并且支持较复杂的查询)
Mysql数据表大概如下:
1、帖子表
字段名 | 类型 | 字段说明 |
---|---|---|
id | int | 主键 |
user_id | int | 发帖用户id |
title | varchar(500) | 帖子标题 |
content | text | 帖子内容 |
2、帖子回复表
字段名 | 类型 | 字段说明 |
---|---|---|
id | int | 主键 |
tid | int | 帖子id |
content | text | 回复内容 |
position | int | 楼层 |
上述表格是经过简化版的内容。
存储方面,Mysql存了全量的帖子和帖子回复,MongoDB也存了全量的帖子和帖子回复,之所以这么设计是因为让用户帖子详情页不用访问数据库,提高访问速度。
那为什么只保存在MongoDB里呢,因为MongoDB不支持多表事务,社区的场景插入回复,还有其它逻辑需要处理,所以需要借助Mysql的InnoDB的事务机制保证数据的一致性。
重构后访问帖子详情页顺序如下:
1、根据帖子id从MongoDB获取帖子详情信息,包括标题、内容及发帖时间和发帖人,如果读取不到,直接报错;
2、根据帖子id及当前条件从MongoDB获取帖子回复信息,同样读取不到也报错。
为什么不按分页将每个帖子按页缓存回复呢,因为前面说了整个详情页展示条件非常复杂,可以倒序排,也可升序排,还可以只看作者,有的回复还有权限,如果全部缓存帖子回复列表,则缓存的数据量非常的大。
二、问题分析
经过分析,这样的设计带来几个问题:
1、系统设计比较复杂,因为要保证数据在Mysql、MongoDB中一致,需要做很多的代码进行数据核对、检查;
2、系统可用性差,因为帖子详情页全部读取的是MongoDB,所以如果MongoDB挂了,则整个系统也就挂了,特别是MongoDB对于运维团队还不是特别熟悉的情况下。
有什么更好的方案呢,回到缓存的本质,关于缓存的使用有不少模式,一般来说对缓存不要强依赖,即缓存挂了,整个系统不要挂,让系统打到后端存储并且更新缓存,这样还有最后一道防线,而在这个案例中,将MongoDB当存储用了,并且同时使用两个存储。
如果当缓存用,怎么解决帖子详情页多种组合条件的导致缓存数据太大的问题?其实对于社区这样的场景,主要占内存的是回复的内容,只要解决帖子回复内容只缓存一份就可以了。
改进后帖子详情页逻辑如下:
1、根据帖子id从 MongoDB中获取帖子详情信息,如果获取不到,则从Mysql中获取,并且写回到MongoDB中;
2、根据帖子id从MongoDB中获取当页需要展示的帖子回复id,读取不到再从Mysql回源,并写回到MongoDB中;根据上面获取的回复id再从MongoDB中获取回复的详情,同样如果获取不到则从Mysql回源,并且写入到MongoDB中。
当然在添加、更新回复后,也需要更新相应的回复内容,这样就保证了帖子回复只缓存一份,不会造成缓存的数据量过大的问题。还有非常重要的一点,整个系统没有对缓存强依赖,即使MongoDB挂了,系统还会从Mysql读取数据。最后系统的代码也变得非常简洁。
当然这里还有很多细节需要注意,像如何避免同一时间大量的回源Mysql的问题,这些业内已经有标准的方案,就不在此展开讨论了。
三、案例总结
1、系统设计越简单越好;
2、不要强依赖缓存;