1. 项目背景与核心需求
私信系统作为社交平台的核心功能模块,其可靠性直接影响用户体验。传统方案常面临三个典型问题:消息丢失(尤其在高峰期)、已读状态不同步、历史消息加载缓慢。我们设计的这套系统需要同时解决这三个痛点,并满足日均千万级消息的处理需求。
技术选型上采用SpringBoot作为基础框架,配合RabbitMQ实现消息削峰,Redis处理实时状态同步,MySQL持久化存储。这套组合在保证功能完整性的同时,能够灵活应对流量波动。实测在模拟2000TPS的压力下,消息投递成功率保持在99.99%以上。
关键设计原则:最终一致性优先于强一致性,允许毫秒级的状态延迟,但必须确保数据不丢失。这对社交场景是完全可接受的。
2. 系统架构设计
2.1 整体数据流向
发送端流程:
- 客户端调用API提交消息
- SpringBoot服务验证后写入MySQL消息表(状态为"发送中")
- 同步推送至RabbitMQ
- 消费者服务将消息写入接收方Redis收件箱
- 更新MySQL状态为"已送达"
已读状态同步流程:
- 接收方打开会话时触发已读事件
- 直接写入Redis的已读状态哈希表
- 异步任务每5分钟批量同步至MySQL
2.2 核心组件交互图
plaintext复制Client -> SpringBoot: HTTP请求
SpringBoot -> MySQL: 消息持久化 (1)
SpringBoot -> RabbitMQ: 消息投递 (2)
RabbitMQ -> Consumer: 消息消费 (3)
Consumer -> Redis: 收件箱更新 (4)
Redis <- SpringBoot: 状态查询
3. 关键实现细节
3.1 消息可靠投递保障
采用双重确认机制:
- 生产者确认模式:配置publisher-confirms确保消息到达RabbitMQ
- 消费者手动ACK:只有成功写入Redis后才确认消费
java复制// RabbitMQ配置示例
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setConfirmCallback((correlationData, ack, cause) -> {
if (!ack) {
// 记录到补偿表
messageCompensator.recordFailure(correlationData.getId());
}
});
return template;
}
3.2 Redis数据结构设计
使用三组核心结构:
- 收件箱:Sorted Set存储消息,score使用时间戳
redis复制ZADD inbox:user1 1625097600000 "msg1_content" - 已读状态:Hash记录最后已读消息ID
redis复制HSET read_status user1:user2 "msg123" - 消息缓存:String存储完整消息体
redis复制SET msg:msg123 "{sender:user2, content:...}"
3.3 MySQL表结构优化
消息主表采用分库分表策略,按用户ID哈希分片。关键字段包括:
sql复制CREATE TABLE `message_%` (
`msg_id` BIGINT PRIMARY KEY,
`sender_id` BIGINT NOT NULL,
`receiver_id` BIGINT NOT NULL,
`content` TEXT,
`status` TINYINT COMMENT '0-发送中 1-已送达 2-已读',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_receiver_status` (`receiver_id`, `status`),
INDEX `idx_conversation` (`sender_id`, `receiver_id`)
) ENGINE=InnoDB;
4. 性能优化实践
4.1 写操作优化
- 批量已读状态同步:累积多个已读事件后一次性更新MySQL
- 消息异步持久化:非关键路径先写Redis再异步落库
- 连接池配置:
yaml复制spring.redis.lettuce.pool.max-active=200 spring.datasource.hikari.maximum-pool-size=100
4.2 读操作优化
- 多级缓存策略:
- 第一层:客户端本地缓存最近50条
- 第二层:Redis缓存最近7天消息
- 第三层:MySQL历史归档
- 热点数据预加载:用户登录时预取最近会话数据
5. 异常处理方案
5.1 消息补偿机制
设计补偿任务表定时扫描:
sql复制CREATE TABLE `message_compensation` (
`id` BIGINT AUTO_INCREMENT,
`msg_id` VARCHAR(64),
`retry_count` INT DEFAULT 0,
`next_retry_time` DATETIME,
PRIMARY KEY (`id`)
);
补偿策略采用指数退避算法:
java复制// 伪代码
if(retryCount < 3) {
delay = 2^retryCount * 1000; // 1s, 2s, 4s
} else {
alertAdmin();
}
5.2 雪崩防护
- Redis集群部署,不同业务使用独立分片
- MySQL读写分离,报表查询走从库
- 熔断配置:
java复制@CircuitBreaker(failureRateThreshold=30%, delay=5000) public List<Message> getHistory(Long userId) { // ... }
6. 实测数据对比
压测环境:8C16G服务器 × 3,千兆内网
| 场景 | QPS | 平均延迟 | 99分位延迟 |
|---|---|---|---|
| 纯MySQL方案 | 1,200 | 85ms | 210ms |
| 本方案 | 8,500 | 12ms | 38ms |
| 峰值处理能力 | 15,000 | 22ms | 65ms |
历史消息加载耗时对比:
- 首屏渲染:从1200ms降至280ms
- 翻页查询:从800ms降至150ms
7. 踩坑经验
-
Redis大Key问题:
- 错误做法:单个用户收件箱无限增长
- 解决方案:按时间分片,自动归档旧消息
-
消息顺序错乱:
- 根源:RabbitMQ多消费者并发处理
- 解决:单个会话固定到同队列,相同接收者消息路由到同一队列
-
已读状态抖动:
- 现象:快速滑动界面导致状态反复更新
- 优化:增加200ms防抖延迟
这套系统上线后稳定运行9个月,日均处理消息1.2亿条。最大的收获是认识到异步架构中监控的重要性——我们建立了从客户端到数据库的全链路追踪,任何环节的异常都能在30秒内触发告警