Nacos 长连接推送模型解析
haoyann Lv1

Nacos 2.0 使用 gRPC 代替 1.x 版本的 HTTP 短链接模型,实现服务注册、监听、心跳上报等功能,对比 1.x 的版本性能提升了10倍 ,相关 压测报告。本文尝试解析 Nacos 2.0 架构的注册和推送模型以及对 gRPC 连接的管理,本文只分析 naming 模块的内容, config 以及其他模块暂不涉及。

什么是gRPC

gRPC 是 Google 开源的一个基于 HTTP 2 协议的 RPC 框架,使用 Protocol buffers 定义服务,具有丰富的请求—响应模型,如 unary RPC、Server-side streaming RPC、Client-side streaming RPC 和 Bidirectional streaming RPC。

Nacos 定义了两个 rpc 方法,分别是 unary RPC 类型的 Request 和 Bidirectional streaming RPC 类型的 BiRequestStreamRequest 方法用于业务请求如服务查询、服务注册、服务订阅等与业务属性相关的请求,而 BiRequestStream 用与连接的管理,这里的连接是 Nacos 内部的概念后文会提到。

1
2
3
4
5
6
7
8
9
10
11
 service Request {
// Sends a commonRequest
rpc request (Payload) returns (Payload) {
}
}

service BiRequestStream {
// Sends a commonRequest
rpc requestBiStream (stream Payload) returns (stream Payload) {
}
}

基础模型

使用 gRPC 做为客户端与服务端的通讯协议之后,客户端的所有请求如注册、订阅都是通过同一个连接与同一个服务端进行通讯。服务端需要对客户端连接和通过该连接注册的数据进行管理,因此抽象出了ConnectionClient 这两个对象。

  • Connection: 理论上一个客户端只会在一个服务端上创建一个 Connection 对象,在客户端启动的时候创建,服务端可以通过此对象推送数据到客户端

  • Client: 一个Connection 对象对应一个 Client 对象,ClientConnection 创建的时候同步创建。负责存放由客户端注册的实例和订阅对象。当 Connection 断开的时候,对应的 Client 中的数据也会清空,反映在业务上便是,客户端连接断开之后由该客户端注册的实例和订阅者数据都清除了。

模型之间的对应关系如下,gRPC 客户端与服务端的 ConnectionClient 一一对应,其中 Client 包含gRPC 客户端所创建的所有数据。

连接模型

服务端启动

这里的服务端指的是 gRPC 服务的启动流程,而非 Nacos 整个服务端的启动流程。服务端的 gRPC 启动流程比较简单,主要关注两个 filter 和两个方法实现即可。

代码的主要入口在 com.alibaba.nacos.core.remote.grpc.BaseGrpcServer ,是一个抽象类。有两个实现类 GrpcSdkServerGrpcClusterServer ,顾名思义分别代表处理 SDK客户端的请求和处理服务端其他节点的请求。两者的差异在于响应不同的请求如 SDK 的请求由 GrpcSdkServer 处理,集群其他节点由的请求由GrpcClusterServer 处理,其他流程都一样。

两个 Filter

  1. ServerTransportFilter: 在 gRPC 连接就绪和断开的时候触发。当连接就绪的时候,将设置客户端相关属性如客户端IP、端口、 Connection Id 等信息设置到当前连接上下文中。当连接断开的时候,会将该连接的 Connection Id 所对应的 Connection 关闭。

  2. ServerInterceptor: 在客户端每次请求的时候生效。负责将 ServerTransportFilter 设置的属性设置到 RPC 上下文中,便于后续请求处理的时候可以获取客户端的相关信息,最主要是 Connection Id。

两个方法

在上文就提到,Nacos 定义了两个 gRPC 方法,RequestBiRequestStream ,这里简单介绍一下这两个方法的实现逻辑。

  1. BiRequestStream :由GrpcBiStreamRequestAcceptor 实现,负责 Connection 对象的创建。

    当接受到客户端的 ConnectionSetupRequest 请求之后,会根据客户端的信息如Connection Id 、IP、端口等生成一个 Connection 对象并将其放入到 connectionManager 中,connectionManager 其实就是一个 ConcurrentHashMap 用于存放 Connection 对象,Key 为 Connection Id。

    当放入 connectionManager 成功之后,将会触发内部 Connection 对象连接成功事件,Client 对象也就在此同步创建。至此上文提到的数据模型 gRPC 客户端、 ConnectionClient 便通过 Connection Id 对应起来。

  2. Request :由 GrpcRequestAcceptor 实现,负责处理各种业务请求。

    该方法的处理整体流程比较清晰,在做一个必要的参数检查之后会根据 Request 对象获取一个 RequestHandler 的对象进行请求处理,使用策略模式将客户端的不同请求和处理区分开,如服务注册、服务订阅。

    请求实现

客户端启动

客户端的启动流程比较复杂,为了方便理解这里就只针对主链路进行梳理。

gRPC 客户端创建的时机在 NamingService 初始化的时候,代码入口在 NamingGrpcClientProxy ,有两部分需要注意 NamingGrpcRedoServicestart 方法。

NamingGrpcRedoService

从名称可以看出是一个重试服务。上文提到当服务端与客户端连接断开时会将该连接上关联的所有数据删除。由于存在网络波动或者节点故障的情况,当客户端通过重试与服务端再次连接的时候,客户端需要将之前的数据重新注册动服务端。

NamingGrpcRedoService 会缓存客户端所有注册的实例和订阅者信息,当客户端与原来服务端重新连接或者连接到集群另外的节点上时,会将这些数据重新注册上去。

start

start方法的流程比较长。首先是两个任务,一个负责监听当前客户端的连接状态,当状态发生改变的时候会唤醒连接事件的监听者,如上文的 NamingGrpcRedoService

另外一个任务有两个作用,一是健康检查,定时请求服务端确保当前连接是可用的,如果健康检查失败将会重新连接服务端,二是如果当前客户端没有可用连接,它会在服务列表中选取一个服务端进行连接,直到连接创建成功。

上面两个任务都是异步执行的,接下来看 start 的同步流程,将会开始连接服务端,如果重试三次都失败之后,将由上文提到的任务来负责对服务端的连接。

接着看一下客户端连接到服务端的具体流程,首先会创建 gRPC 的 Stub ,创建成功之后将会发起 ServerCheckRequest 的请求,请求通过之后再创建 biRequestStreamStub 并发送 ConnectionSetupRequest 请求。在上文讲服务端启动的时候提到服务端在接受到客户端的 ConnectionSetupRequest 请求时会创建 ConnectionClient 对象。因此在连接客户端的代码中也会看到下面一段代码,会有 100 毫秒 的 sleep 等到服务端创建完 Connection 对象。

1
2
//wait to register connection setup
Thread.sleep(100L);

等所有的流程都成功之后,客户端的连接便创建完成了,将发出连接已创建的通知。

连接管理

由上文的分析发现客户端与服务端连接有着至关重要的重要,不仅是作用在通讯场景中还承载的业务数据,所以对连接的管理是非常重要的。上文或多或少的提到一些服务端或客户端对连接的管理,这里尝试汇总一下对连接管理的所有操作。

连接的管理主要由两个目标保证连接的健康度和负载均衡。

  • 连接的健康度:

    1. 服务端通过 ServerTransportFilter ,在 gRPC 连接断开的时候关闭 Connection 对象。

    2. 客户端定时检测,客户端会定时发送健康检查的请求给服务端,如果检测失败,客户端将会重新连接服务端。

    3. 服务端定时检测,服务端也会定时监测 Connection 对象。客户端每次请求的时候都会刷新 ConnectionlastActiveTime 属性,如果客户端上次活跃的时间超过了一定的阈值,服务端将会请求客户端,如果请求失败了将关闭 Connection

  • 负载均衡:

    因为 gRPC 是长连接的模式,可能会出现连接不均衡的情况,所以希望服务端集群的连接保持在比较均衡的状态。

    当前通过运维接口方式手动调整,展示整个集群的连接负载和当前节点的负载判断负载是否均衡,并且支持连接转移的功能,将连接从当前节点转移到其他节点或者指定的节点上。

连接的整体生命周期如下图

连接管理

服务推送流程

在分析完对连接的管理之后,再看服务推送就比较简单了。首先是服务推送的时机:

  • 服务实例发生变化,如客户端注册、移除实例;

  • Connection 移除,对应 Client 中所包含的实例都会移除;

  • 当客户端首次订阅的时候;

推送事件由三个关键属性组成,service 变更的服务,pushToAll 是否推送到所有监听者,targetClients 目标客户端,如客户端首次订阅的触发的推送事件就会指定推送给当前客户端。

为了避免网络抖动出现的频繁推送的情况,推送事件支持延迟推送和合并推送,默认 500 毫秒内相同服务的推送事件会进行合并。

整体的推送流程如下图

推送流程

总结

Nacos 使用 gRPC 做为通讯协议之后,可以学习到是如何对长连接的进行的管理,以及在此之上封装的业务模型。

 Comments