消息推送系统
实现方式三种:
方式
|
描述
|
优点
|
缺点
|
---|---|---|---|
短轮询 |
客户端通过ajax每隔一段时间向服务端发起一次请求,服务 端不管有无消息都返回给客户端。 |
实现简单 对客户端和服务端无要求 |
大部分请求为无效请求 浪费带宽和服务器 实时性不高 |
长轮询 |
与短轮询类似,不同的是,服务端收到请求后,若没有新消息则 等待(不立即返回),直到有新消息或超时才返回。 |
实现简单 对客户端无要求 |
服务端压力增大 浪费服务端连接 |
长连接 |
客户端和服务端开启一条连接通道,实现双工通信。有新消息时, 服务端主动推送给客户端。 |
效率最高 |
浏览器版本要求 nginx、tomcat版本要求 |
笔者采用长连接的方式,用websocket来做长连接。
系统架构图
消息推送时序图
一共两个服务,连接服务和推送服务。
连接服务用于和客户端保持长连接,接收到MQ的消息时,将消息推送给客户端。因为要保持长连接,基于前置机的特点,可能需要采取一些策略来保持,这里是nginx,默认60s超时,需要客户端每一个时间间隔(30s)发送一个心跳包。
推送服务用于接收各业务系统的消息推送请求,过滤并放到MQ中。
连接服务和推送服务的特质不同,用MQ来做解耦。接连服务:公网,有状态,压力来至客户端数量;推送服务:内网,无状态,消息推送频次。
伸缩性:
- 推送服务,无状态,可动态增加节点。提供dubbo接口,软负载均衡。
- 连接服务,有状态,可动态增加节点,需要向nginx增加一条转发规则。通过前置机的负载均衡,每台服务的压力可调控。
健壮性:
- 推送服务无状态,只要存活一台即可保证服务可用。
- 连接服务有状态,down掉一台会丢失这一台的所有连接,但是只要一台存活着,这些丢失的连接都可重连。
权限方面问题:
存在问题:
-
- 权限验证。不是任意连接请求都建立连接,如何验证。
- 一次登录,多次连接。登陆后开多个页面,每个页面会发起一次websocket连接,这些连接的token相同,是否允许多个连接?还是设置一个连接数上限?
- 同一用户,多处登录。每处发起一次websocket连接,是否允许多个连接,还是将之前的连接踢掉?或者是交由各个业务系统自己判断?
解决办法:因为是用于PC的消息系统,不能类似移动端的appKey+appSecret来进行权限校验,用户名和密码容易泄露,故采取token,由各业务系统自己来验证。同时,一次登录多次连接和同一用户多处登录的问题也交由业务系统自己判断。比如建立连接的url为http://ip:port/message/{sysCode}/{userId}/{token}。
-
- sysCode为业务系统代码,用于区分哪个业务系统的用户,取值不能重复
- userId为用户唯一标识,同一个业务系统代码内不能重复。
- token为权限校验参数,消息系统会将这个参数发到对应业务系统,由业务系统判断是否合法,若合法才能成功建立接连。
MQ选型
这里考虑了kafka和redis两种方案
- kafka方案。所有消息放在一个topic中,每个连接服务的consumergroup不同(广播),每条消息会发到每个连接服务,连接服务自己判断是否消息接收者是否连接在自己这台机器上,如果是则发送消息,如果不是则忽略。
- 缺点:一台连接服务挂掉后,用户重连到另一台机器上,因两台机器的offset不同,有可能出现消息漏发或重发。
- redis方案。每个用户一个list,每个连接服务只监听和自己保持连接的的用户的list,每次pop到新消息就通过连接发送。
- 缺点:成熟度问题。redis主要用来做缓存,做MQ是否合适有待考量。
综合考虑,用kafka。原因:问题明确,且出现几率小,后期可通过其他的策略来避免。redis做MQ因为不够成熟,不确定性大故弃用。
日志查询
已发送的消息,未成功发送的消息。本期只做已发送消息的查询,未发送的不做。具体实现为,在连接服务中落地,消息发送后,将消息存到DB,供后续查询用。