IM系统设计

即时通讯(Instant Messaging,简称IM)是一个实时通信系统,允许两人或多人使用网络实时的传递文字消息、文件、语音与视频交流。实现方式有两种。第一种基于Server转发的,Client双方通信会经过Server转发来完成消息传递。例如QQ、微信。

第二种是基于P2P(点对点)的。P2P的实现依赖于客户端之间的互联,但由于NAT与防火墙的存在,客户端无法直接互联,需要coturn服务器用来穿越NAT网络。

本文主要讲述基于Server转发实现的即时通讯原理和实现过程,包括私聊和群聊两类。私聊和群聊,原理基本类似,但私聊是单次转发,群聊属于遍历转发。不同点为,群聊以群ID(多人)划分,私聊以会话ID(两人)划分,其次消息存储上也有较大差异。

总体设计

架构分析

IM架构应满足哪些条件

高可用:任何一个节点故障都不应该引起服务不可用;

易扩展:具有水平扩展的特性,对不同量级的在线用户数都有应变的能力;

高并发低延迟:能支持大量的用户同时收发消息,消息从发出到送达所有在线端的延时在毫秒级;

客户端兼容性:新型的应用都是能同时跨多种设备实现消息互通的,比如网页端,手机端和桌面端,甚至智能电视等。

客户端层

处理各种设备的兼容问题,包括对ios,Android,Windows, Web等各种开发平台的语言适配;消息通道的管理维护,包括移动设备上的弱网络管理,断线重连等;保证数据安全,所有上行下行的数据包都需要加解密处理,规避数据泄露或中间人攻击等各种安全风险。

网关接入层

管理大量客户端连接,单个节点可以维护的客户端数量在数十万量级;处理不同类型客户端的协议兼容,由于客户端实现技术的多样性,导致客户端与网关之间底层的数据通信协议存在差异,需要由不同的接入网关做协议转换;处理数据安全逻辑;跨网络的高可用逻辑,网络级别的主备(谁知道哪天网线会被蓝翔的毕业生挖断呢?);广播消息的高效下行分发,将收到的广播消息分发到所有连接在本节点上的客户端。

路由层

作为业务层接入的中转,同时承担负载均衡和高可用的作用,单个业务节点处理能力达到瓶颈时更方便的扩容,路由层使业务层扩容对前置网关层完全透明;当一个网络的业务集群出现网络故障时,可以切换到备用网络,保证服务可用性。

业务层

处理IM业务消息,一个集群内有众多节点,节点角色相互对等,任何一个节点的故障会使整个集群的处理能力下降,但不会引起服务的中断,因为其他节点可以继续接管业务数据包的处理;业务集群同样有多个网络环境的热备,以应对可能出现的区域性网络故障。

难点在哪里

客户端多样性

目前的应用都存在跨平台的需求,iOS、安卓和PC端,网页端,甚至IOT物联网设备,能连多少是多少,多多益善;但是不同开发平台之间的技术差异性极大,不是所有公司都有这么全的全栈程序猿的;如果团队开发的话单就客户端开发人员就不是几个人可以完成的。

数据安全的保证

当前的网络安全形势异常复杂,开发应用时如果不在通信安全上花心思,那你的用户就是在互联网上裸奔;开发者需要针对不同的平台,不同的通信技术实现可靠的安全方案,避免用户数据在传输过程中泄露,避免中间人攻击等安全风险。

跨机房网络级的高可用方案

当机房网络出现故障时把责任推给市政施工队或者“网络抽风”已经不流行了,用户需要的是故障无感知。

所有环节的单点故障排除

任何硬件和软件都存在故障的可能,我们无法避免应用罢工,那就需要随时准备替补上场。

能应对任何用户量级的需求

架构级做到水平扩展的能力,当用户量增长时随时可以通过堆服务器来解决,而不是将架构推倒重来。

实现方式

协议:WebSocket
框架:Socket IO/Swoole
关键点:可扩展,支持分布式部署。短链接负责业务逻辑,长链接负责Websocket。

总体架构图

时序图

详细实现

接入层实现

接入层的目的:1保证IM服务的可靠性,避免用户同时集中在同一聊天服务器中。2当聊天服务器压力过大时,能实现扩容。接入层可采用ngx_lua实现,保证性能。

客户端请求接入IM,调用接入API。参数为:接入类型(群聊,私聊),接入uid,群聊ID
群聊:保证隶属同群ID的用户落在同一Socket实例上。如果用户ABC,都属于群ID=123中,那么一定要保证ABC都分配在同一实例上。具体分配策略自定,一般按照群ID取余分配在不同机器实例上。
私聊:A和B私聊,也要保证A和B落在同一实例上。一般分配策略为,(A+B)对机器取余。
根据群聊类型,得到接入实例的IP和端口,返回给客户端。

连接逻辑

连接逻辑比较简单,Socket connect的过程。

客户端得到接入层提供的Socket Ip和端口,发起Websocket请求。
Socket服务,根据请求参数,判断客户端合法性(签名验证)和登录验证。
记录请求日志,保存Socket连接句柄(当前实例数组中)。

订阅消息逻辑

上一步连接成功后,用户触发进群(聊天窗口)操作,订阅该群消息。订阅消息用来保证,用户通过socket实时接收到该群其他用户发送的消息。否则,服务端会采用推送完成消息转发。

订阅事件。emit('sub'),群聊:发送当前群ID和用户的uid。私聊:发送对方uid和用户自身uid(会话ID)。
服务端判断该uid是否已经在线,如果已经在线,则主动关闭旧的Socket,保证只有当前窗口在线。并将当前socket句柄push到数组中保存。
业务逻辑判断。群聊:判断用户是否属于该群,判断群合法性。私聊:判断是否为好友。
客户端在线,接收Socket消息,对消息解码,区分类型并展示。编码协议自定。
客户端不在线,收到Push推送。客户端根据接收到的推送,拉取历史消息。更新未读数。

发布消息逻辑

用户完成订阅群ID(群聊)或者订阅会话ID(私聊)后,可主动发送消息到该订阅ID(群ID和会话ID)中。

发布事件。emit('pub'),图片(缩略图)、视频、语音,完成上传(静态文件服务)后,编码消息内容并提交。编码协议自定。
服务端收到消息后,解码,识别过滤色情、政治内容。写入队列,完成其他操作(判断图片色情内容,消息统计,落地入库,更新未读数)。
群聊:遍历当前群存在的socket句柄,如果在线则转发内容,不在线推送通知。
私聊:判断对方是否在线,如果在线转发,不在线push推送。

用户下线

用户退出聊天窗口,触发disconnect事件。服务端关闭socket句柄,标示用户下线。

广播逻辑

广播逻辑,主要用在运营管理上。是对所有在线用户或者特定群ID(会话ID),发送消息(系统消息)。实现方法为:遍历所有在线socket句柄,发送内容。

监控服务

在运营管理或者监控上,需要对聊天服务性能加以判定。 1. 统计在线人数,得到每个实例的Socket句柄长度相加

posted @ 2024-08-27 09:19  不学巫术  阅读(41)  评论(0)    收藏  举报