Nacos 2.x在服务端与客户端直接增加了GRPC通信方式,本文通过2.0.2版本源码,简单分析GRPC通信方式:

  • 服务器启动
  • 客户端连接
  • 客户端心跳
  • 服务器监控检查

服务器

proto文件

api/src/main/proto/nacos_grpc_service.proto文件:

syntax = "proto3";

import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto"; option java_multiple_files = true;
option java_package = "com.alibaba.nacos.api.grpc.auto"; message Metadata {
string type = 3; // 请求/响应的真实类型
string clientIp = 8;
map<string, string> headers = 7;
} // GRPC通信层请求/响应体
message Payload {
Metadata metadata = 2;
// 业务层的请求/响应体,需要使用type做反序列化
google.protobuf.Any body = 3;
} service RequestStream {
// build a streamRequest
rpc requestStream (Payload) returns (stream Payload) {
}
} service Request {
// Sends a commonRequest
rpc request (Payload) returns (Payload) {
}
} service BiRequestStream {
// Sends a commonRequest
rpc requestBiStream (stream Payload) returns (stream Payload) {
}
}

文件定义了通信层的service和message结构,业务层请求响应的序列化和反序列化是Nacos在RequestAcceptor/Connection中使用工具类实现的,业务层请求处理是在RequestAcceptor中进行的转发。

服务器启动

Server类继承关系

BaseRpcServer
|-- BaseGrpcServer
|-- GrpcSdkServer
|-- GrpcClusterServer

此处介绍一下GrpcSdkServer实现。

GrpcSdkServer类

@Service
public class GrpcSdkServer extends BaseGrpcServer { // 所以SDK服务器的监听端口是9848
private static final int PORT_OFFSET = 1000; @Override
public int rpcPortOffset() {
return PORT_OFFSET;
} @Override
public ThreadPoolExecutor getRpcExecutor() {
return GlobalExecutor.sdkRpcExecutor;
}
}

大部分的启动逻辑在BaseGrpcServer中。

BaseGrpcServer类

GRPC服务器的启动逻辑大部分都在这个类的startServer方法。

  1. 将处理请求的RequestAcceptor注册到HandlerRegistry

    • GrpcRequestAcceptor用于处理普通业务请求
    • GrpcBiStreamRequestAcceptor用于处理连接建立请求,获取Channel创建GrpcConnection并注册到ConnectionManager中,后续向客户端发送消息都是使用GrpcConnection做的
  2. 创建GRPC的Server对象
    • 设置port和executor
    • 设置HandlerRegistry
    • 添加ServerTransportFilter在连接建立和断开时做一些业务操作
  3. 启动Server

GrpcRequestAcceptor类

这个类对GRPC做了扩展,重写了request方法:

  1. 解析Payload获取请求体的数据类型
  2. 从RequestHandlerRegistry获取适配的RequestHandler处理器
  3. 将请求体反序列化成请求体类型对象
  4. 调用handleRequest方法处理请求返回响应

处理请求代码:

Request request = (Request) parseObj;
try {
// 获取Connection
Connection connection = connectionManager.getConnection(CONTEXT_KEY_CONN_ID.get());
RequestMeta requestMeta = new RequestMeta();
requestMeta.setClientIp(connection.getMetaInfo().getClientIp());
requestMeta.setConnectionId(CONTEXT_KEY_CONN_ID.get());
requestMeta.setClientVersion(connection.getMetaInfo().getVersion());
requestMeta.setLabels(connection.getMetaInfo().getLabels());
// 刷新活跃时间,后续的健康检查会使用到这个时间戳
connectionManager.refreshActiveTime(requestMeta.getConnectionId());
// 使用RequestHandler处理请求
Response response = requestHandler.handleRequest(request, requestMeta);
Payload payloadResponse = GrpcUtils.convert(response);
traceIfNecessary(payloadResponse, false);
responseObserver.onNext(payloadResponse);
responseObserver.onCompleted();
} catch (Throwable e) {
Payload payloadResponse = GrpcUtils.convert(buildErrorResponse(
(e instanceof NacosException) ? ((NacosException) e).getErrCode() : ResponseCode.FAIL.getCode(),
e.getMessage()));
traceIfNecessary(payloadResponse, false);
responseObserver.onNext(payloadResponse);
responseObserver.onCompleted();
}

RequestHandler处理器

RequestHandler抽象类是Nacos在业务层处理GRPC请求的抽象类:

public abstract class RequestHandler<T extends Request, S extends Response> {

    @Autowired
private RequestFilters requestFilters; /**
* Handler request.
*/
public Response handleRequest(T request, RequestMeta meta) throws NacosException {
for (AbstractRequestFilter filter : requestFilters.filters) {
try {
Response filterResult = filter.filter(request, meta, this.getClass());
if (filterResult != null && !filterResult.isSuccess()) {
return filterResult;
}
} catch (Throwable throwable) {
Loggers.REMOTE.error("filter error", throwable);
}
}
return handle(request, meta);
} /**
* Handler request.
*/
public abstract S handle(T request, RequestMeta meta) throws NacosException;
}

实现类:

Nacos使用RequestHandlerRegistry管理所有的RequestHandler,是一个Map结构:

// key是Request类型的简单名
// value是RequestHandler实现类对象
Map<String, RequestHandler> registryHandlers = new HashMap<String, RequestHandler>();

RequestHandlerRegistry会扫描Spring容器里面所有的RequestHandler对象,解析RequestHandler实现类处理的Request类型的简单名,将其注册到registryHandlers中。

GrpcRequestAcceptor类获取适配的RequestHandler处理器使用的就是RequestHandlerRegistry类的getByRequestType方法:

public RequestHandler getByRequestType(String requestType) {
return registryHandlers.get(requestType);
}

建立连接

在Server初始化的时候,Nacos注册了ServerInterceptor和ServerTransportFilter组件,这些组件会在连接建立时将conn_id、remote_ip、remote_port、local_port、ctx_channel等绑定到Context上。

创建GrpcConnection

客户端在连接建立之后会发送一个ConnectionSetupRequest请求,服务器使用GrpcBiStreamRequestAcceptor处理该请求:

  1. 获取到conn_id、remote_ip、remote_port、local_port等
  2. 解析请求获取clienIp
  3. 封装GrpcConnection对象,包括:conn_id、remote_ip、remote_port、local_port、clientIp、客户端版本等基础信息,以及StreamObserver和Channel
  4. 将GrpcConnection注册到ConnectionManager上

创建Client

ConnectionManager的注册操作会触发ConnectionBasedClientManager的clientConnected方法来创建Client对象:

public void clientConnected(Connection connect) {
// grpc类型
String type = connect.getMetaInfo().getConnectType();
// 此处获取到的是ConnectionBasedClientFactory对象
ClientFactory clientFactory = ClientFactoryHolder.getInstance().findClientFactory(type);
// 此处创建的是ConnectionBasedClient对象
clientConnected(clientFactory.newClient(connect.getMetaInfo().getConnectionId()));
} public boolean clientConnected(Client client) {
if (!clients.containsKey(client.getClientId())) {
// 注册到client集
// 使用Map维护clientId->client对象关系
clients.putIfAbsent(client.getClientId(), (ConnectionBasedClient) client);
}
return true;
}

健康检查

ConnectionManager连接管理器

这个类管理客户端连接,提供注册连接、移除连接等功能:

// 管理IP -> 连接数,用于实现ConnectionLimitRule
private Map<String, AtomicInteger> connectionForClientIp = new ConcurrentHashMap<String, AtomicInteger>(16);
// 管理connectionId -> Connection
Map<String, Connection> connections = new ConcurrentHashMap<String, Connection>();

Connection抽象类实现了Requester接口,能够向客户端发送请求、管理连接状态。

GrpcConnection实现了Connection抽象类。

在连接建立后,客户端会发送一个ConnectionSetupRequest请求,服务端收到该请求后,会解析出connectionId、客户端IP、客户端端口、客户端版本、Channel等封装成GrpcConnection对象,然后注册到ConnectionManager中。

健康检查周期任务

ConnectionManager在启动阶段会启动一个周期任务来检查IP连接数和连接的活跃状态,每3秒执行一次:

  1. 遍历连接集,使用connectionLimitRule查找需要重置的连接,向这些客户端发reset请求重置连接
  2. 获取连接的最后活跃时间(客户端每次请求都会更新这个时间),如果超过20秒不活跃,则向客户端发送一个探测请求,如果请求失败则断开连接

断开连接

业务处理流程

GRPC连接层检测到连接断开之后,会触发GrpcServer的transportTerminated事件:

public void transportTerminated(Attributes transportAttrs) {
String connectionId = null;
try {
connectionId = transportAttrs.get(TRANS_KEY_CONN_ID);
} catch (Exception e) {
// Ignore
}
if (StringUtils.isNotBlank(connectionId)) {
// 使用ConnectionManager移除连接
connectionManager.unregister(connectionId);
}
}

ConnectionManager移除连接:

public synchronized void unregister(String connectionId) {
// 从Connection集移除连接
Connection remove = this.connections.remove(connectionId);
if (remove != null) {
String clientIp = remove.getMetaInfo().clientIp;
AtomicInteger atomicInteger = connectionForClientIp.get(clientIp);
// IP连接数--
if (atomicInteger != null) {
int count = atomicInteger.decrementAndGet();
if (count <= 0) {
connectionForClientIp.remove(clientIp);
}
}
remove.close();
// 通知ClientManager层移除client对象
clientConnectionEventListenerRegistry.notifyClientDisConnected(remove);
}
}

ConnectionBasedClientManager的clientDisconnected方法:

public boolean clientDisconnected(String clientId) {
ConnectionBasedClient client = clients.remove(clientId);
if (null == client) {
return true;
}
client.release();
// 推送一个ClientDisconnectEvent事件
NotifyCenter.publishEvent(new ClientEvent.ClientDisconnectEvent(client));
return true;
}

事件处理流程

ClientDisconnectEvent事件:Client disconnect event. Happened when Client disconnect with server.

  • ClientServiceIndexesManager - 维护注册和订阅关系
  • DistroClientDataProcessor - 同步客户端数据到所有服务节点
  • NamingMetadataManager - 维护客户端注册的服务和实例元数据信息

客户端

建立连接

ServerListFactory接口

Server list factory. Use to inner client to connected and switch servers.

管理Server服务器地址集合,RpcClient使用这个接口选择可用的服务器地址。

public interface ServerListFactory {

    // 选择一个可用的服务器地址 ip:port格式
String genNextServer(); // 返回当前使用的服务器地址 ip:port格式
String getCurrentServer(); // 返回服务器集合
List<String> getServerList();
}

ServerListManager类

解析Properties参数封装服务器地址集合。

创建RpcClient

RpcClientFactory.createClient(uuid, ConnectionType.GRPC, labels);

createClient方法:

public static RpcClient createClient(String clientName,
ConnectionType connectionType,
Map<String, String> labels) {
return CLIENT_MAP.compute(clientName, (clientNameInner, client) -> {
if (client == null) {
if (ConnectionType.GRPC.equals(connectionType)) {
// 创建的是GrpcSdkClient对象
client = new GrpcSdkClient(clientNameInner);
}
if (client == null) {
throw new UnsupportedOperationException(
"unsupported connection type :" + connectionType.getType());
}
client.labels(labels);
}
return client;
});
}

之后需要为Client进行初始化:

  1. 设置ServerListFactory,用于选择服务器地址

  2. 注册ServerRequestHandler处理器,用于处理服务端发送的请求,比如服务订阅的回调、配置文件变化通知

  3. 注册ConnectionEventListener监听器

    rpcClient.serverListFactory(serverListFactory);
    rpcClient.start();
    rpcClient.registerServerRequestHandler(new NamingPushRequestHandler(serviceInfoHolder));
    rpcClient.registerConnectionListener(namingGrpcConnectionEventListener);
  4. 启动Client

    • 启动ConnectionEvent处理线程
    • 启动健康检查(心跳)线程
    • 创建GrpcConnection

创建GrpcConnection

  1. 创建GRPC的RequestFutureStub和BiRequestStreamStub
  2. 发一个ServerCheckRequest请求验证服务端的可用性
  3. 创建GrpcConnection对象,封装serverInfo和executor、connectionId、channel等
  4. 为BiRequestStreamStub绑定请求处理逻辑:使用ServerRequestHandler处理器处理服务端发送过来的请求
  5. 发送ConnectionSetupRequest请求,让服务端创建并注册GrpcConnection
if (grpcExecutor == null) {
int threadNumber = ThreadUtils.getSuitableThreadCount(8);
grpcExecutor = new ThreadPoolExecutor(threadNumber, threadNumber, 10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10000),
new ThreadFactoryBuilder().setDaemon(true).setNameFormat("nacos-grpc-client-executor-%d")
.build());
grpcExecutor.allowCoreThreadTimeOut(true);
}
// 8848+1000
int port = serverInfo.getServerPort() + rpcPortOffset();
RequestGrpc.RequestFutureStub newChannelStubTemp = createNewChannelStub(serverInfo.getServerIp(), port);
// 发一个ServerCheckRequest请求验证服务端的可用性
Response response = serverCheck(serverInfo.getServerIp(), port, newChannelStubTemp);
if (response == null || !(response instanceof ServerCheckResponse)) {
shuntDownChannel((ManagedChannel) newChannelStubTemp.getChannel());
return null;
} BiRequestStreamGrpc.BiRequestStreamStub biRequestStreamStub = BiRequestStreamGrpc
.newStub(newChannelStubTemp.getChannel());
// 创建GrpcConnection对象,封装serverInfo和executor、connectionId、channel等
GrpcConnection grpcConn = new GrpcConnection(serverInfo, grpcExecutor);
grpcConn.setConnectionId(((ServerCheckResponse) response).getConnectionId()); // create stream request and bind connection event to this connection
StreamObserver<Payload> payloadStreamObserver = bindRequestStream(biRequestStreamStub, grpcConn); // stream observer to send response to server
grpcConn.setPayloadStreamObserver(payloadStreamObserver);
grpcConn.setGrpcFutureServiceStub(newChannelStubTemp);
grpcConn.setChannel((ManagedChannel) newChannelStubTemp.getChannel());
// send a setup request
ConnectionSetupRequest conSetupRequest = new ConnectionSetupRequest();
conSetupRequest.setClientVersion(VersionUtils.getFullClientVersion());
conSetupRequest.setLabels(super.getLabels());
conSetupRequest.setAbilities(super.clientAbilities);
conSetupRequest.setTenant(super.getTenant());
grpcConn.sendRequest(conSetupRequest);

发送请求

Requester接口

这个接口定义了发送请求的方法:

public interface Requester {

    /**
* send request.
*
* @param request request.
* @param timeoutMills mills of timeouts.
* @return response response returned.
* @throws NacosException exception throw.
*/
Response request(Request request, long timeoutMills) throws NacosException; /**
* send request.
*
* @param request request.
* @return request future.
* @throws NacosException exception throw.
*/
RequestFuture requestFuture(Request request) throws NacosException; /**
* send async request.
*
* @param request request.
* @param requestCallBack callback of request.
* @throws NacosException exception throw.
*/
void asyncRequest(Request request, RequestCallBack requestCallBack) throws NacosException; /**
* close connection.
*/
void close();
}

GrpcConnection实现

GrpcConnection类实现了Requester接口的三个request方法,使用的是GRPC的Stub发送请求,以request方法为例:

public Response request(Request request, long timeouts) throws NacosException {
Payload grpcRequest = GrpcUtils.convert(request);
ListenableFuture<Payload> requestFuture = grpcFutureServiceStub.request(grpcRequest);
Payload grpcResponse;
try {
// 由于request方法是同步的,所以此处阻塞等待响应
grpcResponse = requestFuture.get(timeouts, TimeUnit.MILLISECONDS);
} catch (Exception e) {
throw new NacosException(NacosException.SERVER_ERROR, e);
} return (Response) GrpcUtils.parse(grpcResponse);
}

对于另外两个方法:

  • requestFuture方法:在grpcFutureServiceStub.request(grpcRequest)发送请求之后,创建一个RequestFuture返回
  • asyncRequest方法:在grpcFutureServiceStub.request(grpcRequest)发送请求之后,为requestFuture添加监听回调

心跳healthCheck

前文介绍过,在启动RpcClient阶段,会启动健康检查任务,该任务每5秒执行一次,对当前客户端封装的connection做健康检查:

// keepAliveTime默认5000L
ReconnectContext reconnectContext = reconnectionSignal
.poll(keepAliveTime, TimeUnit.MILLISECONDS);
if (reconnectContext == null) {
// check alive time.
if (System.currentTimeMillis() - lastActiveTimeStamp >= keepAliveTime) {
// 健康检查
boolean isHealthy = healthCheck();
if (!isHealthy) {
if (currentConnection == null) {
continue;
} RpcClientStatus rpcClientStatus = RpcClient.this.rpcClientStatus.get();
if (RpcClientStatus.SHUTDOWN.equals(rpcClientStatus)) {
break;
} // 准备重连
boolean success = RpcClient.this.rpcClientStatus
.compareAndSet(rpcClientStatus, RpcClientStatus.UNHEALTHY);
if (success) {
reconnectContext = new ReconnectContext(null, false);
} else {
continue;
}
} else {
lastActiveTimeStamp = System.currentTimeMillis();
continue;
}
} else {
continue;
}
} if (reconnectContext.serverInfo != null) {
// clear recommend server if server is not in server list.
boolean serverExist = false;
for (String server : getServerListFactory().getServerList()) {
ServerInfo serverInfo = resolveServerInfo(server);
if (serverInfo.getServerIp().equals(reconnectContext.serverInfo.getServerIp())) {
serverExist = true;
reconnectContext.serverInfo.serverPort = serverInfo.serverPort;
break;
}
}
if (!serverExist) {
reconnectContext.serverInfo = null;
}
}
// 重连
reconnect(reconnectContext.serverInfo, reconnectContext.onRequestFail);

healthCheck方法:

private boolean healthCheck() {
HealthCheckRequest healthCheckRequest = new HealthCheckRequest();
if (this.currentConnection == null) {
return false;
}
try {
Response response = this.currentConnection.request(healthCheckRequest, 3000L);
// not only check server is ok ,also check connection is register.
return response != null && response.isSuccess();
} catch (NacosException e) {
// ignore
}
return false;
}

Nacos源码 (5) Grpc服务端和客户端的更多相关文章

  1. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  2. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  3. Netty 4源码解析:服务端启动

    Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...

  4. 【.NET6】gRPC服务端和客户端开发案例,以及minimal API服务、gRPC服务和传统webapi服务的访问效率大对决

    前言:随着.Net6的发布,Minimal API成了当下受人追捧的角儿.而这之前,程序之间通信效率的王者也许可以算得上是gRPC了.那么以下咱们先通过开发一个gRPC服务的教程,然后顺势而为,再接着 ...

  5. Nacos源码系列—关于服务注册的那些事

    点赞再看,养成习惯,微信搜索[牧小农]关注我获取更多资讯,风里雨里,小农等你,很高兴能够成为你的朋友. 项目源码地址:公众号回复 nacos,即可免费获取源码 简介 首先我们在看Nacos源码之前,要 ...

  6. netty4.1.6源码2-------创建服务端的channel

    1. netty在哪里调用jdk底层的socket去创建netty服务端的socket. 2. 在哪里accept连接. 服务端的启动: 1. 调用jdk底层的api去创建jdk的服务端的channe ...

  7. 4. 源码分析---SOFARPC服务端暴露

    服务端的示例 我们首先贴上我们的服务端的示例: public static void main(String[] args) { ServerConfig serverConfig = new Ser ...

  8. Netty源码分析之服务端启动过程

    一.首先来看一段服务端的示例代码: public class NettyTestServer { public void bind(int port) throws Exception{ EventL ...

  9. Spring Cloud系列(三):Eureka源码解析之服务端

    一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-starter-netflix-eureka-ser ...

  10. zookeeper源码分析之一服务端启动过程

    zookeeper简介 zookeeper是为分布式应用提供分布式协作服务的开源软件.它提供了一组简单的原子操作,分布式应用可以基于这些原子操作来实现更高层次的同步服务,配置维护,组管理和命名.zoo ...

随机推荐

  1. LIS和LCS算法分析

    LIS(最长上升子序列) 常规的解法就是动态规划. mx[ j ]表示长度为j的上升子序列最小的值a[i]; dp[ i ]表示前i个数的最长上升子序列长度多少. 1 for(int i=1;i< ...

  2. 介绍一款轻量型 Web SCADA 组态软件

    ​ 随着互联网.物联网技术的快速发展,图扑物联基于多年研发积累和私有部署实践打磨.以及对业务场景的深入理解,推出了适用于物联网应用场景的轻量型云组态软件. 该产品采用 B/S 架构,提供 Web 管理 ...

  3. 关于SpringBoot中出现的循环依赖问题

    环境: SpringBoot2.7.8 背景: 在增加出库订单时需要对物品表的的数量进行修改 因此我在OutboundController中创建了几个公共方法,并将其注入到Spring中,结果给我报了 ...

  4. 从零玩转七牛云之CDN-qiniuyunzhicdn

    title: 从零玩转七牛云之CDN date: 2022-03-27 19:14:43.036 updated: 2022-04-10 14:13:27.322 url: https://www.y ...

  5. 2023.2 IDEA安装激活教程

    1.下载安装IntelliJ IDEA 先去官网下载,我这里下载的是最新版本的2023.2,测试过2023最新版本以及2022版本以上的版本没问题. 安装然后打开 提示要输入激活码,先关闭应用,等下再 ...

  6. 【云原生 | Kubernetes 系列】— Kubernetes存储方案

    目录 [云原生 | Kubernetes 系列]- Kubernetes存储方案 一.基本存储 EmptyDir HostPath NFS 搭建nfs服务器 二.高级存储 PV和PVC pv pvc ...

  7. Blazor快速开发框架Known-V2.0.0

    Known2.0 Known是基于Blazor的企业级快速开发框架,低代码,跨平台,开箱即用,一处代码,多处运行. 官网:http://known.pumantech.com Gitee: https ...

  8. 在线编辑Word——插入公式

    在Word中可插入多种公式,用于满足于不同运算场景需求,从基本的运算符到大型的运算公式,我们可以根据文档内容的编排需要,任意插入所需公式.下面,介绍如何通过在线编辑Word的方式,向Word中插入公式 ...

  9. 云图说 | 华为云GPU共享型AI容器,让你用得起,用得好,用的放心

    摘要:容器以其独特的技术优势,已经成为业界主流的AI计算框架(如Tensorflow.Caffe)的核心引擎,为了进一步解决企业在AI计算性能与成本上面临的问题,华为云推出了AI容器产品. 容器以其独 ...

  10. 云图说|ModelArts Pro,为企业级AI应用打造的专业开发套件

    摘要: ModelArts Pro 为企业级AI应用打造专业开发套件.基于华为云的先进算法和快速训练能力,提供预置工作流和模型,提升企业AI应用的开发效率,降低开发难度. AI技术的高门槛与落地难是中 ...