1. 背景描述
首先,说明下整个流程图,如下:
整个流程图
我们在前面的Nacos系列文章中,了解到获取服务的实例信息的代码如下:
public abstract class AbstractClient implements Client {
protected final ConcurrentHashMap<Service, InstancePublishInfo> publishers = new ConcurrentHashMap<>(16, 0.75f, 1);
@Override
public InstancePublishInfo getInstancePublishInfo(Service service) {
return publishers.get(service);
}
}
我们看到是从publishers缓存中获取服务实例信息,这个publishers缓存就是一个ConcurrentHashMap。服务实例节点信息缓存到这个map中。那么缓存service实例到publishers中的流程到底是怎样的???又是什么时候缓存的???我们下面分析下。
2. NacosServer提供服务注册接口
首先,启动服务提供者时,会发起注册服务请求,也就是调用NacosServer服务端的注册接口(这个之前的Nacos文章有详细说明),NacosServer服务端的注册接口如下:
@PostMapping
public String register(HttpServletRequest request) throws Exception {
...
// 从Request请求对象中获取serviceName和instance实例信息
final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
NamingUtils.checkServiceNameFormat(serviceName);
final Instance instance = HttpRequestInstanceBuilder.newBuilder()
.setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build();
// 注册服务实例节点信息
getInstanceOperator().registerInstance(namespaceId, serviceName, instance);
-
NacosServer服务端提供接口,接收服务提供者发送的注册请求
-
从Request请求中获取serviceName,获取实例信息
-
然后执行registerInstance注册方法(这个注册方法实际上就是缓存服务实例信息,下文会详细说明)
3. 缓存服务提供者实例对应的Client对象
@Override
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
...
// 为服务提供者实例,维护一个Client对象
String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), ephemeral);
createIpPortClientIfAbsent(clientId);
Service service = getService(namespaceId, serviceName, ephemeral);
// 检查Client连接 && 缓存服务实例节点信息
clientOperationService.registerInstance(service, instance, clientId);
}
为服务提供者实例,缓存对应的Client对象(这个Client对象封装了服务实例的ip、port信息)
private void createIpPortClientIfAbsent(String clientId) {
if (!clientManager.contains(clientId)) {
...
clientManager.clientConnected(clientId, clientAttributes);
}
}
@Override
public boolean clientConnected(String clientId, ClientAttributes attributes) {
return clientConnected(clientFactory.newClient(clientId, attributes));
}
@Override
public boolean clientConnected(final Client client) {
// 缓存Client对象(这个client对象封装了服务实例的ip、port信息)到Map中
clients.computeIfAbsent(client.getClientId(), s -> {
Loggers.SRV_LOG.info("Client connection {} connect", client.getClientId());
IpPortBasedClient ipPortBasedClient = (IpPortBasedClient) client;
ipPortBasedClient.init();
return ipPortBasedClient;
});
return true;
}
此clientManager.clientConnected的意义在于:
-
为服务提供者实例维护(缓存)了一个与之对应的Client对象(其中封装了ip、port信息)
-
这个Client对象代表着与服务提供者实例之间的一个connect连接(用于后面判断connect连接是否断开提供依据)
4. 缓存服务实例信息到publisher
4.1 NacosServer服务端获取Client对象,并检查连接
@Override
public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
...
// 获取NacosServer端为服务提供者实例维护(缓存)的Client对象
Client client = clientManager.getClient(clientId);
// 检查客户端connection连接是否断开
checkClientIsLegal(client, clientId);
// 缓存服务实例节点信息
client.addServiceInstance(singleton, instanceInfo);
...
- 获取NacosServer端为服务提供者实例维护(缓存)的Client对象
【特别注意】:每个注册服务的提供者实例,进行服务注册时,在Nacos服务端都会为其维护一个Client对象。
这个Client对象的作用:
- 用于判断提供者实例与Nacos服务的连接是正常还是断开
- Client对象用于维护缓存服务提供者实例信息(ip、port等信息),这个下文会介绍
后文中Client对象都是指在NacosServer服务端为注册服务的提供者实例缓存维护的Client对象。
- 检查客户端connection连接是否断开
private void checkClientIsLegal(Client client, String clientId) {
if (client == null) {
Loggers.SRV_LOG.warn("Client connection {} already disconnect", clientId);
throw new NacosRuntimeException(NacosException.CLIENT_DISCONNECT,
String.format("Client [%s] connection already disconnect, can't register ephemeral instance.",
clientId));
}
这个检查很关键,主要通过Client对象是否存在,来判断提供者实例与NacosServer端的连接是正常还是断开。
4.2 由Client对象维护(缓存)服务提供者实例信息(主要是ip、port等信息)
也就是缓存服务实例信息(ip、port信息)到Client对象的publisher中,这里的publiser就是一个ConcurrentHashMap中。
public boolean addServiceInstance(Service service, InstancePublishInfo instancePublishInfo) {
...
publishers.put(service, instancePublishInfo);
缓存服务->实例信息到publishers这个map中。这里的service就是服务名称等信息,如下:
service就是服务名称等信息
也就是咱们的服务提供端的应用名称,如下图所示:
服务提供端的应用名称
实例信息其实就是服务提供者实例的ip、port等信息,如下:
实例信息即服务提供者实例的ip、port信息
所以,最终就是把服务->服务提供者实例的ip、port等信息缓存到Client对象的publisher这个map中。
然而这个publisher属性又是属于Client对象的。这个Client对象就是每个提供者实例发送注册请求到NacosServer时,NacosServer服务端为其维护建立的Client对象。
所以,实际上就是每个注册服务的提供者实例的Client对象缓存了服务实例信息。如下图:
提供者实例的Client对象缓存了服务实例信息
5. 从Client对象中获取服务实例
因为在前文说明过,服务实例就是由每个服务提供者实例对应的Client对象缓存到publiser属性中,所以这里消费者获取服务实例肯定最终是要从Client对象中获取。基本流程如下图:
从Client对象中获取服务实例
5.1 消费者获取(发现)服务
我们启动服务消费者,调用这个provider服务的接口,调用之前会先去获取(发现)provider服务实例。
/**
* 调用服务
* @param str
* @return
*/
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
public String echo(@PathVariable String str) {
// 注意这里的provider是服务提供者配置文件中配置的应用名称
return restTemplate.getForObject("http://provider/echo/" + str, String.class);
}
5.2 调用NacosServer的list接口获取服务
如果消费者本地没有缓存服务实例信息,则会去调用NacosServer的list接口获取服务实例(这个流程在之前的Nacos系列文章中有详细说明)。
@GetMapping("/list")
public Object list(HttpServletRequest request) throws Exception {
...
return getInstanceOperator().listInstance(namespaceId, serviceName, subscriber, clusters, healthyOnly);
}
5.3 获取服务对应的Client对象
首先,由service对象获取注册服务的clientId信息,这个clientId就是注册服务的实例的ip、port信息。
private List<Instance> getAllInstancesFromIndex(Service service) {
...
// 获取服务对应的clientId
for (String each : serviceIndexesManager.getAllClientsRegisteredService(service)) {
Optional<InstancePublishInfo> instancePublishInfo = getInstanceInfo(each, service);
然后,由clientId来获取Nacos服务端维护的Client对象(这个Client对象维护(缓存)了服务实例),因为NacosServer为每个服务实例维护了一个Client对象(这个前面章节已经有过说明)
private Optional<InstancePublishInfo> getInstanceInfo(String clientId, Service service) {
// 由clientId获取对应的Client对象
Client client = clientManager.getClient(clientId);
...
return Optional.ofNullable(client.getInstancePublishInfo(service));
}
从Client对象获取服务实例
5.4 最后从这个Client对象中获取服务实例信息
因为从上文中,可知Client对象维护了注册服务的提供者实例自己的实例信息,因此,可以从这个Client对象中获取服务实例信息。
@Override
public InstancePublishInfo getInstancePublishInfo(Service service) {
return publishers.get(service);
}
根据serviceName服务名称等信息,获取服务实例信息(ip、port等信息),如下:
获取服务实例信息(ip、port等信息)