自研IM系统方案设计

本文主要介绍APP功能中的IM模块的设计方案

1 设计原则

  • 合适原则——合适优于业界领先
  • 简单原则——简单优于复杂
  • 演化原则——演化优于一步到位

架构设计的目的在于解决系统复杂度问题,真正优秀的架构都是企业当前人力、条件、业务等各种约束下设计出来的,能够合理地将资源整合在一起并发挥出最大功效、并且能够快速落地。

2 系统复杂度分析

2.1 优先重点考虑

  • 高性能
    消息尽量低延迟
  • 成本
    人力,服务器资源有限,需要做到相比之前使用付费环信更节约成本

2.2 相对重点考虑

  • 高可用
    因为IM不是非常关键功能,只是APP的附属功能,目前阶段可暂时不考虑故障自动恢复,先采用故障手工恢复
  • 可扩展性
    功能相对固定,后期功能扩展较少
  • 规模
    使用该功能的用户规模短期不会爆炸增长

2.3 其他

  • 旧APP版本兼容
    旧版本基于环信SDK,需要支持有环信和无环信用户互聊
  • 消息的有序性
  • 消息已读未读状态更新

3 设计备选方案

3.1 通信方案

  • 方案1:个推+http接口方案
    • 方案图

个推+http接口方案

  • 优点
    技术成熟,基于现有技术,不用额外引入新技术

  • 缺点
    用户发消息频繁建立连接,服务器压力较大
    消息时延较大,个推推送消息时延较大,存在丢消息的可能性

  • 方案2:TCP长连接方案
    • 方案图

  • 优点
    消息实时性较好
    基于单条长连接,消息乱序的可能性较小

  • 缺点
    客户端和服务端需要学习使用新的技术方案维持长连接
    增加新的机器成本

3.2 存储方案

方案1: 聊天记录服务端存储

  • 优点
    支持聊天记录漫游
  • 缺点
    增加服务器成本
    增加服务端开发成本
    增加客户端,服务端接口对接

方案2: 聊天记录客户端存储

  • 优点
    节省存储成本
  • 缺点
    聊天记录不支持漫游,增加客户端开发成本

具体存储设计如下:
自研IM系统存储设计

4 方案评估和选择

基于成本和快速开发的考虑,选择个推+http接口方案,聊天消息保存服务端

5 关键难点解决方案

字段名定义
clientMsgId 消息的客户端UUID,不重复,由客户端生成
serverMsgId 消息的服务端ID,不重复,由服务端递增规则生成,
msgIsResend 消息是否是重发消息,1-是,0-否

hasMsgToReceive 是否有消息待接收,1-是,0-否
msgAccept 消息是否成功被服务端接收,1-是,0-否

5.1 APP新旧版本兼容

  • 问题
    旧版本APP使用环信通信,新版本使用自研IM通信,需要做到兼容使得新旧版本客户端可以互聊

  • 解决
    新版APP做兼容,可以兼容收环信消息,发环信消息,根据新版本发消息时,根据接收方用户的版本号,判断发环信消息,还是发自研IM消息

新旧版本APP兼容方案

5.2 发送消息与接收到的消息乱序问题

  • 问题
    用户发送消息的前,有可能有其他旧的未读消息未到达,
    消息发出去后,未读消息才到达,导致聊天界面展示消息乱序。

  • 解决
    采用客户端存储聊天记录的方案,发送消息的同时获取未读消息,再一并展示给用户看

5.3 消息发送传输失败重发问题

  • 问题
    消息已经发送给服务端,服务端处理成功并返回,但是因为网络问题返回失败,客户端重发消息,导致服务端消息重发

  • 解决
    服务端基于客户端上报的消息的clientMsgId,msgIsResend,进行消息重复校验,如果消息已发的是重复消息,不再重复发送

5.4 发消息时拉取消息消息包过大问题

  • 问题
    客户端在发消息时服务端一并返回未收消息,假定每次限定最多返回1000条消息,当服务端积压超过1000条消息,消息无法一次返回
  • 解决

客户端获取消息的有序性
在一个聊天对话中,客户端本地已有消息的时间戳(服务端创建),总是处于服务端的所有待收消息的时间戳的过去时间。

下图只有A情况满足有序性条件


实际使用上,积压大量消息未接收发生的概率极小,初步解决如下

  • 服务端返回消息报文中带一个字段,hasMsgToReceive,代表是否有消息待接收
  • 当未收过多未能一次返回,需要客户端再请求一次才能全部返回,hasMsgToReceive=1,msgAccept =0
  • 已经全部返回,hasMsgToReceive=0,msgAccept =1
  • 为了保证客户端获取消息的有序性,当服务端消息未能正常一次返回,服务端不发送本条消息,需要客户端再次重发消息

5.5 拉取未读消息传输失败问题

  • 问题
    APP拉取未读消息时,服务端成功处理并返回消息,在消息网络传输到达到达APP之前,网络中断,消息传输失败,导致丢消息。

  • 解决
    拉取消息时请求参数待最新已收消息ID(serverMsgId),服务端根据这个ID获取未读消息和删除历史已读消息

image.png

6 其他待解决问题

6.1 敏感内容,信息过滤监控

目前阶段暂时不做

6.2 消息“已读”、“未读”的标记展示

目前阶段暂时不做

6.3 客户端删除聊天消息

目前阶段暂时不做

参考

基于netty的异步非阻塞实时聊天(IM)服务器——github

陌陌IM通信架构

IM通讯云技术路线的选择

网易IM云千万级并发消息处理能力的架构设计与实践


 上一篇
分布式任务调度平台XXL-JOB 分布式任务调度平台XXL-JOB
本文主要介绍分布式任务调度平台XXL-JOB(v2.1.0版本),包括功能特性、实现原理、优缺点、同类框架比较等 基本介绍项目开发中,常常以下场景需要分布式任务调度: 同一服务多个实例的任务存在互斥时,需要统一协调 定时任务的执行需要支持
2019-10-13
下一篇 
自研IM系统存储设计 自研IM系统存储设计
1 数据操作需求1.1 发消息 发送方新增已发消息 (用于消息判重) 接收方新增待收消息 根据发送方用户ID查询最近100条已发消息(用于消息判重) 消息持久化存储 1.2 收消息 根据接收方用户ID,最新已收消息ID,查询未收消息,支
2019-10-13
  目录