乐趣区

拿起键盘就是干跟我一起徒手开发一套分布式IM系统

1、引言

老读者应该还记得我在去年国庆节前分享过一篇《技术干货:从零开始,教你设计一个百万级的消息推送系统》,虽然我在文中有贴一些伪代码,依然有些朋友希望能直接分享一些可以运行的源码。好吧,质疑我穷我无话可说(因为是真穷。。),怀疑我撸码的能力那是绝对不行,所以这次准备拉起键盘大干一场——徒手撸套分布式 IM 出来!^_^!

本文记录了我开发的一款面向 IM 学习者的 IM 系统——CIM(全称:CROSS-IM),同时提供了一些组件帮助开发者构建一款属于自己可水平扩展的 IM。

通过学习本文和 CIM 代码,你可以获得以下知识:

1)如何从头开发一套 IM(CIM 的客户有点弱,见谅见谅);
2)如何设计分布式的 IM 架构;
3)如何将你的分布式 IM 架构用代码和相关技术实现出来。

本文配套的 CIM 源码地址:

主要镜像:https://github.com/crossoverJie/cim
备用镜像:https://github.com/52im/cim

以下文章与本文类似或相关,同样有助于您的 IM 开发入门:

《自已开发 IM 有那么难吗?手把手教你自撸一个 Andriod 版简易 IM (有源码)》
《适合新手:从零开发一个 IM 服务端(基于 Netty,有完整源码)》
《拿起键盘就是干:跟我一起徒手开发一套分布式 IM 系统》
《浅谈 IM 系统的架构设计》
《简述移动端 IM 开发的那些坑:架构设计、通信协议和客户端》
《一套海量在线用户的移动端 IM 架构设计实践分享 (含详细图文)》
《一套原创分布式即时通讯(IM) 系统理论架构方案》
《一套高可用、易伸缩、高并发的 IM 群聊、单聊架构方案设计实践》

* 友情提示:阅读本文和 CIM 源码,需要您具备一定的网络编程、IM 理论等知识等,如果您还不具备这些,请先阅读《新手入门一篇就够:从零开发移动端 IM》,完全来的及!

学习交流:

– 即时通讯 / 推送技术开发交流 5 群:215477170[推荐]
– 移动端 IM 开发入门文章:《新手入门一篇就够:从零开发移动端 IM》

(本文同步发布于:http://www.52im.net/thread-2775-1-1.html)

2、关于作者

crossoverJie(陈杰): 90 后,毕业于重庆信息工程学院,现供职于重庆猪八戒网络有限公司。

3、运行演示

本次特地录了两段视频演示(群聊、私聊),点击下方链接可以查看视频版 Demo。

CIM 私聊视频演示:https://www.bilibili.com/video/av39405821
CIM 群聊视频演示:https://www.bilibili.com/video/av39405501

4、架构设计

下面来看看具体的架构设计:

架构说明:

1)CIM 中的各个组件均采用 SpringBoot 构建;
2)采用 Netty + Google Protocol Buffer 构建底层通信;
3)Redis 存放各个客户端的路由信息、账号信息、在线状态等;
4)Zookeeper 用于 IM-server 服务的注册与发现。

整体主要由以下模块组成:

1)cim-server——IM 服务端:用于接收 client 连接、消息透传、消息推送等功能。支持集群部署;
2)cim-forward-route——消息路由服务器:用于处理消息路由、消息转发、用户登录、用户下线以及一些运营工具(获取在线用户数等);
3)cim-client——IM 客户端:给用户使用的消息终端,一个命令即可启动并向其他人发起通讯(群聊、私聊);同时内置了一些常用命令方便使用。

5、逻辑流程图

整体的流程也比较简单,流程图如下:

流程解释如下:

1)客户端向 route 发起登录;
2)登录成功从 Zookeeper 中选择可用 IM-server 返回给客户端,并保存登录、路由信息到 Redis;
3)客户端向 IM-server 发起长连接,成功后保持心跳;
4)客户端下线时通过 route 清除状态信息。

所以当我们自己部署时需要以下步骤:

1)搭建基础中间件 Redis、Zookeeper;
2)部署 cim-server,这是真正的 IM 服务器,为了满足性能需求所以支持水平扩展,只需要注册到同一个 Zookeeper 即可;
3)部署 cim-forward-route,这是路由服务器,所有的消息都需要经过它。由于它是无状态的,所以也可以利用 Nginx 代理提高可用性;
4)cim-client 真正面向用户的客户端;启动之后会自动连接 IM 服务器便可以在控制台收发消息了。

更多使用介绍可以参考快速启动。

接下来各章将重点看看具体的详细设计实现,比如群聊、私聊消息如何流转;IM 服务端负载均衡;服务如何注册发现等等。

6、IM 服务端

先来看看服务端:主要是实现客户端上下线、消息下发等功能。

首先是服务启动:

由于是在 SpringBoot 中搭建的,所以在应用启动时需要启动 Netty 服务。

从 pipline 中可以看出使用了 Protobuf 的编解码(具体报文在客户端中分析,相关知识请见:《Protobuf 通信协议详解:代码演示、详细原理介绍等》)。

7、注册发现

需要满足 IM 服务端的水平扩展需求,所以 cim-server 是需要将自身数据发布到注册中心的。这里参考之前分享的《搞定服务注册与发现》有具体介绍。

所以在应用启动成功后需要将自身数据注册到 Zookeeper 中:

最主要的目的就是将当前应用的 ip + cim-server-port+ http-port 注册上去:

上图是我在演示环境中注册的两个 cim-server 实例(由于在一台服务器,所以只是端口不同)。这样在客户端(监听这个 Zookeeper 节点)就能实时的知道目前可用的服务信息。

8、登录

当客户端请求 cim-forward-route 中的登录接口(详见下文)做完业务验证(就相当于日常登录其他网站一样)之后,客户端会向服务端发起一个长连接。

如之前的流程所示:

这时客户端会发送一个特殊报文,表明当前是登录信息。服务端收到后就需要将该客户端的 userID 和当前 Channel 通道关系保存起来。

同时也缓存了用户的信息,也就是 userID 和 用户名。

9、离线消息

当客户端断线后也需要将刚才缓存的信息清除掉。

同时也需要调用 route 接口清除相关信息(具体接口看下文)。

10、IM 路由

从架构图中可以看出,路由层是非常重要的一环;它提供了一系列的 HTTP 服务承接了客户端和服务端。

目前主要是以下几个接口。

10.1 注册接口

由于每一个客户端都是需要登录才能使用的,所以第一步自然是注册。

这里就设计的比较简单,直接利用 Redis 来存储用户信息;用户信息也只有 ID 和 userName 而已。只是为了方便查询在 Redis 中的 KV 又反过来存储了一份 VK,这样 ID 和 userName 都必须唯一。

10.2 登录接口

这里的登录和 cim-server 中的登录不一样,具有业务性质:

具体的流程:

1)登录成功之后需要判断是否是重复登录(一个用户只能运行一个客户端);
2)登录成功后需要从 Zookeeper 中获取服务列表(cim-server)并根据某种算法选择一台服务返回给客户端;
3)登录成功之后还需要保存路由信息,也就是当前用户分配的服务实例保存到 Redis 中。

为了实现只能一个用户登录,使用了 Redis 中的 set 来保存登录信息;利用 userID 作为 key,重复的登录就会写入失败。

类似于 Java 中的 HashSet,只能去重保存。

获取一台可用的路由实例也比较简单:

1)先从 Zookeeper 获取所有的服务实例做一个内部缓存;
2)轮询选择一台服务器(目前只有这一种算法,后续会新增)。

当然要获取 Zookeeper 中的服务实例前自然是需要监听 cim-server 之前注册上去的那个节点。

具体代码如下:

也是在应用启动之后监听 Zookeeper 中的路由节点,一旦发生变化就会更新内部缓存。这里使用的是 Guava 的 cache,它基于 ConcurrentHashMap,所以可以保证清除、新增缓存的原子性。

10.3 群聊接口

这是一个真正发消息的接口,实现的效果就是其中一个客户端发消息,其余所有客户端都能收到!流程肯定是客户端发送一条消息到服务端,服务端收到后在上文介绍的 SessionSocketHolder 中遍历所有 Channel(通道)然后下发消息即可。服务端是单机倒也可以,但现在是集群设计。所以所有的客户端会根据之前的轮询算法分配到不同的 cim-server 实例中。

因此就需要路由层来发挥作用了。

路由接口收到消息后首先遍历出所有的客户端和服务实例的关系。

路由关系在 Redis 中的存放如下:

由于 Redis 单线程的特质,当数据量大时;一旦使用 keys 匹配所有 cim-route:* 数据,会导致 Redis 不能处理其他请求。所以这里改为使用 scan 命令来遍历所有的 cim-route:*。

接着会挨个调用每个客户端所在的服务端的 HTTP 接口用于推送消息。

在 cim-server 中的实现如下:

cim-server 收到消息后会在内部缓存中查询该 userID 的通道,接着只需要发消息即可。

10.4 在线用户接口

这是一个辅助接口,可以查询出当前在线用户信息。

实现也很简单,也就是查询之前保存”用户登录状态的那个去重 set“即可。

10.5 私聊接口

之所以说获取在线用户是一个辅助接口,其实就是用于辅助私聊使用的。一般我们使用私聊的前提肯定得知道当前哪些用户在线,接着你才会知道你要和谁进行私聊。

类似于这样:

在我们这个场景中,私聊的前提就是需要获得在线用户的 userID:

所以私聊接口在收到消息后需要查询到接收者所在的 cim-server 实例信息,后续的步骤就和群聊一致了。调用接收者所在实例的 HTTP 接口下发信息。只是群聊是遍历所有的在线用户,私聊只发送一个的区别。

10.6 下线接口

一旦客户端下线,我们就需要将之前存放在 Redis 中的一些信息删除掉(路由信息、登录状态)。

11、IM 客户端

客户端中的一些逻辑其实在上文已经谈到一些了。

11.1 登录

第一步也就是登录,需要在启动时调用 route 的登录接口,获得 cim-server 信息再创建连接。

登录过程中 route 接口会判断是否为重复登录,重复登录则会直接退出程序。

接下来是利用 route 接口返回的 cim-server 实例信息(ip+port)创建连接。最后一步就是发送一个登录标志的信息到服务端,让它保持客户端和 Channel 的关系。

11.2 自定义协议

上文提到的一些登录报文、真正的消息报文这些其实都是在我们自定义协议中可以区别出来的。由于是使用 Google Protocol Buffer 编解码,所以先看看原始格式。

其实这个协议中目前一共就三个字段:

1)requestId 可以理解为 userId;
2)reqMsg 就是真正的消息;
3)type 也就是上文提到的消息类别。

目前主要是三种类型,分别对应不同的业务:

11.3 心跳

为了保持客户端和服务端的连接,每隔一段时间没有发送消息都需要自动的发送心跳。

目前的策略是每隔一分钟就是发送一个心跳包到服务端:

这样服务端每隔一分钟没有收到业务消息时就会收到 ping 的心跳包:

11.4 内置命令

客户端也内置了一些基本命令来方便使用。

比如输入 :q 就会退出客户端,同时会关闭一些系统资源。

当输入:olu(onlineUser 的简写)就会去调用 route 的获取所有在线用户接口。

11.5 群聊

群聊的使用非常简单,只需要在控制台输入消息回车即可。这时会去调用 route 的群聊接口。

11.6 私聊

私聊也是同理,但前提是需要触发关键字;使用 userId;; 消息内容 这样的格式才会给某个用户发送消息,所以一般都需要先使用 

lu 命令获取所以在线用户才方便使用。

11.7 消息回调

为了满足一些定制需求,比如消息需要保存之类的。所以在客户端收到消息之后会回调一个接口,在这个接口中可以自定义实现。

因此先创建了一个 caller 的 bean,这个 bean 中包含了一个 CustomMsgHandleListener 接口,需要自行处理只需要实现此接口即可。

11.8 自定义界面

由于我自己不怎么会写界面,但保不准有其他大牛会写。所以客户端中的群聊、私聊、获取在线用户、消息回调等业务 (以及之后的业务) 都是以接口形式提供。

也方便后面做页面集成,只需要调这些接口就行了;具体实现不用怎么关心。

12、本文小结

cim 目前只是第一版,BUG 多,功能少(只拉了几个群友做了测试);不过后续还会接着完善,至少这一版会给那些没有相关经验的朋友带来一些思路。

后续计划:

附录:更多 IM 相关文章

[1] 有关 IM 代码实践的文章:
《自已开发 IM 有那么难吗?手把手教你自撸一个 Andriod 版简易 IM (有源码)》
《一种 Android 端 IM 智能心跳算法的设计与实现探讨(含样例代码)》
《手把手教你用 Netty 实现网络通信程序的心跳机制、断线重连机制》
《详解 Netty 的安全性:原理介绍、代码演示(上篇)》
《详解 Netty 的安全性:原理介绍、代码演示(下篇)》
《微信本地数据库破解版 (含 iOS、Android),仅供学习研究 [附件下载]》
《Java NIO 基础视频教程、MINA 视频教程、Netty 快速入门视频 [有源码]》
《轻量级即时通讯框架 MobileIMSDK 的 iOS 源码(开源版)[附件下载]》
《开源 IM 工程“蘑菇街 TeamTalk”2015 年 5 月前未删减版完整代码 [附件下载]》
《微信本地数据库破解版(含 iOS、Android),仅供学习研究 [附件下载]》
《NIO 框架入门(一):服务端基于 Netty4 的 UDP 双向通信 Demo 演示 [附件下载]》
《NIO 框架入门(二):服务端基于 MINA2 的 UDP 双向通信 Demo 演示 [附件下载]》
《NIO 框架入门(三):iOS 与 MINA2、Netty4 的跨平台 UDP 双向通信实战 [附件下载]》
《NIO 框架入门(四):Android 与 MINA2、Netty4 的跨平台 UDP 双向通信实战 [附件下载]》
《用于 IM 中图片压缩的 Android 工具类源码,效果可媲美微信 [附件下载]》
《高仿 Android 版手机 QQ 可拖拽未读数小气泡源码 [附件下载]》
《一个 WebSocket 实时聊天室 Demo:基于 node.js+socket.io [附件下载]》
《Android 聊天界面源码:实现了聊天气泡、表情图标(可翻页) [附件下载]》
《高仿 Android 版手机 QQ 首页侧滑菜单源码 [附件下载]》
《开源 libco 库:单机千万连接、支撑微信 8 亿用户的后台框架基石 [源码下载]》
《分享 java AMR 音频文件合并源码,全网最全》
《微信团队原创 Android 资源混淆工具:AndResGuard [有源码]》
《一个基于 MQTT 通信协议的完整 Android 推送 Demo [附件下载]》
《Android 版高仿微信聊天界面源码 [附件下载]》
《高仿手机 QQ 的 Android 版锁屏聊天消息提醒功能 [附件下载]》
《高仿 iOS 版手机 QQ 录音及振幅动画完整实现 [源码下载]》
《Android 端社交应用中的评论和回复功能实战分享[图文 + 源码]》
《Android 端 IM 应用中的 @人功能实现:仿微博、QQ、微信,零入侵、高可扩展[图文 + 源码]》
《仿微信的 IM 聊天时间显示格式(含 iOS/Android/Web 实现)[图文 + 源码]》
《Android 版仿微信朋友圈图片拖拽返回效果 [源码下载]》
《适合新手:从零开发一个 IM 服务端(基于 Netty,有完整源码)》
《拉起键盘就是干:跟我一起徒手撸一套分布式 IM 系统》
>> 更多同类文章 …… 
[2] 有关 IM 架构设计的文章:
《浅谈 IM 系统的架构设计》
《简述移动端 IM 开发的那些坑:架构设计、通信协议和客户端》
《一套海量在线用户的移动端 IM 架构设计实践分享(含详细图文)》
《一套原创分布式即时通讯(IM) 系统理论架构方案》
《从零到卓越:京东客服即时通讯系统的技术架构演进历程》
《蘑菇街即时通讯 /IM 服务器开发之架构选择》
《腾讯 QQ1.4 亿在线用户的技术挑战和架构演进之路 PPT》
《微信后台基于时间序的海量数据冷热分级架构设计实践》
《微信技术总监谈架构:微信之道——大道至简(演讲全文)》
《如何解读《微信技术总监谈架构:微信之道——大道至简》》
《快速裂变:见证微信强大后台架构从 0 到 1 的演进历程(一)》
《17 年的实践:腾讯海量产品的技术方法论》
《移动端 IM 中大规模群消息的推送如何保证效率、实时性?》
《现代 IM 系统中聊天消息的同步和存储方案探讨》
《IM 开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?》
《IM 开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议》
《IM 开发基础知识补课(四):正确理解 HTTP 短连接中的 Cookie、Session 和 Token》
《WhatsApp 技术实践分享:32 人工程团队创造的技术神话》
《微信朋友圈千亿访问量背后的技术挑战和实践总结》
《王者荣耀 2 亿用户量的背后:产品定位、技术架构、网络方案等》
《IM 系统的 MQ 消息中间件选型:Kafka 还是 RabbitMQ?》
《腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》
《以微博类应用场景为例,总结海量社交系统的架构设计步骤》
《快速理解高性能 HTTP 服务端的负载均衡技术原理》
《子弹短信光鲜的背后:网易云信首席架构师分享亿级 IM 平台的技术实践》
《知乎技术分享:从单机到 2000 万 QPS 并发的 Redis 高性能缓存实践之路》
《IM 开发基础知识补课(五):通俗易懂,正确理解并用好 MQ 消息队列》
《微信技术分享:微信的海量 IM 聊天消息序列号生成实践(算法原理篇)》
《微信技术分享:微信的海量 IM 聊天消息序列号生成实践(容灾方案篇)》
《新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践》
《一套高可用、易伸缩、高并发的 IM 群聊、单聊架构方案设计实践》
《阿里技术分享:深度揭秘阿里数据库技术方案的 10 年变迁史》
《阿里技术分享:阿里自研金融级数据库 OceanBase 的艰辛成长之路》
《社交软件红包技术解密(一):全面解密 QQ 红包技术方案——架构、技术实现等》
《社交软件红包技术解密(二):解密微信摇一摇红包从 0 到 1 的技术演进》
《社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节》
《社交软件红包技术解密(四):微信红包系统是如何应对高并发的》
《社交软件红包技术解密(五):微信红包系统是如何实现高可用性的》
《社交软件红包技术解密(六):微信红包系统的存储层架构演进实践》
《社交软件红包技术解密(七):支付宝红包的海量高并发技术实践》
《社交软件红包技术解密(八):全面解密微博红包技术方案》
《社交软件红包技术解密(九):谈谈手 Q 红包的功能逻辑、容灾、运维、架构等》
《即时通讯新手入门:一文读懂什么是 Nginx?它能否实现 IM 的负载均衡?》
《即时通讯新手入门:快速理解 RPC 技术——基本概念、原理和用途》
《多维度对比 5 款主流分布式 MQ 消息队列,妈妈再也不担心我的技术选型了》
《从游击队到正规军:马蜂窝旅游网的 IM 系统架构演进之路》
《IM 开发基础知识补课(六):数据库用 NoSQL 还是 SQL?读这篇就够了!》
>> 更多同类文章 ……

(本文同步发布于:http://www.52im.net/thread-2775-1-1.html)

退出移动版