老读者应该还记得我在去年国庆节前分享过一篇《技术干货:从零开始,教你设计一个百万级的消息推送系统》,虽然我在文中有贴一些伪代码,依然有些朋友但愿能直接分享一些能够运行的源码。好吧,质疑我穷我无话可说(由于是真穷。。),怀疑我撸码的能力那是绝对不行,因此此次准备拉起键盘大干一场——徒手撸套分布式IM出来!^_^!php
本文记录了我开发的一款面向IM学习者的 IM系统——CIM(全称:CROSS-IM),同时提供了一些组件帮助开发者构建一款属于本身可水平扩展的 IM。html
经过学习本文和CIM代码,你能够得到如下知识:java
1)如何从头开发一套IM(CIM的客户有点弱,见谅见谅);node
2)如何设计分布式的IM架构;git
3)如何将你的分布式IM架构用代码和相关技术实现出来。github
本文配套的CIM源码地址:web
主要镜像:https://github.com/crossoverJie/cimredis
备用镜像:https://github.com/52im/cim算法
如下文章与本文相似或相关,一样有助于您的IM开发入门:数据库
* 友情提示:阅读本文和CIM源码,须要您具有必定的网络编程、IM理论等知识等,若是您还不具有这些,请先阅读《新手入门一篇就够:从零开发移动端IM》,彻底来的及!
学习交流:
- 即时通信/推送技术开发交流5群:215477170[推荐]
- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》
(本文同步发布于:http://www.52im.net/thread-2775-1-1.html)
crossoverJie(陈杰): 90后,毕业于重庆信息工程学院,现供职于重庆猪八戒网络有限公司。
本次特意录了两段视频演示(群聊、私聊),点击下方连接能够查看视频版 Demo。
CIM 私聊视频演示:https://www.bilibili.com/video/av39405821
CIM 群聊视频演示:https://www.bilibili.com/video/av39405501
下面来看看具体的架构设计:
架构说明:
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 客户端:给用户使用的消息终端,一个命令便可启动并向其余人发起通信(群聊、私聊);同时内置了一些经常使用命令方便使用。
总体的流程也比较简单,流程图以下:
流程解释以下:
1)客户端向 route 发起登陆;
2)登陆成功从 Zookeeper 中选择可用 IM-server 返回给客户端,并保存登陆、路由信息到 Redis;
3)客户端向 IM-server 发起长链接,成功后保持心跳;
4)客户端下线时经过 route 清除状态信息。
因此当咱们本身部署时须要如下步骤:
2)部署 cim-server,这是真正的 IM 服务器,为了知足性能需求因此支持水平扩展,只须要注册到同一个 Zookeeper 便可;
3)部署 cim-forward-route,这是路由服务器,全部的消息都须要通过它。因为它是无状态的,因此也能够利用 Nginx 代理提升可用性;
4)cim-client 真正面向用户的客户端;启动以后会自动链接 IM 服务器即可以在控制台收发消息了。
更多使用介绍能够参考快速启动。
接下来各章将重点看看具体的详细设计实现,好比群聊、私聊消息如何流转;IM 服务端负载均衡;服务如何注册发现等等。
先来看看服务端:主要是实现客户端上下线、消息下发等功能。
首先是服务启动:
因为是在 SpringBoot 中搭建的,因此在应用启动时须要启动 Netty 服务。
从 pipline 中能够看出使用了 Protobuf 的编解码(具体报文在客户端中分析,相关知识请见:《Protobuf通讯协议详解:代码演示、详细原理介绍等》)。
须要知足 IM 服务端的水平扩展需求,因此 cim-server 是须要将自身数据发布到注册中心的。这里参考以前分享的《搞定服务注册与发现》有具体介绍。
因此在应用启动成功后须要将自身数据注册到 Zookeeper 中:
最主要的目的就是将当前应用的 ip + cim-server-port+ http-port 注册上去:
上图是我在演示环境中注册的两个 cim-server 实例(因为在一台服务器,因此只是端口不一样)。这样在客户端(监听这个 Zookeeper 节点)就能实时的知道目前可用的服务信息。
当客户端请求 cim-forward-route 中的登陆接口(详见下文)作完业务验证(就至关于平常登陆其余网站同样)以后,客户端会向服务端发起一个长链接。
如以前的流程所示:
这时客户端会发送一个特殊报文,代表当前是登陆信息。服务端收到后就须要将该客户端的 userID 和当前 Channel 通道关系保存起来。
同时也缓存了用户的信息,也就是 userID 和 用户名。
当客户端断线后也须要将刚才缓存的信息清除掉。
同时也须要调用 route 接口清除相关信息(具体接口看下文)。
从架构图中能够看出,路由层是很是重要的一环;它提供了一系列的 HTTP 服务承接了客户端和服务端。
目前主要是如下几个接口。
因为每个客户端都是须要登陆才能使用的,因此第一步天然是注册。
这里就设计的比较简单,直接利用 Redis 来存储用户信息;用户信息也只有 ID 和 userName 而已。只是为了方便查询在 Redis 中的 KV 又反过来存储了一份 VK,这样 ID 和 userName 都必须惟一。
这里的登陆和 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,因此能够保证清除、新增缓存的原子性。
这是一个真正发消息的接口,实现的效果就是其中一个客户端发消息,其他全部客户端都能收到!流程确定是客户端发送一条消息到服务端,服务端收到后在上文介绍的 SessionSocketHolder 中遍历全部 Channel(通道)而后下发消息便可。服务端是单机倒也能够,但如今是集群设计。因此全部的客户端会根据以前的轮询算法分配到不一样的 cim-server 实例中。
所以就须要路由层来发挥做用了。
路由接口收到消息后首先遍历出全部的客户端和服务实例的关系。
路由关系在 Redis 中的存放以下:
因为 Redis 单线程的特质,当数据量大时;一旦使用 keys 匹配全部 cim-route:* 数据,会致使 Redis 不能处理其余请求。因此这里改成使用 scan 命令来遍历全部的 cim-route:*。
接着会挨个调用每一个客户端所在的服务端的 HTTP 接口用于推送消息。
在 cim-server 中的实现以下:
cim-server 收到消息后会在内部缓存中查询该 userID 的通道,接着只须要发消息便可。
这是一个辅助接口,能够查询出当前在线用户信息。
实现也很简单,也就是查询以前保存 ”用户登陆状态的那个去重 set “便可。
之因此说获取在线用户是一个辅助接口,其实就是用于辅助私聊使用的。通常咱们使用私聊的前提确定得知道当前哪些用户在线,接着你才会知道你要和谁进行私聊。
相似于这样:
在咱们这个场景中,私聊的前提就是须要得到在线用户的 userID:
因此私聊接口在收到消息后须要查询到接收者所在的 cim-server 实例信息,后续的步骤就和群聊一致了。调用接收者所在实例的 HTTP 接口下发信息。只是群聊是遍历全部的在线用户,私聊只发送一个的区别。
一旦客户端下线,咱们就须要将以前存放在 Redis 中的一些信息删除掉(路由信息、登陆状态)。
客户端中的一些逻辑其实在上文已经谈到一些了。
第一步也就是登陆,须要在启动时调用 route 的登陆接口,得到 cim-server 信息再建立链接。
登陆过程当中 route 接口会判断是否为重复登陆,重复登陆则会直接退出程序。
接下来是利用 route 接口返回的 cim-server 实例信息(ip+port)建立链接。最后一步就是发送一个登陆标志的信息到服务端,让它保持客户端和 Channel 的关系。
上文提到的一些登陆报文、真正的消息报文这些其实都是在咱们自定义协议中能够区别出来的。因为是使用 Google Protocol Buffer 编解码,因此先看看原始格式。
其实这个协议中目前一共就三个字段:
1)requestId 能够理解为 userId;
2)reqMsg 就是真正的消息;
3)type 也就是上文提到的消息类别。
目前主要是三种类型,分别对应不一样的业务:
为了保持客户端和服务端的链接,每隔一段时间没有发送消息都须要自动的发送心跳。
目前的策略是每隔一分钟就是发送一个心跳包到服务端:
这样服务端每隔一分钟没有收到业务消息时就会收到 ping 的心跳包:
客户端也内置了一些基本命令来方便使用。
好比输入 :q 就会退出客户端,同时会关闭一些系统资源。
当输入 :olu(onlineUser 的简写)就会去调用 route 的获取全部在线用户接口。
群聊的使用很是简单,只须要在控制台输入消息回车便可。这时会去调用 route 的群聊接口。
私聊也是同理,但前提是须要触发关键字;使用 userId;;消息内容 这样的格式才会给某个用户发送消息,因此通常都须要先使用
lu 命令获取因此在线用户才方便使用。
为了知足一些定制需求,好比消息须要保存之类的。因此在客户端收到消息以后会回调一个接口,在这个接口中能够自定义实现。
所以先建立了一个 caller 的 bean,这个 bean 中包含了一个 CustomMsgHandleListener 接口,须要自行处理只须要实现此接口便可。
因为我本身不怎么会写界面,但保不许有其余大牛会写。因此客户端中的群聊、私聊、获取在线用户、消息回调等业务(以及以后的业务)都是以接口形式提供。
也方便后面作页面集成,只须要调这些接口就好了;具体实现不用怎么关心。
cim 目前只是初版,BUG 多,功能少(只拉了几个群友作了测试);不事后续还会接着完善,至少这一版会给那些没有相关经验的朋友带来一些思路。
后续计划:
[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与MINA二、Netty4的跨平台UDP双向通讯实战 [附件下载]》
《NIO框架入门(四):Android与MINA二、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)