前言
前段时间开发某个系统 ,需要用到消息推送功能,类似消息推送服务。
背景
因为公司开发的系统越来越多,有很多系统度需要一个消息的实时推送,接收功能,比如说,在某工单系统提了工单,在个人管理那里收到实时的反馈,或者在某教学系统上完课,家长评论后老师在教学系统也实时收到反馈,又或者在业务系统下单扣减课时,教师或者业务人员实时收到反馈等。如果在每个系统都开发类似的代码就臃肿了,于是消息推送服务出现了。
技术选型
- 很容易想到了使用websocket来作为通信(协议)的载体,它利用浏览器给我们屏蔽协议的具体实现(比如我们不需要自己定义:包头,长度,数据等(我的理解)),就能够和服务端建立长连接。
- Websocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信,即允许服务器主动发送信息给客户端。因此,在Websocket中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输,客户端和服务器之间的数据交换变得更加简单。具体看websocket是什么,或者:Websocket
- 服务端很容易想到了用netty,netty这个支持nio的高性能网络框架作为推送支持,它帮我们屏蔽了网络底层复杂通信逻辑,提供简单易用的api
- mq用的是rabbitmq
思路
- 由于各个系统都是基于 springboot的微服务系统,都把各自信息注册到了注册中心上了,所以只需要开发一个消息推送服务,各个系统登录时和消息推送服务建立连接,就能够收发消息。
- 消息服务开发好接口后,只需要提供接口给其他系统,其他系统处理完自己系统的业务逻辑后通过feign(或mq也行)调用即可【比如系统系统登录时需要获取消息列表,或者点击 查看,删除消息时调用消息服】,这样各个系统只需内部接入消息服务(内部调用)就能够有消息实时推送接收功能了。
消息服务的集群部署问题
- 然而由于真正的正式线上的服务可能会部署多份,长连接的系统会存在一个问题:
- 可以看到:当登录时浏览器a和websocket服务a建立长连接后,当处理某业务逻辑后,由于websocket服务部署多台机,返回数据给浏览器时,不能保证返回的数据发到正确的客户端上,因此需要引入一个分发机制,就是一个路由。可以写一个路由的服务也可以在代码回调时判断。
- 但是怎么保证路由回去正确的客户端呢,可以这样做:
- 引入redis,登录时通过redis记录每个用户对应的websocket客户端
- 当返回数据给客户端时,首先通过redis拿到客户端信息,判断客户端是否连得是本服务节点,如果是直接发送,如果不是可以借助mq(部署时对应好每个队列处理各个客户端的信息),把消息发送到mq,由mq来分发到对应的服务节点,这样不仅起到了解耦作用,当服务间消息调用比较频繁时可以通过调节RabbitMq的投递消息参数:PrefetchCount,來提高客户端吞吐量,如果吞吐量条太大客户端的cup可能调度太频繁导致cup过高可以通过增加机器动态扩容。
- 如下图主要增加说明 websocket返回数据给客户端时通过路由判断是属于哪个机器
- 这样一台机器保证处理唯一的客户端,需要定义一个用于转发的交换机,以及各个queue,一台机器保证一个独立的queue。部署时可以把客户端对应的唯一队列写在启动脚本那里(下面是脚本伪代码):
- 比如:
localQueue=websocket.redirect.a
,当部署另外一台服务时:localQueue=websocket.redirect.b
,以此类推:localQueue=websocket.redirect.c
, - 然后:nohup java -jar push.jar --socketio.rabbitmq.consumer.queue=$localQueue
- 这样做需要每部署多一个服务都需要添加一个唯一队列,以及写一个不一样的脚本来启动,不知读者有没好的办法??
- 比如:
项目主要使用的技术栈
- 主要用到的组件或者中间件有:Eureka,Redis,RabbitMq,Feign
- netty-socketio
最后
上面是个人的总结,迟点把主要功能的代码提取出来写成一个demo放在GitHub,读者有什么觉得我需要改进或者理解不到位的,或者需要补充的,还不够完善的可以提出来。谢谢!
https://github.com/David0101/msg-push
待续…