我是如何巧妙打造高效群消息已读回执系统的?
- 内容介绍
- 文章标签
- 相关推荐
我是如何巧妙打造高效群消息已读回执系统的?
提起群消息的已读回执啊,搞过IM的朋友估计或多或少的遇到过一些弯路。特别是群里有几百号人甚至上千人的时候,要是还傻huhu地给每个人存一份消息副本? 我开心到飞起。 那简直就是给自己找罪受!前阵子用户量蹭蹭涨的时候,数据库直接扛不住了动不动就超时报警,搞得大家真的是头皮发麻。
问题到底出在哪儿?
这是可以说的吗? 简单粗暴,群里发一条消息,就给群里每个人存一条记录。查谁没读倒是挺快,但坏处也秃噜皮地往外冒。先说说就是存得太多太狠了你想想,一个500人的群发一条消息,啪!500条记录就怼进数据库了这谁受得了啊?赶上高峰期,群里消息嗖嗖地发,数据库写操作直接成了瓶颈,吭哧瘪肚的卡的跟PPT似的。人一多立刻就趴窝了群越大,这方案就越拉胯,根本撑不住。

| 方案类型 | 备注 |
|---|---|
| 传统方案 | 简单粗暴但性能差 |
| 我们的方案 | 优化后性能提升明显 |
技术选型考虑
在技术栈的选择上, 我们主要考虑了稳定性和开发效率:
┌─────────────────┬──────────────────────────────────
│ 组件 │ 选择理由
├─────────────────┼──────────────────────────────────
│ Redis │ 处理在线状态,性能出色
│ WebSocket │ 双向通信,实时性强
│ JPA + Hibernate │ ORM方便,减少SQL编写
└─────────────────┴──────────────────────────────────
说实话,选择这些技术主要还是考虑到团队的技术栈熟悉度。毕竟再好的方案,如果团队hold不住也是白搭。
数据库设计优化
数据库设计是整个方案的核心, 我们设计了三张表来支撑整个业务:,一言难尽。
CREATE TABLE group_msgs (
msgid BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '消息ID',
gid BIGINT NOT NULL COMMENT '群ID',
sender_uid BIGINT NOT NULL COMMENT '发送者用户ID',
time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间',
content TEXT COMMENT '消息内容',
msg_type INT NOT NULL DEFAULT 1 COMMENT '消息类型:1-文本 2-图片 3-语音',
status INT NOT NULL DEFAULT 1 COMMENT '消息状态:1-正常 0-已删除',
INDEX idx_group_time ,
INDEX idx_sender ,
INDEX idx_group_msgid
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这张表是核心,存储所有的群消息。我们特别注意了索引的设计:,我好了。
核心优化点
- 客户端批量ACK机制,这个优化效果最明显。客户端不再是收到消息就马上ACK, 而是攒够一定数量再批量提交,这个机制将原来的N次请求压缩到了N/10次效果立竿见影。
- 出哪些消息是未读的。
- 定时清理历史数据, 这个功能是运维同学强烈要求的,不然数据库迟早会爆,选择凌晨2点是主要原因是这个时间段用户最少,对业务影响最小。保留30天的数据基本能满足大部分业务需求。
| 测试场景 | 平均响应时间 | 99%响应时间 |
|---|---|---|
| 100人群 | 45ms | 78ms |
| 500人群 | 200ms | 300ms |
监控指标设计
这东西... 监控这块我们主要关注几个核心指标, 这些指标接入了我们的监控平台,一旦出现异常马上告警。
@Component
public class SystemMetrics {
private final MeterRegistry meterRegistry;
private final Counter messageCounter;
private final Timer ackProcessTime;
public SystemMetrics {
this.meterRegistry = meterRegistry;
this.messageCounter = Counter.builder
.description
.register;
this.ackProcessTime = Timer.builder
.description
.register;
}
}
未来规划
如果业务继续增长, 我们考虑这样分库分表:
1. 消息服务:负责消息存储、群成员管理和消息查询。
2. 回执服务:处理回执记录、批量确认和统计分析。
3. 推送服务:管理在线状态、消息推送和连接管理。
网关服务:作为各个服务之间的路由和负载均衡中心,负责流量管理和限流熔断。
不过目前单体架构还能hold住暂时没必要过度设计。
经过几个月的线上运行, 效果确实不错:
* 消息发送TPS稳定在500以上
* 平均响应时间控制在200ms以内
这套方案在我们的业务场景下运行得还不错,基本解决了大群聊的性能问题。当然任何技术方案都不是银弹,具体还是要根据自己的业务特点来调整。
如果你也在做类似的系统,希望这些经验能给你一些参考。有什么问题的话,欢迎一起交流讨论。
Redis在线状态管理, 用Redis管理在线状态比查数据库快多了过期时间来自动清理离线用户,避免了手动维护的麻烦。我是如何巧妙打造高效群消息已读回执系统的?
提起群消息的已读回执啊,搞过IM的朋友估计或多或少的遇到过一些弯路。特别是群里有几百号人甚至上千人的时候,要是还傻huhu地给每个人存一份消息副本? 我开心到飞起。 那简直就是给自己找罪受!前阵子用户量蹭蹭涨的时候,数据库直接扛不住了动不动就超时报警,搞得大家真的是头皮发麻。
问题到底出在哪儿?
这是可以说的吗? 简单粗暴,群里发一条消息,就给群里每个人存一条记录。查谁没读倒是挺快,但坏处也秃噜皮地往外冒。先说说就是存得太多太狠了你想想,一个500人的群发一条消息,啪!500条记录就怼进数据库了这谁受得了啊?赶上高峰期,群里消息嗖嗖地发,数据库写操作直接成了瓶颈,吭哧瘪肚的卡的跟PPT似的。人一多立刻就趴窝了群越大,这方案就越拉胯,根本撑不住。

| 方案类型 | 备注 |
|---|---|
| 传统方案 | 简单粗暴但性能差 |
| 我们的方案 | 优化后性能提升明显 |
技术选型考虑
在技术栈的选择上, 我们主要考虑了稳定性和开发效率:
┌─────────────────┬──────────────────────────────────
│ 组件 │ 选择理由
├─────────────────┼──────────────────────────────────
│ Redis │ 处理在线状态,性能出色
│ WebSocket │ 双向通信,实时性强
│ JPA + Hibernate │ ORM方便,减少SQL编写
└─────────────────┴──────────────────────────────────
说实话,选择这些技术主要还是考虑到团队的技术栈熟悉度。毕竟再好的方案,如果团队hold不住也是白搭。
数据库设计优化
数据库设计是整个方案的核心, 我们设计了三张表来支撑整个业务:,一言难尽。
CREATE TABLE group_msgs (
msgid BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '消息ID',
gid BIGINT NOT NULL COMMENT '群ID',
sender_uid BIGINT NOT NULL COMMENT '发送者用户ID',
time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间',
content TEXT COMMENT '消息内容',
msg_type INT NOT NULL DEFAULT 1 COMMENT '消息类型:1-文本 2-图片 3-语音',
status INT NOT NULL DEFAULT 1 COMMENT '消息状态:1-正常 0-已删除',
INDEX idx_group_time ,
INDEX idx_sender ,
INDEX idx_group_msgid
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这张表是核心,存储所有的群消息。我们特别注意了索引的设计:,我好了。
核心优化点
- 客户端批量ACK机制,这个优化效果最明显。客户端不再是收到消息就马上ACK, 而是攒够一定数量再批量提交,这个机制将原来的N次请求压缩到了N/10次效果立竿见影。
- 出哪些消息是未读的。
- 定时清理历史数据, 这个功能是运维同学强烈要求的,不然数据库迟早会爆,选择凌晨2点是主要原因是这个时间段用户最少,对业务影响最小。保留30天的数据基本能满足大部分业务需求。
| 测试场景 | 平均响应时间 | 99%响应时间 |
|---|---|---|
| 100人群 | 45ms | 78ms |
| 500人群 | 200ms | 300ms |
监控指标设计
这东西... 监控这块我们主要关注几个核心指标, 这些指标接入了我们的监控平台,一旦出现异常马上告警。
@Component
public class SystemMetrics {
private final MeterRegistry meterRegistry;
private final Counter messageCounter;
private final Timer ackProcessTime;
public SystemMetrics {
this.meterRegistry = meterRegistry;
this.messageCounter = Counter.builder
.description
.register;
this.ackProcessTime = Timer.builder
.description
.register;
}
}
未来规划
如果业务继续增长, 我们考虑这样分库分表:
1. 消息服务:负责消息存储、群成员管理和消息查询。
2. 回执服务:处理回执记录、批量确认和统计分析。
3. 推送服务:管理在线状态、消息推送和连接管理。
网关服务:作为各个服务之间的路由和负载均衡中心,负责流量管理和限流熔断。
不过目前单体架构还能hold住暂时没必要过度设计。
经过几个月的线上运行, 效果确实不错:
* 消息发送TPS稳定在500以上
* 平均响应时间控制在200ms以内
这套方案在我们的业务场景下运行得还不错,基本解决了大群聊的性能问题。当然任何技术方案都不是银弹,具体还是要根据自己的业务特点来调整。
如果你也在做类似的系统,希望这些经验能给你一些参考。有什么问题的话,欢迎一起交流讨论。
Redis在线状态管理, 用Redis管理在线状态比查数据库快多了过期时间来自动清理离线用户,避免了手动维护的麻烦。
