你还在羡慕别人成熟的推送系统么?
你想定制本身的推送系统么?
你有内网推送的需求而不能使用外网推送产品的困扰么? java
文章将向你介绍研究分布式推送的过程与心得(本人呕心沥血打造......),但愿看完文章的你能有所收获。mysql
在搜索了n屡次websocket这个关键词之后...我选择用netty这个支持nio的高性能网络框架做为推送支持,它帮咱们屏蔽了网络底层复杂通讯逻辑,提供简单易用的api。(websocket的netty实现网上一搜一大把)
客户端的websocket握手请求如:ws://127.0.0.1:9003/websocket?channelId=123456
将客户端的惟一标识123456
与客户端在咱们netty
中抽象出的链接对象channel
,维护至全局变量中git
private static Map<String,Channel> channels = new ConcurrentHashMap(1000);
推送逻辑为:根据http
请求或者客户端发过来的websocket
消息格式,解析内容,经过客户端链接的标识channelId
找到对应客户端的链接channel
对象,调用channel.writeAndFlush(new TextWebSocketFrame("须要推送的内容"))
,完成推送。github
消息体的两个主要参数为:web
到此,咱们的推送功能,已基本实现,只是受限于单机各项性能参数,没有任何的拓展性。redis
咱们但愿根据系统的业务职能,作初步的拆分,以便将来根据不一样业务的负载,作更细粒度的集群。
当咱们考虑把各个业务模块部署多份时,咱们要面对这些问题:
1.须要对外暴露一个统一的入口来路由到咱们不一样的集群服务
2.对于集群中节点的上下线要作到动态感知
咱们加入这两个组件spring
在这个阶段中,咱们须要解决一个推送业务中比较核心的问题:sql
咱们知道:
websocket
与http
瞬时无状态的请求响应不同,客户端在发起websocket
握手成功后,不会立马断开,会维持住与服务器的tcp
链接,用于全双工通讯。在这种状况下,咱们的推送请求,就不能任由portal
服务端负载均衡,路由到各个websocket
服务节点上了。
举个栗子:
如上图所示client1
发起websocket
握手时,由网关将握手请求路由给websocket1
节点,client1
会经过网关与websocket1
节点维持一个tcp
通道,那么以http
请求来触发推送时,必需要把对client1
的推送请求指定路由给websocket1
节点来处理(对于以websocket
消息触发的推送请求也是如此,client4
发起对client1
的推送必须由接收到消息的websocket2
节点转发给websocket1
节点处理)。
此时,咱们须要引入redis
来记录各个websocket节点上所维护的客户端
:数据库
websocket
服务节点处理完客户端的握手请求之后,将节点与客户端的路由关系保存进redis
http
触发推送的流程:portal
接收到推送请求时,根据须要推送的客户端的目的地,从redis
中找到客户端所在的服务器,转发http
请求至客户端所在服务器的websocket
节点,websocket
节点发起推送websocket
消息触发推送的流程:websocket
节点接收到客户端websocket
消息推送请求时,判断须要推送的客户端是否在本节点上,若是是则直接推送,若是不是则转发给对应的其余websocket
节点发起推送在我当前的项目中,一些地方使用到了redis
的管道特性,因此这里redis
不支持cluster
这种分片的集群部署方式,要想适用分片的集群方式来提升并发只能使用codis
作代理。。要么就单个master
。
上图可见:服务间的调用关系很是复杂,系统间耦合很是高,在线上对端口严格管控的服务器下部署这样一套系统是很是痛苦的,咱们考虑引入MQ
解耦:当有须要转发给websocket
节点进行推送时,投递到MQ中对应节点所订阅的主题就行 api
简单描述mq的选型问题:目前经常使用的分布式高可用MQ中间件有:RabbitMQ
,kafka
,RocketMQ
RabbitMQ:须要安装Erlang环境。。我的但愿系统部署尽可能简单,首先就排除(对java有把握一点。。)
kafka:通常用于日志分析,大数据计算
RocketMQ: 相比于kafka,可靠,实时,易用等特性更适用与业务系统交互的场景中,注册中心nameSer也比kafka的zk要轻便(就他了) 详细对比传送门
到此,功能已基本完成,只是咱们如今每部署一个服务节点都须要配置相同的注册中心地址、redis地址、mq地址,当某个中间件地址变更时,整个服务的全部节点都须要相适配,为避免这种繁琐而重复的配置,咱们考虑引入配置中心:
配置中心选型:
springcloud config:须要依赖git
apollo:须要依赖mysql
nacos:服务自带数据库,没有任何依赖,安装使用简单便捷。(就他了)
如今的完整配置如图:
咱们的分布式消息推送系统已经完成了O(∩_∩)O,不过这里存在一个运行上的小问题:
如今从gateway
网关路由websocket
握手请求到各个websocket
服务节点的权重都是相同的,也就是说理论上咱们但愿各个websocket
节点所维持的客户端链接数量是大体相同的,可是当咱们服务部署运行一段时间后,发现各个websocket
节点的负载较高,咱们但愿增长(或者服务节点重启)websocket
服务节点的数量。可是增长(重启)完websocket
节点之后,gateway
路由到各个websocket
节点的权重依然相同,其实咱们但愿gateway
网关将websocket
握手请求能优先能路由给新部署上来的websocket
服务节点,即:
具体实如今我
完整项目传送门中的
task
模块
第一次写文章。。但愿你们多多支持。。。 认知有限。。若有描述错误的地方还请指出。。。有问题提交至项目中的issue