目录
1、RPC客户端
1.1、RPC客户端结构
源码中相关包结构如下图所示:
话不多说,直接上UML图
从上图可知,RPC客户端目前只有一种GRPC,而GRPC又有两种具体的实现,一种是GrpcSdkClient和GrpcClusterClient,下面我们一起来看一下他们的抽象类RpcClient。
1.2、RpcClient类
在这个类中有非常多的属性,下面我们来看看这些属性。
以下属性注释中已经给出了一些注释,有些不太清楚作用的会在之后的内容中讲到。
/**
* 服务器工厂
* 作用:在这主要是获取服务器列表的
*/
private ServerListFactory serverListFactory;
/**
* rpc连接事件阻塞队列
*/
protected LinkedBlockingQueue<ConnectionEvent> eventLinkedBlockingQueue = new LinkedBlockingQueue<ConnectionEvent>();
/**
* rpc客户端状态
*/
protected volatile AtomicReference<RpcClientStatus> rpcClientStatus = new AtomicReference<RpcClientStatus>(
RpcClientStatus.WAIT_INIT);
/**
* 周期性任务线程池
*/
protected ScheduledExecutorService clientEventExecutor;
/**
* 重试服务阻塞队列
*/
private final BlockingQueue<ReconnectContext> reconnectionSignal = new ArrayBlockingQueue<ReconnectContext>(1);
/**
* 当前rpc连接
*/
protected volatile Connection currentConnection;
/**
* 用于标注客户端请求的标签
*/
protected Map<String, String> labels = new HashMap<String, String>();
/**
* rpc客户端连接服务器重试次数
*/
private static final int RETRY_TIMES = 3;
/**
* rpc客户端请求默认的超时时间
*/
private static final long DEFAULT_TIMEOUT_MILLS = 3000L;
/**
* 表示rpc客户端能力的对象
*/
protected ClientAbilities clientAbilities;
/**
* default keep alive time 5s.
* rpc客户端与服务端连接默认保持时间为5s
*/
private long keepAliveTime = 5000L;
/**
* rpc客户端与服务端连接最后存活时间
*/
private long lastActiveTimeStamp = System.currentTimeMillis();
/**
* listener called where connection's status changed.
* 在连接状态改变的地方调用监听器
*/
protected List<ConnectionEventListener> connectionEventListeners = new ArrayList<ConnectionEventListener>();
/**
* handlers to process server push request.
* 处理服务器推送请求的处理程序
*/
protected List<ServerRequestHandler> serverRequestHandlers = new ArrayList<ServerRequestHandler>();
这里主要说一下currentConnection属性。由下图可以看出,如果使用GRPC的话,RpcClient中的Connection属性是GrpcClient中创建的GrpcConnection。connection属性是rpc通信时,与服务端建立的链接通道,用于发送一些请求用的。
1.3、RpcClient启动流程
nacos的rpcClient启动最主要的方法是start方法,下面我们来看看这个方法。
public final void start() throws NacosException {
// 修改rpc客户端状态为STARTING
boolean success = rpcClientStatus.compareAndSet(RpcClientStatus.INITIALIZED, RpcClientStatus.STARTING);
if (!success) {
return;
}
// 创建周期性任务线程池
clientEventExecutor = new ScheduledThreadPoolExecutor(2, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.remote.worker");
t.setDaemon(true);
return t;
}
});
// connection event consumer.
// 连接事件消费者,给对应监听器发送事件
clientEventExecutor.submit(new Runnable() {
@Override
public void run() {
// 判断线程池是否被关闭,被关闭则不再执行
while (!clientEventExecutor.isTerminated() && !clientEventExecutor.isShutdown()) {
ConnectionEvent take = null;
try {
// 从连接事件阻塞队列拿连接事件
// 在连接成功时,会往队列中存放连接成功的事件;连接断开时会存放连接断开的事件
take = eventLinkedBlockingQueue.take();
// 判断rpc客户端是否已经连接
if (take.isConnected()) {
// 执行连接成功的监听器(通知重试机制已经连接,不需要再重试)
notifyConnected();
} else if (take.isDisConnected()) {
// 执行连接断开的监听器(通知重试机制未连接,需要重试)
notifyDisConnected();
}
} catch (Throwable e) {
//Do nothing
}
}
}
});
// 服务端健康检查及重连服务,有重连的情况则不会进行健康检查,健康检查的前提是所有连接都健康
clientEventExecutor.submit(new Runnable() {
@Override
public void run() {
while (true) {
try {
// 如果rpc客户端关闭则终止循环
if (isShutdown()) {
break;
}
// 获取ReconnectContext(重连上下文对象)对象,默认5秒内取不到则返回null
ReconnectContext reconnectContext = reconnectionSignal
.poll(keepAliveTime, TimeUnit.MILLISECONDS);
// 1、如果没有拿到ReconnectContext对象,则表示没有重连的需要,则会检查server的健康状况,
// 如果不健康,则会创建一个空的重连上下文对象,后面会选择一个server重连
if (reconnectContext == null) {
//check alive time.
// 检查存活时间
// 如果没有重连的需求,这里每隔默认5秒会去检查server健康情况
if (System.currentTimeMillis() - lastActiveTimeStamp >= keepAliveTime) {
// 判断当前连接是否健康
boolean isHealthy = healthCheck();
if (!isHealthy) {
// 如果currentConnection为空,表明还在启动连接server的情况,并不是server那边不健康的问题
if (currentConnection == null) {
continue;
}
// 执行到这里表示,rpc客户端和服务端连接有异常
LoggerUtils.printIfInfoEnabled(LOGGER,
"[{}]Server healthy check fail,currentConnection={}", name,
currentConnection.getConnectionId());
RpcClientStatus rpcClientStatus = RpcClient.this.rpcClientStatus.get();
// 在发起重连之前,这里再次判断rpc客户端是否被关闭(这里有可能客户端被关闭了,如果不判断的话SHUTDOWN又被改为UNHEALTHY是不合理的)
if (RpcClientStatus.SHUTDOWN.equals(rpcClientStatus)) {
// 如果已经关闭连接,则中断循环
break;
}
// 设置客户端不健康状态(这里必须使用cas来更新,因为此时状态可能被修改了,可能会存在SHUTDOWN->UNHEALTHY)
boolean success = RpcClient.this.rpcClientStatus
.compareAndSet(rpcClientStatus, RpcClientStatus.UNHEALTHY);
// TODO 这个地方状态可能会变成RUNNING
if (success) {
// 如果客户端不健康状态设置成功,则设置一个空的重新连接上下文对象(相当于重新选一个随机的server来重连)
reconnectContext = new ReconnectContext(null, false);
} else {
// 如果设置失败,表示状态在更新之前发生了改变,则选择重试
// 可能是状态期间被改为SHUTDOWN了,也可能是状态期间被改为RUNNING
// 因为在代码①处是先赋值新连接,后修改状态的。可能会出现这个问题,所以这里选择continue
continue;
}
} else {
// 健康则更新最后的活跃时间
lastActiveTimeStamp = System.currentTimeMillis();
continue;
}
} else {
continue;
}
}
// 2、有重连的需求,如果ReconnectContext对象是有指定的server,则检查server是否存在,如果不存在则将ReconnectContext设置为空
// 之后自己会选取一个server
if (reconnectContext.serverInfo != null) {
//clear recommend server if server is not in server list.
boolean serverExist = false;
// 从serverListFactory中获取服务器列表
for (String server : getServerListFactory().getServerList()) {
ServerInfo serverInfo = resolveServerInfo(server);
// 判断服务器是否存在
if (serverInfo.getServerIp().equals(reconnectContext.serverInfo.getServerIp())) {
serverExist = true;
reconnectContext.serverInfo.serverPort = serverInfo.serverPort;
break;
}
}
// 如果给到的server信息不存在,则自己选取一个
if (!serverExist) {
LoggerUtils.printIfInfoEnabled(LOGGER,
"[{}] Recommend server is not in server list ,ignore recommend server {}", name,
reconnectContext.serverInfo.getAddress());
reconnectContext.serverInfo = null;
}
}
// 3、重新连接server,关闭之前的连接
reconnect(reconnectContext.serverInfo, reconnectContext.onRequestFail);
} catch (Throwable throwable) {
//Do nothing
}
}
}
});
//connect to server ,try to connect to server sync once, async starting if fail.
// 连接到服务器,尝试连接到服务器同步一次,如果失败异步启动
Connection connectToServer = null;
// TODO 这段我认为没什么必要,在这期间状态是不会被改变的
rpcClientStatus.set(RpcClientStatus.STARTING);
int startUpRetryTimes = RETRY_TIMES;
while (startUpRetryTimes > 0 && connectToServer == null) {
try {
startUpRetryTimes--;
// 获取服务器信息
ServerInfo serverInfo = nextRpcServer();
LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Try to connect to server on start up, server: {}", name,
serverInfo);
connectToServer = connectToServer(serverInfo);
} catch (Throwable e) {
LoggerUtils.printIfWarnEnabled(LOGGER,
"[{}]Fail to connect to server on start up, error message={}, start up retry times left: {}",
name, e.getMessage(), startUpRetryTimes);
}
}
// 判断是否连接成功
if (connectToServer != null) {
LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Success to connect to server [{}] on start up,connectionId={}",
name, connectToServer.serverInfo.getAddress(), connectToServer.getConnectionId());
this.currentConnection = connectToServer; // ①
rpcClientStatus.set(RpcClientStatus.RUNNING);
// 添加连接成功事件
eventLinkedBlockingQueue.offer(new ConnectionEvent(ConnectionEvent.CONNECTED));
} else {
// 要是没有一个服务端能连接,则尝试随机重连
switchServerAsync();
}
// 监听重新设置连接请求
registerServerRequestHandler(new ConnectResetRequestHandler());
//register client detection request.
registerServerRequestHandler(new ServerRequestHandler() {
@Override
public Response requestReply(Request request) {
if (request instanceof ClientDetectionRequest) {
return new ClientDetectionResponse();
}
return null;
}
});
}
start主要做了几件事
- 开启定时任务1,用于消费连接事件。用于通知ConnectionEventListener监听器,在连接成功或断开的时候会触发
- 开启定时任务2,在没有重连信号的时候每隔一段时间会进行健康检查,如果有重连信号则重新选择一个rpc服务端进行连接
- 自己去尝试连接rpc服务端,尝试几次如果都失败了,则发送重连信号到队列中,定时任务2会检测到重连信号进行重连
- 注册服务请求处理器,一个是用来监听服务端发送过来的重连请求的,还有一个是用来监听服务端发送过来的存活检测请求的
上面简单的说了一下大概的功能,下面具体展开聊聊。
第一个定时任务
clientEventExecutor.submit(new Runnable() {
@Override
public void run() {
// 判断线程池是否被关闭,被关闭则不再执行
while (!clientEventExecutor.isTerminated() && !clientEventExecutor.isShutdown()) {
ConnectionEvent take = null;
try {
// 从连接事件阻塞队列拿连接事件
// 在连接成功时,会往队列中存放连接成功的事件;连接断开时会存放连接断开的事件
take = eventLinkedBlockingQueue.take();
// 判断rpc客户端是否已经连接
if (take.isConnected()) {
// 执行连接成功的监听器(通知重试机制已经连接,不需要再重试)
notifyConnected();
} else if (take.isDisConnected()) {
// 执行连接断开的监听器(通知重试机制未连接,需要重试)
notifyDisConnected();
}
} catch (Throwable e) {
//Do nothing
}
}
}
});
这个定时任务会消费连接事件,其实本身ConnectionEventListener监听器暂时只有NamingGrpcRedoService一个,这个类的作用主要是用来grpc重连服务的,具体的实现会在之后的client编详细来讲,这块都简单带过~
第二个定时任务
clientEventExecutor.submit(new Runnable() {
@Override
public void run() {
while (true) {
try {
// 如果rpc客户端关闭则终止循环
if (isShutdown()) {
break;
}
// 获取ReconnectContext(重连上下文对象)对象,默认5秒内取不到则返回null
ReconnectContext reconnectContext = reconnectionSignal
.poll(keepAliveTime, TimeUnit.MILLISECONDS);
// 1、如果没有拿到ReconnectContext对象,则表示没有重连的需要,则会检查server的健康状况,
// 如果不健康,则会创建一个空的重连上下文对象,后面会选择一个server重连
if (reconnectContext == null) {
//check alive time.
// 检查存活时间
// 如果没有重连的需求,这里每隔默认5秒会去检查server健康情况
if (System.currentTimeMillis() - lastActiveTimeStamp >= keepAliveTime) {
// 判断当前连接是否健康
boolean isHealthy = healthCheck();
if (!isHealthy) {
// 如果currentConnection为空,表明还在启动连接server的情况,并不是server那边不健康的问题
if (currentConnection == null) {
continue;
}
// 执行到这里表示,rpc客户端和服务端连接有异常
LoggerUtils.printIfInfoEnabled(LOGGER,
"[{}]Server healthy check fail,currentConnection={}", name,
currentConnection.getConnectionId());
RpcClientStatus rpcClientStatus = RpcClient.this.rpcClientStatus.get();
// 在发起重连之前,这里再次判断rpc客户端是否被关闭(这里有可能客户端被关闭了,如果不判断的话SHUTDOWN又被改为UNHEALTHY是不合理的)
if (RpcClientStatus.SHUTDOWN.equals(rpcClientStatus)) {
// 如果已经关闭连接,则中断循环
break;
}
// 设置客户端不健康状态(这里必须使用cas来更新,因为此时状态可能被修改了,可能会存在SHUTDOWN->UNHEALTHY)
boolean success = RpcClient.this.rpcClientStatus
.compareAndSet(rpcClientStatus, RpcClientStatus.UNHEALTHY);
// TODO 这个地方状态可能会变成RUNNING
if (success) {
// 如果客户端不健康状态设置成功,则设置一个空的重新连接上下文对象(相当于重新选一个随机的server来重连)
reconnectContext = new ReconnectContext(null, false);
} else {
// 如果设置失败,表示状态在更新之前发生了改变,则选择重试
// 可能是状态期间被改为SHUTDOWN了,也可能是状态期间被改为RUNNING
// 因为在代码①处是先赋值新连接,后修改状态的。可能会出现这个问题,所以这里选择continue
continue;
}
} else {
// 健康则更新最后的活跃时间
lastActiveTimeStamp = System.currentTimeMillis();
continue;
}
} else {
continue;
}
}
// 2、有重连的需求,如果ReconnectContext对象是有指定的server,则检查server是否存在,如果不存在则将ReconnectContext设置为空
// 之后自己会选取一个server
if (reconnectContext.serverInfo != null) {
//clear recommend server if server is not in server list.
boolean serverExist = false;
// 从serverListFactory中获取服务器列表
for (String server : getServerListFactory().getServerList()) {
ServerInfo serverInfo = resolveServerInfo(server);
// 判断服务器是否存在
if (serverInfo.getServerIp().equals(reconnectContext.serverInfo.getServerIp())) {
serverExist = true;
reconnectContext.serverInfo.serverPort = serverInfo.serverPort;
break;
}
}
// 如果给到的server信息不存在,则自己选取一个
if (!serverExist) {
LoggerUtils.printIfInfoEnabled(LOGGER,
"[{}] Recommend server is not in server list ,ignore recommend server {}", name,
reconnectContext.serverInfo.getAddress());
reconnectContext.serverInfo = null;
}
}
// 3、重新连接server,关闭之前的连接
reconnect(reconnectContext.serverInfo, reconnectContext.onRequestFail);
} catch (Throwable throwable) {
//Do nothing
}
}
}
});
这个定时任务会定时的从reconnectionSignal阻塞队列中取重连上下文对象,默认5s没有取到对象的话则会进行健康检查,如果不健康,则会经过一系列的判断给rpcClient状态改为不健康的,并往reconnectionSignal队列中设置重连上下文对象;如果取到重连上下文对象则判断是否有指定的服务端,如果没有则选择一个rpc服务端进行连接。
在上面方法中,关键的方法是这个reconnect方法,在reconnect方法之前的逻辑也都只是判断选取服务端而已,真正的重连逻辑在reconnect方法中。
protected void reconnect(final ServerInfo recommendServerInfo, boolean onRequestFail) {
try {
AtomicReference<ServerInfo> recommendServer = new AtomicReference<ServerInfo>(recommendServerInfo);
// 判断服务端是否健康,当前请求是否失败
// onRequestFail为true,则表示rpc请求失败了,如果请求失败了,之后再次检查server是否健康
// 如果是false,则可能是服务端的重连请求,则不再检查服务端的健康状态
// 如果健康,则重新设置回RUNNING状态,不继续进行重连行为
if (onRequestFail && healthCheck()) {
LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Server check success,currentServer is{} ", name,
currentConnection.serverInfo.getAddress());
// 有可能会出现这样的情况,在shutdown方法中设置状态为SHUTDOWN之后,这里还会继续执行改为RUNNING。但是实际上也只是状态的改变,关闭线程池之后不会影响
// 之前修改状态代码如下:
// RpcClientStatus rpcClientStatus = RpcClient.this.rpcClientStatus.get();
// if (RpcClientStatus.SHUTDOWN.equals(rpcClientStatus)) {
// break;
// }
// boolean success = RpcClient.this.rpcClientStatus.compareAndSet(rpcClientStatus, RpcClientStatus.UNHEALTHY);
// 使用cas来保证状态是因为是怕在关闭线程池之后还创建了重连对象,之后因为状态是UNHEALTHY,还会继续往下执行重连的操作,
// 因为rpc连接被关闭后再次调用该连接去访问服务器会导致后续一些资源浪费(比如说关闭之前又创建了rpc连接,浪费性能)。
// 而这里但存的将状态改为RUNNING是不会产生这些的,虽然会导致最后状态有问题,但是最后还是关闭了,不会影响。
rpcClientStatus.set(RpcClientStatus.RUNNING);
return;
}
LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] try to re connect to a new server ,server is {}", name,
recommendServerInfo == null ? " not appointed,will choose a random server."
: (recommendServerInfo.getAddress() + ", will try it once."));
// loop until start client success.
// 循环直到客户端启动成功
boolean switchSuccess = false;
// 重试次数
int reConnectTimes = 0;
// 重试了几轮
int retryTurns = 0;
Exception lastException = null;
// 如果没有切换成功并且client没有关闭,则继续连接
while (!switchSuccess && !isShutdown()) {
//1.get a new server
// 获取到新的服务端
ServerInfo serverInfo = null;
try {
// 如果没有指定server信息,则会重新轮询选取一个
serverInfo = recommendServer.get() == null ? nextRpcServer() : recommendServer.get();
//2.create a new channel to new server
// 根据server信息,创建新服务端的新通道
Connection connectionNew = connectToServer(serverInfo);
// 成功创建新连接,则如果有老连接对象则替换,并发布连接成功事件
if (connectionNew != null) {
LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] success to connect a server [{}],connectionId={}",
name, serverInfo.getAddress(), connectionNew.getConnectionId());
//successfully create a new connect.
// 成功创建一个新连接
if (currentConnection != null) {
LoggerUtils.printIfInfoEnabled(LOGGER,
"[{}] Abandon prev connection ,server is {}, connectionId is {}", name,
currentConnection.serverInfo.getAddress(), currentConnection.getConnectionId());
//set current connection to enable connection event.
// 标注废弃该连接
currentConnection.setAbandon(true);
// 关闭当前连接
closeConnection(currentConnection);
}
// 新连接替换旧连接
currentConnection = connectionNew;
rpcClientStatus.set(RpcClientStatus.RUNNING);
switchSuccess = true;
// 发布已连接事件
boolean s = eventLinkedBlockingQueue.add(new ConnectionEvent(ConnectionEvent.CONNECTED));
return;
}
//close connection if client is already shutdown.
// 如果客户端已经关闭,则关闭连接
if (isShutdown()) {
closeConnection(currentConnection);
}
lastException = null;
} catch (Exception e) {
lastException = e;
} finally {
recommendServer.set(null);
}
if (reConnectTimes > 0
&& reConnectTimes % RpcClient.this.serverListFactory.getServerList().size() == 0) {
LoggerUtils.printIfInfoEnabled(LOGGER,
"[{}] fail to connect server,after trying {} times, last try server is {},error={}", name,
reConnectTimes, serverInfo, lastException == null ? "unknown" : lastException);
if (Integer.MAX_VALUE == retryTurns) {
retryTurns = 50;
} else {
// 服务器循环了一轮则加一
retryTurns++;
}
}
reConnectTimes++;
try {
//sleep x milliseconds to switch next server.
if (!isRunning()) {
// first round ,try servers at a delay 100ms;second round ,200ms; max delays 5s. to be reconsidered.
// 第一轮,服务器延迟100ms;第二轮200ms,最后每轮最高一直是5000毫秒重连一次
Thread.sleep(Math.min(retryTurns + 1, 50) * 100L);
}
} catch (InterruptedException e) {
// Do nothing.
}
}
if (isShutdown()) {
LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Client is shutdown ,stop reconnect to server", name);
}
} catch (Exception e) {
LoggerUtils.printIfWarnEnabled(LOGGER, "[{}] Fail to re connect to server ,error is {}", name, e);
}
}
reconnect方法中可以看出,这里面主要还是做了一些重连的逻辑处理,连接成功则发布连接成功的事件,如果存在旧连接则替换旧连接。如果一直重连不上,则会延迟一段时间进行重连。
由上分析可得,reconnect中实际上创建连接对象的方法是connectToServer方法,这个方法由子类来实现,nacos目前支持的rpc只有grpc,下面我们来看看grpc创建连接对象的逻辑。
public Connection connectToServer(ServerInfo serverInfo) {
try {
// 初始化线程池
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);
}
int port = serverInfo.getServerPort() + rpcPortOffset();
// 创建stub
RequestGrpc.RequestFutureStub newChannelStubTemp = createNewChannelStub(serverInfo.getServerIp(), port);
if (newChannelStubTemp != null) {
// 校验服务器是否可以访问
Response response = serverCheck(serverInfo.getServerIp(), port, newChannelStubTemp);
if (response == null || !(response instanceof ServerCheckResponse)) {
// 如果服务器不能访问成功,则关闭channel
shuntDownChannel((ManagedChannel) newChannelStubTemp.getChannel());
return null;
}
BiRequestStreamGrpc.BiRequestStreamStub biRequestStreamStub = BiRequestStreamGrpc
.newStub(newChannelStubTemp.getChannel());
GrpcConnection grpcConn = new GrpcConnection(serverInfo, grpcExecutor);
grpcConn.setConnectionId(((ServerCheckResponse) response).getConnectionId());
//create stream request and bind connection event to this connection.
// 创建stream观察者,将响应发送到服务器,到时候返回时会执行自定义的响应方法
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.
// 构建请求数据(用于注册到服务端的connectionManager中)
ConnectionSetupRequest conSetupRequest = new ConnectionSetupRequest();
conSetupRequest.setClientVersion(VersionUtils.getFullClientVersion());
conSetupRequest.setLabels(super.getLabels());
conSetupRequest.setAbilities(super.clientAbilities);
conSetupRequest.setTenant(super.getTenant());
grpcConn.sendRequest(conSetupRequest);
//wait to register connection setup
Thread.sleep(100L);
return grpcConn;
}
return null;
} catch (Exception e) {
LOGGER.error("[{}]Fail to connect to server!,error={}", GrpcClient.this.getName(), e);
}
return null;
}
在上面的方法中,都是grpc的一些基本方法,封装了一下。客户端和服务端建立连接的方法其实就是这么一句RequestGrpc.RequestFutureStub newChannelStubTemp = createNewChannelStub(serverInfo.getServerIp(), port);如果创建通道失败,则返回一个null来表示连接创建失败;如果创建成功,则会构建请求对象注册到rpc服务端的connectionManager中(如果不符合服务端的一些规则注册失败,则服务端会给客户端发送重连的请求,并且拒绝该客户端的请求,之后的讲到服务端再具体分析),之后会返回连接对象。
2、RPC服务端
2.1、rpc服务端结构
具体看一下UML图
这张图我列举了几个主要属性,由上图可见,RPC服务端和客户端结构类似,目前只有一种GRPC,而GRPC服务端又有两种具体的实现,一种是GrpcSdkServer和GrpcClusterServer,下面看一下他们的抽象类BaseRpcServer。
2.2、BaseRpcServer类
@PostConstruct
public void start() throws Exception {
String serverName = getClass().getSimpleName();
Loggers.REMOTE.info("Nacos {} Rpc server starting at port {}", serverName, getServicePort());
// 具体启动rpc服务逻辑
startServer();
Loggers.REMOTE.info("Nacos {} Rpc server started at port {}", serverName, getServicePort());
// 关闭时执行的钩子方法
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
Loggers.REMOTE.info("Nacos {} Rpc server stopping", serverName);
try {
BaseRpcServer.this.stopServer();
Loggers.REMOTE.info("Nacos {} Rpc server stopped successfully...", serverName);
} catch (Exception e) {
Loggers.REMOTE.error("Nacos {} Rpc server stopped fail...", serverName, e);
}
}
});
}
由上代码可见,BaseRpcServer实际上没有做什么,只是借助了@PostConstruct来启动rpc服务。下面我们来看看它的实现BaseGrpcServer。
2.3、GRPC服务端启动流程
BaseGrpcServer这个类中有几个属性,下面一一介绍一下。
// grpc 请求接收器
@Autowired
private GrpcRequestAcceptor grpcCommonRequestAcceptor;
// grpc bi流请求接收器
@Autowired
private GrpcBiStreamRequestAcceptor grpcBiStreamRequestAcceptor;
// 客户端连接管理器
@Autowired
private ConnectionManager connectionManager;
// grpc服务对象
private Server server;
grpc启动流程方法如下:
@Override
public void startServer() throws Exception {
final MutableHandlerRegistry handlerRegistry = new MutableHandlerRegistry();
// server interceptor to set connection id.
ServerInterceptor serverInterceptor = new ServerInterceptor() {
@Override
public <T, S> ServerCall.Listener<T> interceptCall(ServerCall<T, S> call, Metadata headers,
ServerCallHandler<T, S> next) {
Context ctx = Context.current()
.withValue(CONTEXT_KEY_CONN_ID, call.getAttributes().get(TRANS_KEY_CONN_ID))
.withValue(CONTEXT_KEY_CONN_REMOTE_IP, call.getAttributes().get(TRANS_KEY_REMOTE_IP))
.withValue(CONTEXT_KEY_CONN_REMOTE_PORT, call.getAttributes().get(TRANS_KEY_REMOTE_PORT))
.withValue(CONTEXT_KEY_CONN_LOCAL_PORT, call.getAttributes().get(TRANS_KEY_LOCAL_PORT));
if (REQUEST_BI_STREAM_SERVICE_NAME.equals(call.getMethodDescriptor().getServiceName())) {
Channel internalChannel = getInternalChannel(call);
ctx = ctx.withValue(CONTEXT_KEY_CHANNEL, internalChannel);
}
return Contexts.interceptCall(ctx, call, headers, next);
}
};
// 添加rpc服务
addServices(handlerRegistry, serverInterceptor);
server = ServerBuilder.forPort(getServicePort()).executor(getRpcExecutor())
// 设置服务端最大消息大小
.maxInboundMessageSize(getInboundMessageSize()).fallbackHandlerRegistry(handlerRegistry)
// 设置压缩的注册类
.compressorRegistry(CompressorRegistry.getDefaultInstance())
// 设置解压的注册类
.decompressorRegistry(DecompressorRegistry.getDefaultInstance())
// 添加过滤器
.addTransportFilter(new ServerTransportFilter() {
@Override
public Attributes transportReady(Attributes transportAttrs) {
InetSocketAddress remoteAddress = (InetSocketAddress) transportAttrs
.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
InetSocketAddress localAddress = (InetSocketAddress) transportAttrs
.get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR);
int remotePort = remoteAddress.getPort();
int localPort = localAddress.getPort();
String remoteIp = remoteAddress.getAddress().getHostAddress();
Attributes attrWrapper = transportAttrs.toBuilder()
.set(TRANS_KEY_CONN_ID, System.currentTimeMillis() + "_" + remoteIp + "_" + remotePort)
.set(TRANS_KEY_REMOTE_IP, remoteIp).set(TRANS_KEY_REMOTE_PORT, remotePort)
.set(TRANS_KEY_LOCAL_PORT, localPort).build();
String connectionId = attrWrapper.get(TRANS_KEY_CONN_ID);
Loggers.REMOTE_DIGEST.info("Connection transportReady,connectionId = {} ", connectionId);
return attrWrapper;
}
@Override
public void transportTerminated(Attributes transportAttrs) {
String connectionId = null;
try {
connectionId = transportAttrs.get(TRANS_KEY_CONN_ID);
} catch (Exception e) {
// Ignore
}
if (StringUtils.isNotBlank(connectionId)) {
Loggers.REMOTE_DIGEST
.info("Connection transportTerminated,connectionId = {} ", connectionId);
// 注销连接
connectionManager.unregister(connectionId);
}
}
}).build();
// 启动grpc服务
server.start();
}
private void addServices(MutableHandlerRegistry handlerRegistry, ServerInterceptor... serverInterceptor) {
// unary common call register.
final MethodDescriptor<Payload, Payload> unaryPayloadMethod = MethodDescriptor.<Payload, Payload>newBuilder()
.setType(MethodDescriptor.MethodType.UNARY)
.setFullMethodName(MethodDescriptor.generateFullMethodName(REQUEST_SERVICE_NAME, REQUEST_METHOD_NAME))
.setRequestMarshaller(ProtoUtils.marshaller(Payload.getDefaultInstance()))
.setResponseMarshaller(ProtoUtils.marshaller(Payload.getDefaultInstance())).build();
// 定义服务处理方法回调
final ServerCallHandler<Payload, Payload> payloadHandler = ServerCalls
.asyncUnaryCall((request, responseObserver) -> {
grpcCommonRequestAcceptor.request(request, responseObserver);
});
// 定义Service
final ServerServiceDefinition serviceDefOfUnaryPayload = ServerServiceDefinition.builder(REQUEST_SERVICE_NAME)
.addMethod(unaryPayloadMethod, payloadHandler).build();
handlerRegistry.addService(ServerInterceptors.intercept(serviceDefOfUnaryPayload, serverInterceptor));
// bi stream register.
// 定义bi注册器回调
final ServerCallHandler<Payload, Payload> biStreamHandler = ServerCalls.asyncBidiStreamingCall(
(responseObserver) -> grpcBiStreamRequestAcceptor.requestBiStream(responseObserver));
final MethodDescriptor<Payload, Payload> biStreamMethod = MethodDescriptor.<Payload, Payload>newBuilder()
.setType(MethodDescriptor.MethodType.BIDI_STREAMING).setFullMethodName(MethodDescriptor
.generateFullMethodName(REQUEST_BI_STREAM_SERVICE_NAME, REQUEST_BI_STREAM_METHOD_NAME))
.setRequestMarshaller(ProtoUtils.marshaller(Payload.newBuilder().build()))
.setResponseMarshaller(ProtoUtils.marshaller(Payload.getDefaultInstance())).build();
final ServerServiceDefinition serviceDefOfBiStream = ServerServiceDefinition
.builder(REQUEST_BI_STREAM_SERVICE_NAME).addMethod(biStreamMethod, biStreamHandler).build();
handlerRegistry.addService(ServerInterceptors.intercept(serviceDefOfBiStream, serverInterceptor));
}
以上方法都是grpc的封装,具体这里就不说明了,不懂的可以先学习一下grpc相关知识。
2.4、ConnectionManager注册
ConnectionManager的主要作用是注册客户端连接实例,用来做一些客户端权限验证。下面我们来看看客户端是如何注册的。
之前在介绍grpc客户端的时候,在GrpcClient类中的connectToServer方法中,有向grpc服务端去注册的方法,我这里稍微删减了一些,如下代码
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.
// 构建请求数据(用于注册到服务端的connectionManager中)
ConnectionSetupRequest conSetupRequest = new ConnectionSetupRequest();
conSetupRequest.setClientVersion(VersionUtils.getFullClientVersion());
conSetupRequest.setLabels(super.getLabels());
conSetupRequest.setAbilities(super.clientAbilities);
conSetupRequest.setTenant(super.getTenant());
grpcConn.sendRequest(conSetupRequest);
在执行grpcConn.sendRequest(conSetupRequest);的时候,实际上是调用了payloadStreamObserver的onNext来发送数据到服务端到。源码如下图
下面我们来看看服务端是怎么接收的。服务端接收代码如下:
public void onNext(Payload payload) {
clientIp = payload.getMetadata().getClientIp();
traceDetailIfNecessary(payload);
Object parseObj;
try {
parseObj = GrpcUtils.parse(payload);
} catch (Throwable throwable) {
Loggers.REMOTE_DIGEST
.warn("[{}]Grpc request bi stream,payload parse error={}", connectionId, throwable);
return;
}
if (parseObj == null) {
Loggers.REMOTE_DIGEST
.warn("[{}]Grpc request bi stream,payload parse null ,body={},meta={}", connectionId,
payload.getBody().getValue().toStringUtf8(), payload.getMetadata());
return;
}
if (parseObj instanceof ConnectionSetupRequest) {
ConnectionSetupRequest setUpRequest = (ConnectionSetupRequest) parseObj;
Map<String, String> labels = setUpRequest.getLabels();
String appName = "-";
if (labels != null && labels.containsKey(Constants.APPNAME)) {
appName = labels.get(Constants.APPNAME);
}
// 构建连接元数据对象
// clientId默认是客户端的ip
ConnectionMeta metaInfo = new ConnectionMeta(connectionId, payload.getMetadata().getClientIp(),
remoteIp, remotePort, localPort, ConnectionType.GRPC.getType(),
setUpRequest.getClientVersion(), appName, setUpRequest.getLabels());
metaInfo.setTenant(setUpRequest.getTenant());
// 创建连接对象
Connection connection = new GrpcConnection(metaInfo, responseObserver, CONTEXT_KEY_CHANNEL.get());
connection.setAbilities(setUpRequest.getAbilities());
boolean rejectSdkOnStarting = metaInfo.isSdkSource() && !ApplicationUtils.isStarted();
// 需要重连的情况有如下:
// 1、客户端是sdk单机的,并且nacos服务没有启动完成
// 2、客户端是sdk单机的,并且nacos服务启动完成了,但是注册失败
// 3、客户端是集群的,不管有没有启动完成,但是注册失败
if (rejectSdkOnStarting || !connectionManager.register(connectionId, connection)) {
//Not register to the connection manager if current server is over limit or server is starting.
try {
Loggers.REMOTE_DIGEST.warn("[{}]Connection register fail,reason:{}", connectionId,
rejectSdkOnStarting ? " server is not started" : " server is over limited.");
// 如果注册失败,则给服务端返回ConnectResetRequest进行重连
connection.request(new ConnectResetRequest(), 3000L);
connection.close();
} catch (Exception e) {
//Do nothing.
if (connectionManager.traced(clientIp)) {
Loggers.REMOTE_DIGEST
.warn("[{}]Send connect reset request error,error={}", connectionId, e);
}
}
}
} else if (parseObj instanceof Response) {
Response response = (Response) parseObj;
if (connectionManager.traced(clientIp)) {
Loggers.REMOTE_DIGEST
.warn("[{}]Receive response of server request ,response={}", connectionId, response);
}
RpcAckCallbackSynchronizer.ackNotify(connectionId, response);
connectionManager.refreshActiveTime(connectionId);
} else {
Loggers.REMOTE_DIGEST
.warn("[{}]Grpc request bi stream,unknown payload receive ,parseObj={}", connectionId,
parseObj);
}
}
由于客户端发送的请求类型是ConnectionSetupRequest,那么会进入if去构建connection,然后注册到connectionManager中去。如果无法注册,那么给客户端发送重连的请求。
下面我们看看客户端怎么处理服务端发送的ConnectResetRequest请求的。客户端接收代码如下:
public void onNext(Payload payload) {
// 接受服务端请求
LoggerUtils.printIfDebugEnabled(LOGGER, "[{}]Stream server request receive, original info: {}",
grpcConn.getConnectionId(), payload.toString());
try {
Object parseBody = GrpcUtils.parse(payload);
final Request request = (Request) parseBody;
if (request != null) {
try {
// 处理服务器请求数据
Response response = handleServerRequest(request);
if (response != null) {
response.setRequestId(request.getRequestId());
sendResponse(response);
} else {
LOGGER.warn("[{}]Fail to process server request, ackId->{}", grpcConn.getConnectionId(),
request.getRequestId());
}
} catch (Exception e) {
LoggerUtils.printIfErrorEnabled(LOGGER, "[{}]Handle server request exception: {}",
grpcConn.getConnectionId(), payload.toString(), e.getMessage());
sendResponse(request.getRequestId(), false);
}
}
} catch (Exception e) {
LoggerUtils.printIfErrorEnabled(LOGGER, "[{}]Error to process server push response: {}",
grpcConn.getConnectionId(), payload.getBody().getValue().toStringUtf8());
}
public Response requestReply(Request request) {
if (request instanceof ConnectResetRequest) {
try {
synchronized (RpcClient.this) {
// 判断是否在运行中
if (isRunning()) {
ConnectResetRequest connectResetRequest = (ConnectResetRequest) request;
if (StringUtils.isNotBlank(connectResetRequest.getServerIp())) {
ServerInfo serverInfo = resolveServerInfo(
connectResetRequest.getServerIp() + Constants.COLON + connectResetRequest
.getServerPort());
switchServerAsync(serverInfo, false);
} else {
// 往reconnectionSignal中设置空的重连上下文
switchServerAsync();
}
}
}
} catch (Exception e) {
LoggerUtils.printIfErrorEnabled(LOGGER, "[{}]Switch server error ,{}", name, e);
}
return new ConnectResetResponse();
}
return null;
}
在接收到请求后,如果处理成功,则会给服务端返回一个response,服务端接收到response后会刷新连接到存活时间等操作(这段代码在这个逻辑下实际上是没什么用的,主要还是为了适配其他地方的调用逻辑),如下图所示
下面我们来看看 ConnectionManager类中的registry方法,代码如下:
public synchronized boolean register(String connectionId, Connection connection) {
if (connection.isConnected()) {
// 如果已经存在,则直接注册成功
if (connections.containsKey(connectionId)) {
return true;
}
// 检查客户端限制规则
if (!checkLimit(connection)) {
return false;
}
if (traced(connection.getMetaInfo().clientIp)) {
connection.setTraced(true);
}
connections.put(connectionId, connection);
// 递增
connectionForClientIp.get(connection.getMetaInfo().clientIp).getAndIncrement();
// 发送连接成功事件
clientConnectionEventListenerRegistry.notifyClientConnected(connection);
Loggers.REMOTE_DIGEST
.info("new connection registered successfully, connectionId = {},connection={} ", connectionId,
connection);
return true;
}
return false;
}
以上代码主要是判断了下客户端是否可以注册,可以则存入到connections中去,其中有发送连接成功事件,这个在之后的distro协议篇再展开来讨论。
以上都是个人理解,如有错误恳请指正。