请选择 进入手机版 | 继续访问电脑版

默认
发表评论 0
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
一些简单优化IM消息推送延迟的思路
优化结果:从单次 3.5s -> 0.2s

本机启动 ETCD、Redis、MySQL 和 RabbitMQ
服务端和客户端都在本地
群成员总数:500 人
在线人数:300 人
每秒发送消息数量:100次
发送消息次数:1次
响应消息数量:3 0000次
丢失消息数量:0条
Message 表数量总数:5 0000条
总耗时:3.5s -> 0.2s

优化目的:降低消息推送延迟

服务端执行流程


群聊为例:
1681127942305-70edc6f4-8b61-4a40-968e-bd4f97f761ac.png

  • Server 启动时,启动 worker pool
  • Client 请求建立连接,Server 为其创建 read 和 write 协程
  • Client 发送消息,read 读取并解析消息,根据消息类型赋予不同的路由函数,发送给 worker pool 等待调度业务层执行

业务层执行实际路由消息,如果是群聊消息发送:

  • 给自己发送一条消息(获取 seqId 和落库 Message 记录,但是不进行推送)
  • 根据 groupId 从 MySQL 中获取群成员信息
  • 验证发送者是否属于群聊

对于每个除发送者之外的群成员:

  • 从 MySQL 获取该用户的 seqId(select seq where userId = ? and object_id = ? for update)
  • 消息携带刚刚获取的 seqId 落库
  • 组装下行消息进行推送
  • 查询用户是否在线(用户通过 websocket 进行 Login 时,接入层本地存储 userId:conn 的映射,Redis 存储该 userId:RPC address 的映射),如果用户不在线,返回
  • 查询是否用户的长连接是否在本地,如果在本地,直接进行本地推送
  • RPC 服务提供接入层消息投递接口,通过 Redis 中 userId 映射的 RPC addr 获取到 gRPC 连接后调用 RPC 方法进行消息投递

思路1:缓存


应用缓存带来的问题:

  • 缓存自身的问题
  • 数据一致性和可用性问题
  • 创建群聊时,将群组信息存入 Redis(群组相关功能:增、删、改)
  • user 的 seqId 使用 Redis incr(后续优化可以使用预分配中间层,思想是直接使用内存作为缓存)

思路2:批处理


  • 集中获取用户的 seqId,为了保证顺序性,使用 lua 脚本,批次进行处理,一次最多执行 1k 个用户的 incr 脚本
  • 批量消息落库,每次落库 500 个对象
  • 消息下发时,之前都是先查用户是否在线,在哪个网关地址,再单独投递。群聊场景下,直接将全部消息投递给所有长连接网关,让它本地查找哪些用户在线,在线就进行推送,需要引入 ETCD 做服务注册
  • 消息收发过于频繁,发送消息时,暂存 buffer,当 buffer 数量满足或者到间隔时间时间,打包发送 buffer 中的数据,提高整体吞吐但是单条消息延迟上升

思路3:异步处理


  • 消息推送不必等到消息落库后再进行,消息落库可以异步,引入 MQ 来做
  • 消息推送是推送给不同客户端,可以异步处理,但是需要限制并发数量,比如 5 个

思路4:优化数据结构


  • 场景本身:读多写少 map+mutex VS sync.Map(读多写少场景性能好) VS concurrent-map(分段锁)
  • json -> proto  10倍性能提升
  • 推送后使用时间轮替代原来的 time.NewTicker(四叉树),增删 O(LogN) -> 增删 O(1),损失精度
  • 接入层 I/O 多路复用(其实已经算优化系统架构了)

思路5:池化


  • 协程池
  • 连接池

最新结果:


注:本机压本机

  • 系统:Windows 10 19045.2604
  • CPU: 2.90 GHz AMD Ryzen 7 4800H with Radeon Graphics
  • 内存: 16.0 GB
  • 群成员总数: 500人
  • 在线人数: 500人

  • 每秒/次发送消息数量: 500条
  • 每秒理论响应消息数量:25 0000条 = 500条 * 在线500人
  • 发送消息次数: 40次
  • 响应消息总量:1000 0000条 = 500条 * 在线500人 * 40次
  • Message 表数量总数:1000 0000条 = 总数500人 * 500条 * 40次
  • 丢失消息数量: 0条
  • 总耗时: 39948ms

平均每500条消息发送/转发在线人员/在线人员接收总耗时: 998ms(其实更短,因为消息是每秒发一次)

如果发送消息次数为 1,时间为:940ms

代码实现:
https://github.com/callmePicacho/GoChat

参考:
http://www.52im.net/thread-3631-1-1.html
https://mp.weixin.qq.com/s/oMStgpD_5vFsBEt-Huq8zQ
https://github.com/aceld/zinx/bl ... r/timerscheduler.go

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

标签:IM开发
上一篇:一种简单保障IM消息可靠性和有序性的实现方案下一篇:求教一个问题,用户在直播间送礼物
推荐方案
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部