CIM(CROSS-IM) 一款面向开发者的 IM(即时通讯)系统;同时提供了一些组件帮助开发者构建一款属于自己可水平扩展的 IM 。
借助 CIM 你可以实现以下需求:
下面来看看具体的架构设计。
整体主要由以下模块组成:
cim-server
IM 服务端;用于接收 client 连接、消息透传、消息推送等功能。
支持集群部署。
cim-forward-route
消息路由服务器;用于处理消息路由、消息转发、用户登录、用户下线以及一些运营工具(获取在线用户数等)。
cim-client
IM 客户端;给用户使用的消息终端,一个命令即可启动并向其他人发起通讯(群聊、私聊);同时内置了一些常用命令方便使用。
整体的流程也比较简单,流程图如下:
所以当我们自己部署时需要以下步骤:
接下来重点看看具体的实现,比如群聊、私聊消息如何流转;IM 服务端负载均衡;服务如何注册发现等等。
IM 服务端
先来看看服务端;主要是实现客户端上下线、消息下发等功能。
首先是服务启动:
由于是在 SpringBoot 中搭建的,所以在应用启动时需要启动 Netty 服务。
从 pipline 中可以看出使用了 Protobuf 的编解码(具体报文在客户端中分析)。
注册发现
需要满足 IM 服务端的水平扩展需求,所以 cim-server 是需要将自身数据发布到注册中心的。
所以在应用启动成功后需要将自身数据注册到 Zookeeper 中。
最主要的目的就是将当前应用的 ip + cim-server-port+ http-port 注册上去。
上图是我在演示环境中注册的两个 cim-server 实例(由于在一台服务器,所以只是端口不同)。
这样在客户端(监听这个 Zookeeper 节点)就能实时的知道目前可用的服务信息。
登录
当客户端请求 cim-forward-route 中的登录接口(详见下文)做完业务验证(就相当于日常登录其他网站一样)之后,客户端会向服务端发起一个长连接,如之前的流程所示:
这时客户端会发送一个特殊报文,表明当前是登录信息。
服务端收到后就需要将该客户端的 userID 和当前 Channel 通道关系保存起来。
同时也缓存了用户的信息,也就是 userID 和 用户名。
离线
当客户端断线后也需要将刚才缓存的信息清除掉。
同时也需要调用 route 接口清除相关信息(具体接口看下文)。
IM 路由
从架构图中可以看出,路由层是非常重要的一环;它提供了一系列的 HTTP 服务承接了客户端和服务端。
目前主要是以下几个接口。
注册接口
由于每一个客户端都是需要登录才能使用的,所以第一步自然是注册。
这里就设计的比较简单,直接利用 Redis 来存储用户信息;用户信息也只有 ID 和 userName 而已。
只是为了方便查询在 Redis 中的 KV 又反过来存储了一份 VK,这样 ID 和 userName 都必须唯一。
登录接口
这里的登录和 cim-server 中的登录不一样,具有业务性质,
为了实现只能一个用户登录,使用了 Redis 中的 set 来保存登录信息;利用 userID 作为 key ,重复的登录就会写入失败。
获取一台可用的路由实例也比较简单:
当然要获取 Zookeeper 中的服务实例前自然是需要监听 cim-server 之前注册上去的那个节点。
具体代码如下:
也是在应用启动之后监听 Zookeeper 中的路由节点,一旦发生变化就会更新内部缓存。
群聊接口
这是一个真正发消息的接口,实现的效果就是其中一个客户端发消息,其余所有客户端都能收到!
流程肯定是客户端发送一条消息到服务端,服务端收到后在上文介绍的 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 中的一些信息删除掉(路由信息、登录状态)。
IM 客户端
客户端中的一些逻辑其实在上文已经谈到一些了。
登录
第一步也就是登录,需要在启动时调用 route 的登录接口,获得 cim-server 信息再创建连接。
登录过程中 route 接口会判断是否为重复登录,重复登录则会直接退出程序。
接下来是利用 route 接口返回的 cim-server 实例信息(ip+port)创建连接。
最后一步就是发送一个登录标志的信息到服务端,让它保持客户端和 Channel 的关系。
自定义协议
上文提到的一些登录报文、真正的消息报文这些其实都是在我们自定义协议中可以区别出来的。
由于是使用 Google Protocol Buffer 编解码,所以先看看原始格式。
其实这个协议中目前一共就三个字段:
目前主要是三种类型,分别对应不同的业务:
心跳
为了保持客户端和服务端的连接,每隔一段时间没有发送消息都需要自动的发送心跳。
目前的策略是每隔一分钟就是发送一个心跳包到服务端:
这样服务端每隔一分钟没有收到业务消息时就会收到 ping 的心跳包:
内置命令
客户端也内置了一些基本命令来方便使用。
比如输入 :q 就会退出客户端,同时会关闭一些系统资源。
当输入 :olu(onlineUser 的简写)就会去调用 route 的获取所有在线用户接口。
群聊
群聊的使用非常简单,只需要在控制台输入消息回车即可。
这时会去调用 route 的群聊接口。
私聊
私聊也是同理,但前提是需要触发关键字;使用 userId消息内容 这样的格式才会给某个用户发送消息,所以一般都需要先使用 :olu 命令获取所以在线用户才方便使用。
消息回调
为了满足一些定制需求,比如消息需要保存之类的。
所以在客户端收到消息之后会回调一个接口,在这个接口中可以自定义实现。
因此先创建了一个 caller 的 bean,这个 bean 中包含了一个 CustomMsgHandleListener 接口,需要自行处理只需要实现此接口即可。
自定义界面
由于我自己不怎么会写界面,但保不准有其他大牛会写。所以客户端中的群聊、私聊、获取在线用户、消息回调等业务(以及之后的业务)都是以接口形式提供。
也方便后面做页面集成,只需要调这些接口就行了;具体实现不用怎么关心。
cim 目前只是第一版,BUG 多,功能少(只拉了几个群友做了测试);不过后续还会接着完善,至少这一版会给那些没有相关经验的朋友带来一些思路。
欢迎工作一到五年的Java工程师朋友们加入Java程序员开发: 721575865
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!
我于2014年开启即时通讯的开发之路,历经从服务端到客户端,从第三方到自研,经历过诸多的研发难题,都一一破解。现将经验总结如下,希望对行业内从事IM开发的程序员有所帮助。
①P2P方式
P2P方式多用于局域网内聊天,这种方式在有种种限制和不便。一方面它只适合在线的点对点消息传输,对离线,群组等支持不够。另一方面由于 NAT 的存在,使得不同局域网内机器互联难度大大上升,在某些网络类型(对称NAT)下无法建立连接。使用P2P方式的软件在启动后一般做两件事情:
1、进行UDP广播:发送自己信息和接受同局域网内其他端信息。
2、开启TCP监听:等待其他端进行连接。
②服务器中转方式
大部分的互联网IM产品都采用服务器中转这种方式进行消息传输,相对于P2P的方式,具有有以下的优点:
1、支持更多P2P无法支持或支持不好的业务,如离线消息,群组,聊天室。
2、方便业务逻辑的拓展和新旧版本的兼容,当然它也有自己的问题,就是服务器架构复杂,并发要求高。
通过以上的比较,建议我们在开发IM系统的时候使用服务器中转的方式。
IM的网络连接方式有基于TCP的长连接和基于HTTP短连接两种:
①基于TCP的长连接
基于TCP长连接则能够更好地支持大批量用户,问题是客户端和服务器的实现比较复杂。也有一些改进,比如下行使用MQTT进行服务器通知/消息的下发,上行使用HTTP短连接进行指令和消息的上传。这种方式能够保证下行消息/指令的及时性,但是在弱网络下上行慢的问题还是比较严重,早期的来往就是基于这种方式。
②基于HTTP短连接
常见于WEB IM系统(现在很多WEBIM都是基于WebSocket实现),它的优点是实现简单,方便开发上手,问题是流量大,服务器负载较大,消息及时性无法很好地保证,对大规模的用户量支持不够,适合小型的IM系统。
IM常见的协议有:XMPP,MQTT,私有协议。各种协议优缺点情况如下:
①XMPP协议
优点:协议开源,可拓展性强,在各个端(有各种语言的实现,对于前期入门级的开发者是很好的选择,方便进入IM开发的程序员快速上手。
缺点:XML表现力弱,有太多冗余信息,流量大。
常见案例:Gtalk、新浪微博、Facebook。
②MQTT协议
优点:协议简单,流量少。
缺点:不是一个专门为IM设计的协议,多使用于推送。
③私有协议
几乎所有主流的IM APP都是使用私有协议。
优点:高效,节约流量(一般使用二进制协议),安全性高,难以破解。
缺点:开发初期没有现有样列可以参考,对于参与IM开发的程序员的要求比较高。
常见案例:微信、钉钉。
根据以上的对比,我们得出结果,一个好的协议需要满足高效、简洁、节约流量、易于拓展等要求,同时又能够和当前的开发团队的技术堆栈匹配,不能选择一个他们很难上手的。
这里再提一下,我当时开发IM系统的时候,上手用的是XMPP,在使用的过程中发现了很多问题,踩了很多坑。
①实时性原则
消息实时到达接收方,如果用户在线,则消息实时到达,如果用户不在线,则消息在用户登录后到达。由于网络波动,以及移动端操作系统对应用前后台切换的管理,如何实现用户连接管理、消息实时推送,推送失败的处理方式,客户端重连机制,消息如何补齐等,都需要IM系统考虑。由于TCP开发略微复杂,早期的基于HTTP短轮询、长轮询的低效的技术方案,也无法达到实时性的要求。
②可靠性原则
是指我们经常听到的“消息送达”,通常用消息的不丢失和不重复两个技术指标来表示。可靠性是要确保消息被发送后,能够被接收者收到。由于网络环境的复杂性,以及用户在线的不确定性,消息的可靠性(不丢失、不重复)是IM系统的核心指标,也是IM系统实现中的难点之一。总体来说,IM系统的消息“可靠性”,通常就是指聊天消息投递的可靠性(准确的说,这个“消息”是广义的,因为还存用户看不见的各种指令和通知,包括但不限于进群退群通知、好友添加通知等,为了方便描述,统称“消息”)。
从消息发送者和接收者用户行为来讲,消息“可靠性”应该分为以下几种情况:
1、发送失败:对于这种情况要感知到,明确反馈给发送方。如果此消息没有发送成功,发送方可以选择重试或者稍后再试。
2、发送成功:如果接收方处在“在线”状态,应该立即收到此消息。如果接收方处在“离线”状态不能收到消息,一旦上线则立刻收到消息。
3、消息不能重复:简言之就是发送的一条消息不能被重复收到多次。
③一致性原则
系统中要重视消息的时序问题,不能出现发送的消息顺序颠倒的问题。通常出现时序的问题有以下的原因:
1、网络传输延迟导致时序不一致。不同用户发送的消息到达服务器的延时差异较大,给消息时序性带来挑战。早期开发过程中经常会遇到这种问题。
2、分布式系统的出现导致时序不一致。IM系统模块众多,接入层、消息逻辑层等、每层都分布式集群化,这些应用分布在不同的机器上,如何保证时序是个难点。
④扩展性原则
扩展性是IM系统后期要考虑的问题,包括功能的扩展,服务器的扩展等,这次就先不展开阐述。
Mina和Netty都是Java领域高性能和高可伸缩性网络应用程序的网络应用框架。
Mina是 Apache 组织的项目,它为开发高性能和高可用性的网络 应用程序提供的框架。当前的Mina版本支持基于 Java NIO 技术的 TCP/UDP 应用程序开发、串口通讯程序。目前正在使用 Mina的 软件有:Apache Directory Project、AsyncWeb、AMQP(Advanced Message Queuing Protocol)、RED5 Server(Macromedia Flash Media RTMP)、ObjectRADIUS、Openfire等。
Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、 事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说Netty是一个基于NIO的客户端和服务器端框架,使用Netty可以确保你快速和简单的开发出一个网络应用。
虽然我使用过Mina,但是建议开发选型上使用Netty 。因为Netty有对google protocal buf的支持,有更完整的ioc容器支持(spring,guice,jbossmc和osgi)。Mina更新到2.0就不再更新了,而Netty一直在更新,目前最新发布的版本已经更新到4.1,从版本更新角度可以看出Netty的社区很活跃,修复问题一直在持续,这将对我们选择它进行开发带来很多便利。
单体Netty IM系统,可以支持10万并发,如果机器性能良好的情况下可以超过10万。
分布式的Netty IM系统,可以支持更高的并发数。各组件的功能如下:
①IM Server 连接器:主要用来负责维持和客户端的TCP连接。
②缓存:负责用户、用户绑定关系、用户群组关系的缓存。 缓存临时数据、加快读速度。可以做成集群方式。
③数据库:用户、群组、离线消息。可以做成集群方式。
④消息队列:用户状态广播、群组消息广播。可以做成集群方式。
开发环境推荐使用netty-4.1.30这个版本,jdk使用1.8及以上版本。如下所示:
io.netty
netty-all
4.1.30.Final
①开发框架采用Netty + Spring(Spring4.x)。
②Spring采用Spring cloud。基于restful 短连接的分布式微服务架构,完成用户在线管理、单点登录系统。
③消息队列采用rocketMQ 高速队列,整流作用。
④数据库采用MYSQL。
⑤协议JSON +自定义数据包采用Fastjson。
基于Netty的IM开源代码在网上有很多,这里就不列举了,可以自行去git上下载。我认为关键是把概念理清楚,技术堆栈选好,总体框架定好,接下来就是开发一个适合中小企业的IM系统了,但是要考虑到后期的扩展性,因为一个好的产品不能自己用,要让更多的人使用。
100万人及以上的及时通信系统对于服务器本身都的要求都不是最重要的了,那只是你的这个整体方案中的一部分了,最主要的就是整体系统的搭建
现在主流的这种网络应用解决方案主选品牌只有IBM DELL HP 三种
要搭建这个主要需要的设备有:1,中心数据存储,不建议光纤SCS数据传输方式,现在主流的这种类型主要都是使用ISCS链接,成本低速度也不逊色光纤;2,交换机;3,服务器;4,终端机
可以去www.dell.com.cn里面去看下各种产品的资料
因为我也只是比较了解戴尔的产品所以就简单的用戴尔的来介绍下
中心数据存储,如果预算充足是可以考虑下DELL的目前全球最先进的EQL系列产品,如果普通的话直接用MD3000I系列还是不错的,用MD3000I做数据储存阵列,利用ISCS 通过交换机链接到前段服务器,服务器可以考虑主流的R710系列或者更高端的R900
这样一套下来,如果还要做双机热备,容灾我想应该不用了,MD3000I按照8台来算,交换机4台,前段服务器至少10台,终端PC机器根据具体需求来配置,其中还要配置软件
这样预算下来的话大约在150万-180左右
其实这个只是一个大众化的解决方案
有兴趣了解的话可以留下你的联系方式,深入探讨下
欢迎分享,转载请注明来源:夏雨云
评论列表(0条)