即时通讯开发如何设计一个百万级的消息推送

先简单介绍下本次要分享的主题,由于我最近做的是物联网相关的开发工作,其中就不免会遇到和设备的数据通信(交互)。其中最主要的工作就是要有一个系统来支持设备的接入、向设备推送消息,同时还得满足大量设备接入的需求。

所以本次分享的内容不但可以满足物联网领域同时还支持以下场景:

    1)基于 WEB 的聊天系统(点对点、群聊);
    2)WEB 应用中需求服务端推送的场景;
    3)基于 SDK 的消息推送平台。

要满足大量的连接数、同时支持双全工通信,并且性能也得有保障。在 Java 技术栈中进行选型首先自然是排除掉了传统 IO。

那就只有选 NIO 了,在这个层面其实选择也不多,考虑到社区、资料维护等方面最终选择了 Netty。

既然是一个消息系统,那自然得和客户端定义好双方的协议格式。

常见和简单的是 HTTP 协议,但我们的需求中有一项需要是双全工的交互方式,同时 HTTP 更多的是服务于浏览器。我们需要的是一个更加精简的协议,减少许多不必要的数据传输。

因此我觉得最好是在满足业务需求的情况下定制自己的私有协议,在这个场景下有标准的物联网协议。

如果是其他场景可以借鉴现在流行的 RPC 框架定制私有协议,使得双方通信更加高效。

不过根据这段时间的经验来看,不管是哪种方式都得在协议中预留安全相关的位置。协议相关的内容就不过多讨论了,更多介绍具体的应用。即时通讯聊天软件app开发可以加蔚可云的v:weikeyun24咨询

注册鉴权

在做真正的消息上、下行之前首先要考虑的就是鉴权问题。就像你使用微信一样,第一步怎么也得是登录吧,不能无论是谁都可以直接连接到平台。所以第一步得是注册才行。

如上面第4节架构图中的注册/鉴权模块。通常来说都需要客户端通过 HTTP 请求传递一个唯一标识,后台鉴权通过之后会响应一个 Token,并将这个 Token 和客户端的关系维护到 Redis 或者是 DB 中。

客户端将这个 Token 也保存到本地,今后的每一次请求都得带上这个 Token。一旦这个 Token 过期,客户端需要再次请求获取 Token。

鉴权通过之后客户端会直接通过 TCP 长连接到图中的 push-server 模块。这个模块就是真正处理消息的上、下行。

保存通道关系

在连接接入之后,真正处理业务之前需要将当前的客户端和 Channel 的关系维护起来。

假设客户端的唯一标识是手机号码,那就需要把手机号码和当前的 Channel 维护到一个 Map 中。

消息上行

接下来则是真正的业务数据上传,通常来说第一步是需要判断上传消息输入什么业务类型。在聊天场景中,有可能上传的是文本、图片、视频等内容。

所以我们得进行区分,来做不同的处理,这就和客户端协商的协议有关了:

    1)可以利用消息头中的某个字段进行区分;
    2)更简单的就是一个 JSON 消息,拿出一个字段用于区分不同消息。


不管是哪种只要可以区分出来即可。

消息解析与业务解耦

消息可以解析之后便是处理业务,比如可以是写入数据库、调用其他接口等。

在这里可以解析消息,区分类型。但如果我们的业务逻辑也写在里面,那这里的内容将是巨多无比。

甚至我们分为好几个开发来处理不同的业务,这样将会出现许多冲突、难以维护等问题。所以非常有必要将消息解析与业务处理完全分离开来。

这时面向接口编程就发挥作用了。这里的核心代码和 「造个轮子」——cicada(轻量级 Web 框架)是一致的(另外,即时通讯网的MobileIMSDK工程也使用了同样的API解偶设计思路)。

都是先定义一个接口用于处理业务逻辑,然后在解析消息之后通过反射创建具体的对象执行其中的处理函数即可。

这样不同的业务、不同的开发人员只需要实现这个接口同时实现自己的业务逻辑即可。

上行还有一点需要注意:由于是基于长连接,所以客户端需要定期发送心跳包用于维护本次连接。

同时服务端也会有相应的检查,N 个时间间隔没有收到消息之后,将会主动断开连接节省资源。

这点使用一个 IdleStateHandler 就可实现。

消息下行

有了上行自然也有下行。比如在聊天的场景中,有两个客户端连上了 push-server,它们直接需要点对点通信。

这时的流程是:

    1)A 将消息发送给服务器;
    2)服务器收到消息之后,得知消息是要发送给 B,需要在内存中找到 B 的 Channel;
    3)通过 B 的 Channel 将 A 的消息转发下去。


这就是一个下行的流程。甚至管理员需要给所有在线用户发送系统通知也是类似:遍历保存通道关系的 Map,挨个发送消息即可。这也是之前需要存放到 Map 中的主要原因。

分布式方案

单机版的实现了,现在着重讲讲如何实现百万连接。

百万连接其实只是一个形容词,更多的是想表达如何来实现一个分布式的方案,可以灵活的水平拓展从而能支持更多的连接。在做这个事前,首先得搞清楚我们单机版的能支持多少连接。

影响这个的因素就比较多了:

    1)服务器自身配置:内存、CPU、网卡、Linux 支持的最大文件打开数等;
    2)应用自身配置:因为 Netty 本身需要依赖于堆外内存,但是 JVM 本身也是需要占用一部分内存的,比如存放通道关系的大 Map。这点需要结合自身情况进行调整。


结合以上的情况可以测试出单个节点能支持的最大连接数。单机无论怎么优化都是有上限的,这也是分布式主要解决的问题。

猜你喜欢

转载自blog.csdn.net/wecloud1314/article/details/126605695