一、内容社区的数据挑战

内容社区产品的核心业务包括动态发布、圈子生态、问答悬赏等功能。这些功能背后有一个共同特点:读多写少,热点集中。一篇热门动态可能被数十万用户浏览,而这条数据在数据库里只有一条记录;一条评论发布后,会被成千上万次阅读。如果每次查询都直接打到MySQL,数据库很快会成为瓶颈。本文将拆解友猫社区系统(一个开源的垂直内容社区项目)在数据库设计和缓存架构上的解决方案。

二、核心功能模块与数据特征

在深入数据库之前,先明确三个核心功能的数据特征:

动态发布:支持图文、短视频、长图文。数据特征是写一次、读多次,热门动态的读请求可能达到每秒数千次。

圈子生态:用户与圈子的隶属关系,以及圈内动态的聚合查询。查询模式是“查某个圈子的动态列表”,需要按圈子ID和时间排序。

问答悬赏:涉及积分账户的增减操作,对事务一致性要求高,需要保证悬赏积分从提问者到回答者的原子性转移。

三、核心表结构设计

1. 用户表与动态表

用户ID采用雪花算法生成,避免自增ID在分表场景下的冲突问题。手机号作为唯一索引,用于登录快速校验。密码使用BCrypt加密存储。

动态表建表语句:

CREATE TABLE `post` (
  `id` bigint NOT NULL,
  `user_id` bigint NOT NULL,
  `content_type` tinyint DEFAULT '1',
  `status` tinyint DEFAULT '0',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_user_status` (`user_id`, `status`),
  KEY `idx_create_time` (`create_time`)
);

2. 圈子表与成员关系表

圈子表存储圈主ID、名称、公告、类型(普通/推荐/热门/付费)、入圈价格。成员关系表使用(user_id, circle_id)联合主键防止重复加入,同时在这两个字段上分别建立索引,支持“用户加入了哪些圈子”和“圈子有哪些成员”两种查询方向。

3. 问答表与悬赏记录

问题表存储提问者ID、悬赏积分、状态。答案表存储回答者ID、内容、采纳状态。悬赏积分的转移必须在事务中完成——更新提问者积分(减)、更新回答者积分(加)、更新问题状态、记录积分流水,四个操作要么全部成功要么全部回滚。

四、评论分表与冷热分离

1. 评论表的分表策略

评论表的写入频率远高于读取频率。按post_id取模分成16张物理表,分表键选择动态ID而非评论ID,因为查询评论时总是“根据动态ID查评论列表”。分表后单表数据量控制在500万以内。

分表路由封装在MyBatis-Plus拦截器中,应用层无感知。

分表路由核心方法:

public String getCommentTableName(Long postId) {
    long shardKey = postId % 16;
    return "comment_" + shardKey;
}

2. 动态表冷热分离

动态表中只有最近30天的内容需要频繁访问。通过Quartz定时任务,每天凌晨将30天前的动态迁移到post_archive历史表中,原表只保留热数据。被迁移的动态若再次被访问,异步回迁到热表。这套方案使主表行数保持在可控范围。

五、Redis缓存架构设计

1. 三层缓存策略

Redis缓存三类数据:用户信息(String结构,user:info:{userId},过期1小时)、热门动态列表(ZSet结构,Score为热度值)、点赞状态(Set结构,post:like:{postId})。

点赞状态用Redis Set存储,判断是否已点赞只需执行SISMEMBER命令,O(1)时间复杂度,避免查询MySQL互动表。

点赞状态判断逻辑:

public boolean hasLiked(Long userId, Long postId) {
    String key = "post:like:" + postId;
    return redisTemplate.opsForSet().isMember(key, userId.toString());
}

2. 缓存穿透与雪崩防护

缓存穿透是指查询一个不存在的数据,请求直接打到数据库。解决方案:对查询结果为空的Key也缓存一个空值并设置5分钟过期。

缓存雪崩是指大量Key同时过期。解决方案:为不同Key设置随机过期时间(在原过期时间上加随机分钟数)。

3. 缓存与DB的一致性

采用先更新数据库,再删除缓存的策略。更新用户信息时先执行MySQL的update,成功后删除Redis中的用户信息缓存。下次读取时缓存未命中,再从数据库加载最新数据。

对于点赞这类允许短暂不一致的场景,采用异步落库——点赞只写Redis,定时任务每5分钟批量同步到MySQL。

六、慢SQL治理与索引优化

Druid连接池开启了SQL监控功能,在管理后台可以查看每条SQL的执行次数和耗时。执行超过1秒的SQL自动记录到慢SQL日志表。

通过监控发现,初期未加索引的select * from post where user_id = ? and status = 1耗时超过800ms,添加联合索引idx_user_status后降至15ms。每条核心查询在上线前都经过EXPLAIN分析。

七、总结

这套数据库与缓存架构已在生产环境稳定运行,核心经验可总结为三点:

  • 分表提前规划:评论等高频写入表预估数据量,提前设计分表键和路由规则

  • 缓存分层:Redis扛热点读请求,DB专注于写和冷数据读

  • 冷热分离:30天前数据迁移历史表,控制主表规模

友猫社区在市面上已有成熟的开源https://www.chongyou.info/1/product/tm.html

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐