从零开始构建本身的分布式消息推送系统

前言


你还在羡慕别人成熟的推送系统么?
你想定制本身的推送系统么?
你有内网推送的需求而不能使用外网推送产品的困扰么? java

文章将向你介绍研究分布式推送的过程与心得(本人呕心沥血打造......),但愿看完文章的你能有所收获。mysql

  • 经过这个标题点进来就默认你对websocket有了基本的了解,若是你不知道,这里有个很好的答案websocket是什么
  • 若是你不想听我bb,完整项目传送门(给个star支持一下....3q ^_^),里面有详细的项目介绍与搭建过程。
  • websocket协议只是推送的一种实现,固然可使用其余协议,但愿构建的思路能够启发到你。

技术选型

在搜索了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

  • 推送的目标客户端标识(who)
  • 推送的内容(what)

到此,咱们的推送功能,已基本实现,只是受限于单机各项性能参数,没有任何的拓展性。redis

初步拆分

咱们但愿根据系统的业务职能,作初步的拆分,以便将来根据不一样业务的负载,作更细粒度的集群。
  • portal—负责处理http请求
  • websocket—负责处理websocket握手链接,接收和推送websocket消息

集群模式

注册中心与网关

当咱们考虑把各个业务模块部署多份时,咱们要面对这些问题:
1.须要对外暴露一个统一的入口来路由到咱们不一样的集群服务
2.对于集群中节点的上下线要作到动态感知

咱们加入这两个组件spring

  • gateway—网关,统一入口,动态路由(zuul并不支持websocket)
  • eureka—注册中心,动态的感知服务上下线

在这个阶段中,咱们须要解决一个推送业务中比较核心的问题:sql

  • 推送请求的路由
咱们知道:
websockethttp瞬时无状态的请求响应不同,客户端在发起 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中间件有: RabbitMQkafkaRocketMQ
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服务节点,即:

  • 网关动态权重路由(将客户端websocket握手链接优先分配给较少链接数量的websocket节点)
具体实如今我 完整项目传送门中的 task模块

第一次写文章。。但愿你们多多支持。。。 认知有限。。若有描述错误的地方还请指出。。。有问题提交至项目中的issue

相关文章
相关标签/搜索