基于Tars高并发IM系统的设计与实现--进阶篇1

#基于Tars高并发IM系统的设计与实现–进阶篇1

进阶篇

五个难题

通过前文可以搭建一个IM系统,实现IM的基本功能。
要想达到理想的商业使用状态还需要解决一些难题,本篇将着重讲解IM系统中常见难题及解决方法,如何设计可以让IM系统达到高可用,高性能,低延时,并发支撑千百万上亿的小目标。

IM系统中常见的5大难题分别是:

  • 连接稳定
  • 消息不丢不重
  • 消息未读数准确
  • 消息时序
  • 超大群信息送达。

连接稳定

消息的收发及时对IM系统来说很重要,要做到消息及时收发依赖于稳定的TCP长连接;

要保持稳定的长连接需要解决几个棘手问题:

  • 连接缓慢
  • 身份认证耗时
  • 断网不能及时感知
  • 中途断掉能快速重连

要保持连接稳定,需要解决以上问题,我们对以上问题逐一进行分析;

连接缓慢

主要原因有DNS解析反应慢,服务端接入服务过载,移动网络环境不稳定;
DNS慢的情况可以做本地DNS缓存机制,没必要每次都需要做DNS解析;
服务端接入过载,直接扩容接入服务器即可;
移动网络环境不稳定,可以采用赛马策略进行网络连接,赛马策略是一种网络连接策略,连接时同时释放不止一个连接,选取最快连接,淘汰其他连接。

身份认证耗时

正常身份认证需要在认证服务器(或者第三方认证服务)上走完所有流程,验证用户是否合法,依赖于认证服务器反应速度,身份认证的速度严重影响IM连接速度;
为了减少对认证服务的依赖,IM对认证服务器认证结果进行缓存,缓存一定时间(可设置,一般24小时),采用IM内部认证来进行快速认证;这样大大减少对第三方认证服务的调用和依赖;

断网不能及时感知

普通情况下TCP连接对断网或者切换网络不敏感,通常情况通过心跳和对网络状态的监控来解决问题;

  • 心跳:
    在客户端和服务端进行心跳报文交换;
    通常情况下,客户端/服务端大于2个心跳周期既收不到数据报文也收不到心跳报文,就认为TCP长连接断掉了,需要启动重新进行TCP连接;
    心跳时间最简单的就是固定时间,实现起来比较容易,简单粗暴
    要想达到好的效果,就需要根据网络环境,操作系统平台,终端等设计不同的心跳或者智能心跳,还是比较复杂,需要不断优化。

  • 网络状态监控
    客户端监控网络设备开启,关闭,切换等操作,只要有状态变化,就主动关闭当前TCP长连接;启动重新进行TCP连接;

中途断掉能快速重连

TCP长连接断掉很正常,如果断掉后,没有必要走完整的登录和初始化流程;
通常情况下连接成功后需要做认证,获取热会话,同步增量好友,同步群聊等;
走简化快速重连流程,减少连接过程太久对用户体验的不友好。

通过以上分析也许有人会说不是都有解决方法么,怎么还是难题呢,说是难题不是说不能解决,要做到最佳状态很难,满分100分,能做到70分就很有效,大多数人能做到50分就不错了;
每个问题都需要好的策略设计,长时间的运行和调优才能达到比较好的状态;

消息不丢不重,

这个命题有两个要点:不丢和不重

消息不丢

这里说的不丢主要指消息系统正常运转时消息的保证到达;
消息发送的流程如图:
在这里插入图片描述

通过上图可以看到,要确保客户端A发的消息到达客户端B,并且消息不丢;必须做到如下:

  • 确保服务端能收到客户端A的消息并且落盘保存; 要做到这点,需要做到服务的收到客户端A的消息成功落盘后给客户端A一个确认ACK,客户端收到确认ACK后就可以认为次消息发送成功; 如果在一定时间内收不确认ACK就认为服务端没收到,需要重发消息直到收到确认ACK为止;
  • 确保客户端B能收到/获取到服务端的消息;
  • 客户端B分为在线和不在线两种情况;
    • 在线时,服务端会直接讲消息推送给B,B收到消息后会回一个确认ACK给服务端;
    • 不在线时,客户端B回先通过热会话获取最新消息和未读数,然后在跟据最新消息seqId获取离线时的历史消息;

获取历史消息前文已经说明,要做到既少传数据,又保证数据完整也是一件很难的事情,一般情况都是用流量还稳定,可以允许有数据冗余,或者多拉数据来保证数据完整性;

消息不重

消息重复是由于消息重发导致,在一些情况下服务端收到消息后,客户端没及时收到确认ACK,触发客户端重新发送消息,这时候服务端会再次收到相同消息,服务端需要对于多余的消息进行排重处理;

如何排重?

  • 每个消息有一个唯一且不重复消息id,一般为UUID;
  • 服务端收到消息存储后,在返回确认ACK前,将消息Id保存在判重缓存器中;
  • 每个消息到服务端需要在判重缓存器中检测是当前消息id否已经存在,如果存在表示改消息已经接收到,为重复消息,直接给前端返回消息重复ACK,中止后续处理;
  • 以上3步也不能确保消息100%不重,为做到100%不重,客户端还需要将收到所有消息根据packId去重;

通过以上4点可以做到消息不重,以上方案有2个缺点:

  • 判重缓存器中会持续增加,影响性能;
  • 每条消息都判重,也影响性能,毕竟大多数消息都是不需要重传的;

针对以上两个缺点可以进行优化:

  • 判重缓存器设置淘汰策略,一般根据时间自动淘汰,packId在缓存器中保存时间设定一个时间,通常情况下半个小时足矣。
  • 重发的消息客户端都知道,带一个重发标记,服务端只根据重发标记进行消息判重即可,不需要每条消息都判重。

消息未读数准确

未读数的显示对于IM系统来说也是一个基本功能,首先对未读数进行一个定义:

  • 用户未阅读的消息数量
  • 每个会话有个独立数量
  • 只要用户在某一个设备上阅读某个会话的消息,都算已读

要实现以上未读数功能,有两种方式:

客户端计数

客户端计数实现针对多个设备上的数据同步比较复杂,需要 客户端-》服务端->客户端;不能保证数据最大的准确性;

服务端计数

服务端计数只需要维护服务端的数据正确性,每个客户端根据情况进行数据同步就行,少一步 客户端向服务器的同步操作,别小看这一步,针对一个多端多活的异步系统,能减少很多数据同步和完整性的维护操作,也减少很多逻辑。

服务端未读数存储采用redis中zset结构,key为每个用户id+会话id,数据为未读消息id,seqid

  • 增加未读数:有新消息时向该key数据结构增加新消息的packId,seqid
  • 计算未读数:获取该key中的数量;
  • 减少未读数:通过已读消息的seqid,删除<=seqid所有数据

假设用户id为22,会话id为s1,未读数变化如图:
w200

通过以上方式可以得到一个相对准确的未读数;要获取一个准确的未读数很难;

主要有如下难题:

  • IM作为一个分布式系统,并发操作的情况下很难保持绝对的统一,客户端的已读消息指令不一定能非常准确发到服务端进行未读数变更,这样就出现客户端和服务端未读数不一致情况。
  • 终端设备很多,有iPhone/iPad,小米,华为,oppo,PC等每个设备的离线push推送中对未读数的定义和展现又不一致,要确保跟客户端软件中一致,也很难;细心的用户也许会发现著名IM软件微信和钉钉桌面图标上的未读数跟APP内部未读数经常也会不一致。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值