
大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈
前言
Nacos作为服务注册中心,整体的一个工作示意图如下所示。

Nacos的服务端是Springboot应用,启动后会实例化大量Controller来处理HTTP请求,而在2.x版本中,新增加了gRPC的通信方式,因此Nacos的服务端启动时,也会在服务端进行gRPC的监听,用于处理客户端的gRPC请求。
本文将对Nacos作为注册中心,服务端启动时开启gRPC监听的一个流程进行学习。
Nacos版本:2.0.4
正文
Nacos提供了GrpcClusterServer(用于集群间注册中心的数据同步)和GrpcSdkServer(用于与客户端通信)作为gRPC服务端,这两个类由@Service注解修饰,因此在Nacos启动时,该类的实例会作为bean注册到Spring容器中。类图如下所示。

GrpcClusterServer和GrpcSdkServer的父类BasePrcServer有一个由@PostConstruct注解修饰的start() 方法,该方法会在初始化bean时被调用。下面看一下BaseRpcServer的start() 方法的实现。
@PostConstruct
public void start() throws Exception {
String serverName = getClass().getSimpleName();
Loggers.REMOTE.info("Nacos {} Rpc server starting at port {}", serverName, getServicePort());
// 在这里进行启动
startServer();
Loggers.REMOTE.info("Nacos {} Rpc server started at port {}", serverName, getServicePort());
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
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的startServer() 方法,如下所示。
@Override
public void startServer() throws Exception {
final MutableHandlerRegistry handlerRegistry = new MutableHandlerRegistry();
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);
}
};
// 初始化用于处理客户端gRPC请求的handler
addServices(handlerRegistry, serverInterceptor);
// 构建gRPC的服务端
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();
}
上述方法会先初始化用于处理客户端gRPC请求的handler,然后构建一个gRPC Server(实际类型是io.grpc.internal.ServerImpl),最后调用ServerImpl的start() 方法进行启动。继续跟进ServerImpl的start() 方法。
@Override
public ServerImpl start() throws IOException {
synchronized (lock) {
// 状态检查方式重复启动和关闭后启动
checkState(!started, "Already started");
checkState(!shutdown, "Shutting down");
ServerListenerImpl listener = new ServerListenerImpl();
// 启动Netty服务端
for (InternalServer ts : transportServers) {
ts.start(listener);
activeTransportServers++;
}
executor = Preconditions.checkNotNull(executorPool.getObject(), "executor");
started = true;
return this;
}
}
实际上gRPC服务端底层是通过Netty的方式监听端口,上述方法就是将Netty服务端启动起来并监听对应端口。默认情况下,如果是初始化GrpcClusterServer的bean时调用到ServerImpl的start() 方法,Netty服务端会监听在9849端口上,如果是初始化GrpcSdkServer的bean时调用到ServerImpl的start() 方法,Netty服务端会监听在9848端口上,因此,Nacos服务端作为注册中心启动后,默认情况下会监听三个端口。
- 8848。用于处理HTTP请求;
- 9848。用于处理客户端gRPC请求;
- 9849。用于集群间注册中心的数据同步。
至此,Nacos作为注册中心时,服务端的一个启动流程分析完毕。
总结
Nacos 2.x版本中,Nacos作为注册中心服务端,启动时会做如下事情。
- 启动Springboot内嵌的Tomcat容器并监听在8848端口。用于处理HTTP请求;
- 启动gRPC Server并监听在9848端口。用于处理客户端的gRPC请求;
- 启动gRPC Server并监听在9849端口。用于集群间注册中心的数据同步。
gRPC Server的启动,依赖于GrpcClusterServer和GrpcSdkServer这两个由@Service注解修饰的类,也就是在Springboot加载bean的时候,会调用到GrpcClusterServer和GrpcSdkServer的初始化逻辑,从而完成gRPC Server的启动。流程示意如下。

最后思考一下,为什么作为注册中心时,Nacos需要启动gRPC Server并监听在9849端口用于集群间注册中心的数据同步,而作为配置中心是不需要的。
这是由于作为注册中心时,注册数据保存在内存中,所以为了数据一致性,Nacos集群中的实例之间需要通过9849端口通信用于数据同步,而作为配置中心时,配置数据持久化到了数据库中,因此数据的一致性,可以由数据库来保证。
大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈
