长连接网关如何与后端服务通信

在长连接网关中,网关与后端服务的通信方式需要考虑长连接的特点,因此与短连接网关有所不同。

在长连接网关中,客户端会与网关建立长连接以进行通信。通常情况下,一个客户端只需要与一个网关实例建立长连接。

当客户端数量较多时,可以通过增加网关实例进行水平扩展,此外考虑稳定性等因素,一般会部署多个网关实例。如下图所示:

网关

从图中可以看到,每一个网关实例都会维护一组只属于自己的客户端连接,可见不同网关实例服务的客户端对象是完全独立的。

网关与后端服务的通信方式

客户端会向网关发起请求,后端服务也会向客户端推送数据。下面就针对这两种场景讨论下应如何通信。

##请求后端服务

客户端与一个网关实例建立长连接,然后向该网关实例发起请求。网关接到请求后,需要向后端服务发起调用。这里存在两个问题:1.如何找到对应的后端服务,并且找到可以响应请求的服务实例?2.如何调用该实例?

首先考虑一个问题:网关是同步向后端服务发起请求?还是异步发起请求?为了能够及时响应请求,需要同步调用后端服务。倘若请求量比较大、qps比较高,同时也不需要后端服务及时给出响应,那么则可以考虑异步请求后端服务。

同步调用后端服务

对于同步调用来说,可以采用 rpc 调用的方式。通常 rpc 调用的客户端一方需要引入服务方提供的 jar 包,以调用服务方的接口。

对于网关来说,这种方式显然不合适,因为需要依赖每一个服务提供方,这样就会将网关和各个业务功能提供方耦合在一起。

此时就需要用到泛化调用,它通过字符串的形式指定了要调用的接口、方法,消除了对服务提供方jar包的依赖。常见的 rpc 框架都提供了泛化调用的能力。

当新增功能时,我们还希望能够动态地新增接口调用能力,而不用重新发布网关。这就需要网关能够支持动态配置。

动态配置

异步调用后端服务

对于异步调用的方式,可以采用mq消息来实现。如下图所示,网关只需要将消息发送到 mq 集群的 broker 上,不需要等待后端服务的结果。当后端服务受到请求后,根据请求指令进行处理,然后决定是否要给客户端发送推送通知。

异步调用

这种方式非常适合一些数据上报的场景,例如日志上报等。

后端服务推送数据

后端服务有时需要向某一个客户端推送数据,此时应该如何处理呢?

与一般的 rpc 请求不同,服务端不能无差别地选择某一个网关实例发起请求,这是因为一个客户端只与一台网关实例建立了长连接,要推送到该客户端,首先要找到它连接的网关实例。

如下图所示,后端服务通过负载均衡后选择了一个网关实例,该实例并未与目标客户端建立连接,无法处理。

找不到客户端

面对这种场景,应该如何解决?

我们可以对负载均衡模块进行修改,让其可以感知到网关的连接情况。但是,这种方式需要修改 rpc 框架的底层实现,而且负载均衡模块需要维护所有的客户端连接信息,显然不是一种好的方式。

我们可以考虑一个问题:后端服务是否有必要同步调用网关?通常情况下后端服务只是向客户端做单向通知,不需要等待返回结果。而且,即使需要等待返回结果,客户端可能因为某种原因响应很慢。因此,同步调用网关不是必须的,也是不合适的。这样的话,服务端的通知就变成了下图的样子

异步通知

我们知道在 mq 的普通消费模式下,一条消息只会被一个 consumer 实例消费到,因此,这里还会有找不到客户端实例的问题。

一种解决方法是使用广播消费,这样每一个网关实例都能收到后端服务发送的所有消息,然后再根据消息的目标检查对应客户端的连接是否存在,如果存在则发送给对应客户端,否则直接丢弃。

广播模式

广播消费的缺点是offset偏移量保存在consumer侧,不是很可靠。要解决这个问题,可以针对每一网关实例分别建立一个consumerGroup,这样所有的网关实例都能收到全量消息,并且偏移量保存在broker侧,更加可靠。

多个consumer组

每个网关实例会消费到的全量的消息,解析消息内容,根据目标客户端信息查找当前维护的连接关系,如果不是自己维护的连接,则直接丢弃消息,否则向找到的连接(channel)发送消息内容