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

前言


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

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

  • 通过这个标题点进来就默认你对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,维护至全局变量中

private static Map channels = new ConcurrentHashMap(1000);

推送逻辑为:根据http请求或者客户端发过来的websocket消息格式,解析内容,通过客户端连接的标识channelId找到对应客户端的连接channel对象,调用channel.writeAndFlush(new TextWebSocketFrame("需要推送的内容")),完成推送。

消息体的两个主要参数为:

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

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

初步拆分

我们希望根据系统的业务职能,做初步的拆分,以便未来根据不同业务的负载,做更细粒度的集群。
  • portal—负责处理http请求
  • websocket—负责处理websocket握手连接,接收和推送websocket消息

集群模式

注册中心与网关

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

我们加入这两个组件

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

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

  • 推送请求的路由
我们知道:
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中对应节点所订阅的主题就行

简单描述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

你可能感兴趣的:(springcloud,netty,websocket,java)