大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈
前言
Nacos作为服务注册中心,整体的一个工作示意图如下所示。
当Nacos服务端启动完毕后,Nacos客户端就可以进行注册。
最简单的方式,可以通过HTTP请求的方式来注册,如下所示。
127.0.0.1:8848/nacos/v1/ns/instance?serviceName=learn-nacos&ip=127.0.0.1&port=8080
也可以通过模板代码来调用Nacos提供的API来注册,如下所示。
public class NamingExample {
public static void main(String[] args) throws NacosException {
Properties properties = new Properties();
properties.setProperty("serverAddr", System.getProperty("serverAddr"));
properties.setProperty("namespace", System.getProperty("namespace"));
NamingService naming = NamingFactory.createNamingService(properties);
naming.registerInstance("learn-nacos", "127.0.0.1", 8080, "TEST1");
}
}
如果是在SpringCloud中,Nacos提供了一套更加优雅的服务注册机制。
本篇文章将对SpringCloud整合Nacos作为服务注册中心时,应用启动后的服务注册流程进行学习。
SpringCloud版本:2021.0.4
Nacos版本:2.0.4
SpringCloud Alibaba版本:2021.0.4.0
正文
一. 示例工程搭建
首先简单搭建一个示例工程。
创建Maven工程,POM文件如下。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
</parent>
<groupId>com.lee.learn.nacos</groupId>
<artifactId>learn-nacos</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
添加一个Springboot启动类,如下所示。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
最后就是提供一份application.yml文件,如下所示。
server:
port: 8080
spring:
cloud:
nacos:
server-addr: localhost:8848
application:
name: learn-nacos
二. 客户端的服务注册流程分析
SpringCloud整合Nacos是基于自动装配类AutoServiceRegistrationAutoConfiguration(位于spring-cloud-commons包中),该类依赖注入了AutoServiceRegistration,这里实际类型是NacosAutoServiceRegistration(位于spring-cloud-starter-alibaba-nacos-discovery包中),NacosAutoServiceRegistration的类图如下所示。
(特别需要注意的就是, 上述类图中的AbstractAutoServiceRegistration位于spring-cloud-commons包中,但NacosAutoServiceRegistration位于spring-cloud-starter-alibaba-nacos-discovery包中)
AbstractAutoServiceRegistration同时也是一个事件监听器,监听的逻辑在其onApplicationEvent() 方法中,如下所示。
public void onApplicationEvent(WebServerInitializedEvent event) {
bind(event);
}
因此监听的是WebServerInitializedEvent事件,该事件会在WEB容器初始化完毕后发布,上述监听到WebServerInitializedEvent事件后的行为时序图如下所示。
那么这里其实就想说明一点,SpringCloud定义了服务注册这个功能的相关接口(AutoServiceRegistration)或抽象(AbstractAutoServiceRegistration),然后Nacos作为服务注册这个功能的提供方,就需要提供对应的实现(NacosAutoServiceRegistration),那么在初始化服务注册这个功能的时候,肯定会走到NacosAutoServiceRegistration的逻辑中,而通过NacosAutoServiceRegistration,最终可以执行到NacosServiceRegistry的register() 方法,从这里开始,服务注册就完全交给到Nacos来实现了,如下所示。
@Override
public void register(Registration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
// 获取NamingService
NamingService namingService = namingService();
// 获取服务Id,作为注册到Nacos上的服务名
String serviceId = registration.getServiceId();
// 获取配置的组,如果没有配置就使用默认的DEFAULT_GROUP
String group = nacosDiscoveryProperties.getGroup();
// 生成当前实例,包含当前实例的ip和port等信息
Instance instance = getNacosInstanceFromRegistration(registration);
try {
// 使用NamingService进行服务注册
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
instance.getIp(), instance.getPort());
}
catch (Exception e) {
if (nacosDiscoveryProperties.isFailFast()) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
rethrowRuntimeException(e);
}
else {
log.warn("Failfast is false. {} register failed...{},", serviceId,
registration.toString(), e);
}
}
}
上面方法的逻辑很清晰,就是拿到当前实例的元数据后,调用NamingService的registerInstance() 方法来进行服务注册。
现在对SpringCloud整合Nacos的客户端服务注册流程进行一个小结。
- SpringCloud提供事件监听器监听WebServerInitializedEvent事件。该事件会在WEB容器初始化完成后发布;
- 监听到WebServerInitializedEvent事件后会执行服务注册逻辑;
- 服务注册逻辑由SpringCloud提供抽象定义,各服务注册组件方提供具体实现。
上面,主要是分析SpringCloud整合Nacos后,为什么可以在应用启动完毕后,自动将当前实例注册到Nacos,并且最终发现实际的注册还是需要调用到nacos-client包提供的NacosNamingService的registerInstance() 方法。
下面,就将从NacosNamingService的registerInstance() 方法开始,对具体的注册细节进行学习。
NacosNamingService的registerInstance(String, String, Instance) 方法如下所示。
@Override
public void registerInstance(String serviceName, String groupName, Instance instance)
throws NacosException {
// 对心跳时间进行校验
NamingUtils.checkInstanceIsLegal(instance);
// 使用客户端代理来注册服务
clientProxy.registerService(serviceName, groupName, instance);
}
上面方法中的clientProxy是一个代理对象,实际类型为NamingClientProxy,继续跟进NamingClientProxy的registerService() 方法,如下所示。
@Override
public void registerService(String serviceName, String groupName, Instance instance)
throws NacosException {
// 先判断实例类型,然后根据不同的实例类型用不同的客户端向Nacos服务端发送注册请求
getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}
private NamingClientProxy getExecuteClientProxy(Instance instance) {
// 临时实例使用gRPC客户端,永久实例使用HTTP客户端
return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
}
上述方法很重要的一个逻辑就是会判断当前需要注册的实例是临时的还是永久的。
- 临时实例。实例信息暂存于注册中心的内存中,健康检测机制需要各实例向Nacos服务端主动发送心跳;
- 永久实例。实例信息会存储到注册中心的内存和磁盘上,健康检测机制需要Nacos服务端主动去探活各实例。
默认情况下,都是临时实例,所以下面主要分析grpcClientProxy的逻辑,grpcClientProxy的实际类型为NamingGrpcClientProxy,其registerService() 方法如下所示。
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
instance);
redoService.cacheInstanceForRedo(serviceName, groupName, instance);
doRegisterService(serviceName, groupName, instance);
}
public void doRegisterService(String serviceName, String groupName, Instance instance) throws NacosException {
// 创建一个服务注册的gRPC请求
InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName,
NamingRemoteConstants.REGISTER_INSTANCE, instance);
// 发送请求
requestToServer(request, Response.class);
redoService.instanceRegistered(serviceName, groupName);
}
private <T extends Response> T requestToServer(AbstractNamingRequest request, Class<T> responseClass)
throws NacosException {
try {
request.putAllHeader(getSecurityHeaders());
request.putAllHeader(getSpasHeaders(
NamingUtils.getGroupedNameOptional(request.getServiceName(), request.getGroupName())));
// 在这里使用rpcClient发送InstanceRequest
Response response =
requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout);
if (ResponseCode.SUCCESS.getCode() != response.getResultCode()) {
throw new NacosException(response.getErrorCode(), response.getMessage());
}
if (responseClass.isAssignableFrom(response.getClass())) {
return (T) response;
}
NAMING_LOGGER.error("Server return unexpected response '{}', expected response should be '{}'",
response.getClass().getName(), responseClass.getName());
} catch (Exception e) {
throw new NacosException(NacosException.SERVER_ERROR, "Request nacos server failed: ", e);
}
throw new NacosException(NacosException.SERVER_ERROR, "Server return invalid response");
}
上述方法会先创建出服务注册的gRPC请求InstanceRequest,然后使用rpcClient发送InstanceRequest到Nacos服务端。
其中rpcClient的实际类型是GrpcSdkClient,是在NamingGrpcClientProxy的构造方法中被创建并启动起来的,rpcClient启动后会去基于服务端端口偏移1000与服务端建立连接。具体的逻辑在RpcClient的start() 方法中,这里不再赘述。
下面再跟进一下RpcClient的request(Request) 方法,如下所示。
public Response request(Request request, long timeoutMills) throws NacosException {
int retryTimes = 0;
Response response;
Exception exceptionThrow = null;
long start = System.currentTimeMillis();
while (retryTimes < RETRY_TIMES && System.currentTimeMillis() < timeoutMills + start) {
boolean waitReconnect = false;
try {
if (this.currentConnection == null || !isRunning()) {
waitReconnect = true;
throw new NacosException(NacosException.CLIENT_DISCONNECT,
"Client not connected, current status:" + rpcClientStatus.get());
}
// 发送请求并且currentConnection类型是GrpcConnection
response = this.currentConnection.request(request, timeoutMills);
if (response == null) {
throw new NacosException(SERVER_ERROR, "Unknown Exception.");
}
if (response instanceof ErrorResponse) {
if (response.getErrorCode() == NacosException.UN_REGISTER) {
synchronized (this) {
waitReconnect = true;
if (rpcClientStatus.compareAndSet(RpcClientStatus.RUNNING, RpcClientStatus.UNHEALTHY)) {
LoggerUtils.printIfErrorEnabled(LOGGER,
"Connection is unregistered, switch server, connectionId = {}, request = {}",
currentConnection.getConnectionId(), request.getClass().getSimpleName());
switchServerAsync();
}
}
}
throw new NacosException(response.getErrorCode(), response.getMessage());
}
lastActiveTimeStamp = System.currentTimeMillis();
return response;
} catch (Exception e) {
if (waitReconnect) {
try {
Thread.sleep(Math.min(100, timeoutMills / 3));
} catch (Exception exception) {
}
}
LoggerUtils.printIfErrorEnabled(LOGGER, "Send request fail, request = {}, retryTimes = {}, errorMessage = {}",
request, retryTimes, e.getMessage());
exceptionThrow = e;
}
retryTimes++;
}
if (rpcClientStatus.compareAndSet(RpcClientStatus.RUNNING, RpcClientStatus.UNHEALTHY)) {
switchServerAsyncOnRequestFail();
}
if (exceptionThrow != null) {
throw (exceptionThrow instanceof NacosException) ? (NacosException) exceptionThrow
: new NacosException(SERVER_ERROR, exceptionThrow);
} else {
throw new NacosException(SERVER_ERROR, "Request fail, unknown Error");
}
}
上述方法就是使用与Nacos服务端已经建立好连接的GrpcConnection来发送InstanceRequest。
至此Nacos作为服务注册中心时,Nacos客户端启动时如何向服务端发送服务注册请求的完整流程,就分析完毕。
三. 服务端的服务注册流程分析
在分析Nacos服务端的服务注册流程前,需要先说明一下Nacos中的数据模型和服务领域模型。数据模型如下所示。
也就是Namespace(命名空间)下划分Group(组),Group下是各个Service(服务)。在Nacos服务端有一个com.alibaba.nacos.naming.core.v2.pojo.Service对象,用于表示一个服务,并且就通过namespace,group和name(服务名)这三个字段唯一确定一个服务。
对于服务来说,也有其领域模型,如下图所示。
服务下其实就是一个又一个的实例,在Nacos服务端使用com.alibaba.nacos.naming.core.v2.pojo.InstancePublishInfo这个对象来表示已经发布的服务的实例。除此之外在Nacos2.x服务端还需要关注一个对象,是ConnectionBasedClient,用于表示基于TCP会话的Nacos客户端,里面会存储connectionId(连接Id),存储这个Nacos客户端发布的服务和实例信息,存储这个Nacos客户端的订阅信息等,所以Nacos服务端不是面向每个实例进行通信,而是面向每个Nacos客户端进行通信。
已知Nacos客户端会通过gRPC的方法向Nacos服务端发送服务注册请求InstanceRequest,那么相应的,在Nacos服务端会有一个叫做InstanceRequestHandler的处理器来处理InstanceRequest请求。下面看一下InstanceRequestHandler的handle() 方法的实现。
@Override
@Secured(action = ActionTypes.WRITE, parser = NamingResourceParser.class)
public InstanceResponse handle(InstanceRequest request, RequestMeta meta) throws NacosException {
// 基于命名空间,组名,服务名和临时节点标识来创建当前实例所属的服务Service
Service service = Service
.newService(request.getNamespace(), request.getGroupName(), request.getServiceName(), true);
// 判断是服务注册还是注销
switch (request.getType()) {
case NamingRemoteConstants.REGISTER_INSTANCE:
// 服务注册逻辑
return registerInstance(service, request, meta);
case NamingRemoteConstants.DE_REGISTER_INSTANCE:
// 服务注销逻辑
return deregisterInstance(service, request, meta);
default:
throw new NacosException(NacosException.INVALID_PARAM,
String.format("Unsupported request type %s", request.getType()));
}
}
当收到服务注册或者注销的请求后,会先根据请求实例的命名空间,组名,服务名和临时节点标识来创建当前实例所属的服务Service,然后再根据当前请求是注册还是注销,分别执行不同的逻辑。
这里仅分析服务注册的逻辑,对应InstanceRequestHandler的registerInstance() 方法,如下所示。
private InstanceResponse registerInstance(Service service, InstanceRequest request, RequestMeta meta) {
clientOperationService.registerInstance(service, request.getInstance(), meta.getConnectionId());
return new InstanceResponse(NamingRemoteConstants.REGISTER_INSTANCE);
}
在上述方法中,会先获取表示当前注册的实例的Instance,然后再获取这个实例的gRPC连接Id,最后调用clientOperationService的registerInstance() 方法进行服务注册。Instance中会存储当前注册的实例的各种数据,例如实例的ip,port等,其类图如下所示。
同时由于gRPC是长连接,连接是一直保持着的,所以Nacos服务端需要保存每个gRPC的连接Id,用于后续推送数据到每一个Nacos客户端。
处理服务注册的clientOperationService的实际类型是EphemeralClientOperationServiceImpl,用于处理临时实例的服务注册。下面跟进其registerInstance() 方法。
@Override
public void registerInstance(Service service, Instance instance, String clientId) {
// 从缓存中拿到当前注册实例所属服务的Service对象
// 如果当前注册实例所属服务是第一次注册,那么就缓存传入的Service对象
Service singleton = ServiceManager.getInstance().getSingleton(service);
if (!singleton.isEphemeral()) {
throw new NacosRuntimeException(NacosException.INVALID_PARAM,
String.format("Current service %s is persistent service, can't register ephemeral instance.",
singleton.getGroupedServiceName()));
}
// 通过clientId拿到对应的Nacos客户端Client
Client client = clientManager.getClient(clientId);
// 校验一下Client,防止注册超时等情况
if (!clientIsLegal(client, clientId)) {
return;
}
// 基于客户端传递过来的Instance构建InstancePublishInfo
// 服务端使用InstancePublishInfo来表示某一个实例
InstancePublishInfo instanceInfo = getPublishInfo(instance);
// 将Service和InstancePublishInfo添加到Client
// 然后发布一个ClientChangedEvent事件,用于将当前注册的实例同步到集群
client.addServiceInstance(singleton, instanceInfo);
// 刷新Client最后一次更新的时间
client.setLastUpdatedTime();
// 发布ClientRegisterServiceEvent事件
NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
// 发布InstanceMetadataEvent事件
NotifyCenter
.publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
}
EphemeralClientOperationServiceImpl的registerInstance() 方法会做如下事情。
- 缓存当前注册实例所属服务的Service对象。如果已经缓存过,则将已经缓存的Service对象获取出来使用。需要注意,同一个服务的不同实例进行注册时,传递过来的Service对象在堆上是不同的对象,但是在判断对象是否相等时,可能会判断为相等,这是因为Service类中针对equals() 和hashCode() 方法进行了重写,只要namespace(命名空间),group(组)和name(服务名)这三个字段一样,那么这两个不同的Service对象会被判定为相等;
- 根据clientId获取到Client对象。Client表示Nacos客户端,里面缓存着gRPC的连接Id,这个Nacos客户端发布的服务和实例信息,这个Nacos客户端的订阅信息等;
- 将服务Service和实例InstancePublishInfo缓存到Client的publishers中。publishers类型为ConcurrentHashMap<Service, InstancePublishInfo>,用于存储当前Client发布的服务以及对应的实例;
- 发布ClientChangedEvent事件。该事件用于将当前注册的实例的信息同步给Nacos集群的其它服务端;
- 发布ClientRegisterServiceEvent事件。该事件用于完成服务注册;
- 发布InstanceMetadataEvent事件。该事件用于更新实例的元数据信息。
所以可以知道,Nacos服务端处理服务注册,使用到了事件监听机制,NotifyCenter就是事件的广播器。这里重点关注ClientRegisterServiceEvent事件,对应的事件处理器是ClientServiceIndexesManager,其处理事件的onEvent() 方法如下所示。
@Override
public void onEvent(Event event) {
if (event instanceof ClientEvent.ClientDisconnectEvent) {
handleClientDisconnect((ClientEvent.ClientDisconnectEvent) event);
} else if (event instanceof ClientOperationEvent) {
handleClientOperation((ClientOperationEvent) event);
}
}
private void handleClientOperation(ClientOperationEvent event) {
Service service = event.getService();
String clientId = event.getClientId();
if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) {
// 服务注册处理逻辑
addPublisherIndexes(service, clientId);
} else if (event instanceof ClientOperationEvent.ClientDeregisterServiceEvent) {
removePublisherIndexes(service, clientId);
} else if (event instanceof ClientOperationEvent.ClientSubscribeServiceEvent) {
addSubscriberIndexes(service, clientId);
} else if (event instanceof ClientOperationEvent.ClientUnsubscribeServiceEvent) {
removeSubscriberIndexes(service, clientId);
}
}
private void addPublisherIndexes(Service service, String clientId) {
// 建立服务与服务下的实例发布者的映射关系
// 一个服务对应一个ConcurrentHashSet集合
// 集合中存储着发布了该服务实例的所有客户端Id
publisherIndexes.computeIfAbsent(service, (key) -> new ConcurrentHashSet<>());
publisherIndexes.get(service).add(clientId);
// 发布ServiceChangedEvent事件
// 将当前服务的变更推送给当前服务的订阅者
NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true));
}
监听到ClientRegisterServiceEvent事件后,会最终调用到ClientServiceIndexesManager的addPublisherIndexes() 方法,在这个方法中,会首先将服务与发布了该服务实例的客户端Id缓存到publisherIndexes中,然后发布ServiceChangedEvent事件用于将当前服务的变更消息推送给当前服务的订阅者。其中publisherIndexes类型是ConcurrentMap<Service, Set<String>>,也就是一个服务对应多个客户端。
那么至此,Nacos服务端处理服务注册的流程分析完毕。整个流程会发布四个事件,小结如下。
- ClientChangedEvent事件。用于将当前注册的实例的信息同步给Nacos集群的其它服务端;
- ClientRegisterServiceEvent事件。用于完成服务注册;
- InstanceMetadataEvent事件。用于更新实例的元数据信息;
- ServiceChangedEvent事件。用于向注册实例所属服务的订阅者推送服务变更消息。
与服务注册强相关的ClientRegisterServiceEvent事件,会被ClientServiceIndexesManager监听和处理,最后当前注册实例的服务和发布这个实例的客户端会建立起一对多的关系并缓存在内存中(缓存在ClientServiceIndexesManager的publisherIndexes字段中)。
最后再提一下ClientServiceIndexesManager中的两个字段,如下所示。
private final ConcurrentMap<Service, Set<String>> publisherIndexes = new ConcurrentHashMap<>();
private final ConcurrentMap<Service, Set<String>> subscriberIndexes = new ConcurrentHashMap<>();
publisherIndexes表示一个服务有哪些发布者。subscriberIndexes表示一个服务有哪些订阅者。
关于服务发现和服务订阅相关的逻辑,将会在后续的文章中进行学习。
总结
首先是SpringCloud整合Nacos后,客户端启动时服务注册的一个时序图,如下所示。
SpringCloud针对服务注册,专门提供了事件监听器来监听WebServerInitializedEvent事件,监听到事件后,会调用到SpringCloud定义的服务注册接口ServiceRegistry的具体实现,这里的具体实现由Nacos提供,后续就是Nacos的服务注册流程。
Nacos客户端的一个服务注册流程图如下所示。
如果是临时实例,则创建gRPC的请求InstanceRequest,然后通过gRPC客户端往服务端发送。
在Nacos服务端,处理服务注册流程图如下所示。
Nacos服务端在收到InstanceRequest后,会创建出Service,InstancePublishInfo和Client,分别代表本次注册的实例所属服务,本次注册的实例和客户端,然后发布一系列事件。其中ClientRegisterServiceEven事件是服务注册相关事件,该事件在Nacos服务端的处理流程图如下所示。
Nacos服务端处理服务注册事件其实就是先缓存本次注册的服务Service与本次发布服务实例的客户端的映射关系(一对多),然后发布一个ServiceChangedEvent事件,用于将当前服务的变更推送给当前服务的订阅者。
最后,就是Nacos服务端这边,有几个内存缓存,这里可以总结一下。
首先是ServiceManager,有两个缓存,分别如下。
// 用于将每个服务Service缓存起来
// Map[服务, 服务]
private final ConcurrentHashMap<Service, Service> singletonRepository;
// 用于缓存命名空间namespace与服务Service的映射关系
// Map[命名空间, Set[服务]]
private final ConcurrentHashMap<String, Set<Service>> namespaceSingletonMaps;
然后是客户端Client,实际类型是ConnectionBasedClient,缓存定义在其父类AbstractClient中,如下所示。
// 用于缓存当前客户端发布的服务和实例的映射关系
// Map[服务, 实例]
protected final ConcurrentHashMap<Service, InstancePublishInfo> publishers = new ConcurrentHashMap<>(16, 0.75f, 1);
// 用于缓存当前客户端订阅的服务和订阅者的映射关系
// Map[服务, 订阅者]
protected final ConcurrentHashMap<Service, Subscriber> subscribers = new ConcurrentHashMap<>(16, 0.75f, 1);
最后是ClientServiceIndexesManager,有两个缓存,如下所示。
// 用于缓存服务和发布这个服务实例的客户端的映射关系
// Map[服务, Set[客户端Id]]
private final ConcurrentMap<Service, Set<String>> publisherIndexes = new ConcurrentHashMap<>();
// 用于缓存服务和订阅这个服务的客户端的映射关系
// Map[服务, Set[客户端Id]]
private final ConcurrentMap<Service, Set<String>> subscriberIndexes = new ConcurrentHashMap<>();
大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈