在 分布式架构入门 一节中,我们已经大概了解了分布式架构的产生。也提到了在分布式架构的演进中,发生的一些问题及解决方案。这里我们主要探讨其中注册中心一块的内容。
前言
文章中所说源码版本以及 Nacos 官网文档:
- spring-cloud-alibaba-nacos : 2.2.5.RELEASE
- nacos-server: 2.0.2
- nacos-example: master
为什么需要注册中心
在 分布式架构入门 一节中我们了解到,分布式架构的发展,最终(其实也是目前主流的架构)会演变为如下这样的架构:
在这样的架构下会给我们带来什么样的问题和挑战呢?
- 单体服务经过拆分,分成了多个独立的服务,服务与服务之前如何通信。
- 微服务应用为了避免单体故障,会采用多个副本,服务与服务之前调用,如何负载及调度。
- 主调服务如何获取到被调服务的信息,包括节点信息以及健康状态。
RPC
RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。
RPC 主要是为了解决服务与服务之前远程调用的问题,像 webservice 、restFul 、dubbo 等都是 rpc 协议。 Java 方面的 RPC 框架有:Dubbo、Spring Cloud、Thrift、grpc 等。
本文中使用的 spring cloud 的 demo(restTemplate) 为例来探讨 Nacos 注册中心的实现。
客户端负载均衡
主调服务与多个被调服务之间如何负载及调度呢,则需要使用负载均衡来实现。这里我们主要使用 Spring Cloud Ribbon。如下图是简单的负载均衡的工作方式,在客户端调用服务端时,不直接调用,而是请求负载均衡器,通过负载均衡算法选择一个服务端的信息返回给客户端,然后再进行调用。
注册中心原理
解决了上述两个问题以后,我们发现仍然有问题在困扰我们。服务之前如何获取服务的信息,及如何确认服务当前的健康状态。
这里就需要引入注册中心。根据目前已有的问题,我们对注册中心有如下要求:
- 可以保存微服务的信息。
- 微服务之前可以相互查询保存的信息。
- 可以检测到服务的状态是否健康,并及时更新。
使用 Nacos 注册中心
添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${latest.version}</version>
</dependency>
复制代码
服务提供者
在 application.properties 中配置 Nacos Server 的地址
server.port=8070
spring.application.name=service-provider
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
复制代码
通过 Spring Cloud 原生注解 @EnableDiscoveryClient
开启服务注册发现功能
@SpringBootApplication
@EnableDiscoveryClient
public class NacosProviderApplication {
public static void main(String[] args) {
SpringApplication.run(NacosProviderApplication.class, args);
}
@RestController
class EchoController {
@RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)
public String echo(@PathVariable String string) {
return "Hello Nacos Discovery " + string;
}
}
}
复制代码
服务消费方
在 application.properties 中配置 Nacos Server 的地址
@SpringBootApplication
@EnableDiscoveryClient
public class NacosProviderApplication {
public static void main(String[] args) {
SpringApplication.run(NacosProviderApplication.class, args);
}
@RestController
class EchoController {
@RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)
public String echo(@PathVariable String string) {
return "Hello Nacos Discovery " + string;
}
}
}
复制代码
通过 Spring Cloud 原生注解 @EnableDiscoveryClient 开启服务注册发现功能。给 RestTemplate 实例添加 @LoadBalanced 注解,开启 @LoadBalanced 与 Ribbon 的集成:
@SpringBootApplication
@EnableDiscoveryClient
public class NacosConsumerApplication {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(NacosConsumerApplication.class, args);
}
@RestController
public class TestController {
private final RestTemplate restTemplate;
@Autowired
public TestController(RestTemplate restTemplate) {this.restTemplate = restTemplate;}
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
public String echo(@PathVariable String str) {
return restTemplate.getForObject("http://service-provider/echo/" + str, String.class);
}
}
}
复制代码
Nacos 作为注册中心的实现
服务注册流程 - 客户端
AbstractAutoServiceRegistration
Spring Cloud Common 中有一个接口用于抽象 Spring Cloud 服务注册的流程:org.springframework.cloud.client.serviceregistry.AutoServiceRegistration
在该接口下有一个抽象实现 org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration
。在该抽象类中,会监听 web 容器初始化的事件,并注册服务信息:
@Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
// 监听 WebServerInitializedEvent 事件,事件触发时执行 bind 方法
bind(event);
}
@Deprecated
public void bind(WebServerInitializedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(((ConfigurableWebServerApplicationContext) context)
.getServerNamespace())) {
return;
}
}
this.port.compareAndSet(0, event.getWebServer().getPort());
// 启动 discovery
this.start();
}
复制代码
从这里我们看到,当程序监听到 WebServerInitializedEvent
事件时,会执行到 org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#start
方法
AbstractAutoServiceRegistration#start
public void start() {
if (!isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get()) {
// 触发注册前事件
this.context.publishEvent(
new InstancePreRegisteredEvent(this, getRegistration()));
// 服务注册
register();
if (shouldRegisterManagement()) {
registerManagement();
}
// 触发注册后事件
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
}
}
复制代码
在 AbstractAutoServiceRegistration#start
中,其实就是调用的 register
方法进行服务注册,并在注册前后发布了注册前后的回调事件。
AbstractAutoServiceRegistration#register
protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry,
AutoServiceRegistrationProperties properties) {
this.serviceRegistry = serviceRegistry;
this.properties = properties;
}
protected void register() {
this.serviceRegistry.register(getRegistration());
}
复制代码
在抽象类中的 register
将请求代理到了构造方法 serviceRegistry#register
。而我们这里是使用的 Nacos 作为注册中心,这里的具体实现是 com.alibaba.cloud.nacos.registry.NacosServiceRegistry
NacosServiceRegistry#register
@Override
public void register(Registration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
// 获取 namingserver
NamingService namingService = namingService();
// serviceId 其实就是 spring.applicatio.name
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
// 组装需要注册的节点信息
Instance instance = getNacosInstanceFromRegistration(registration);
try {
// 通过 namingServer 注册节点信息
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
instance.getIp(), instance.getPort());
}
catch (Exception e) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
// rethrow a RuntimeException if the registration is failed.
// issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
rethrowRuntimeException(e);
}
}
复制代码
在 com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register
中先获取了 namingServer
服务,这里可以简单理解为根据 application.properties
配置的 nacosServer
地址获取一个 httpClient
。然后组装当前需要注册的节点信息,并通过 namingService.registerInstance
注册服务。
NacosNamingService#registerInstance
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
NamingUtils.checkInstanceIsLegal(instance);
String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
// 是否是临时节点,临时节点的话,构建心跳任务
if (instance.isEphemeral()) {
BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
beatReactor.addBeatInfo(groupedServiceName, beatInfo);
}
// 注册服务
serverProxy.registerService(groupedServiceName, groupName, instance);
}
复制代码
这里的具体实现是 com.alibaba.nacos.client.naming.NacosNamingService#registerInstance
。
这个方法中主要做了两个事情:
- 判断是否是临时节点,如果是临时节点,构建心跳任务。
- 注册服务。
NamingProxy#registerService
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,
instance);
// 封装请求
final Map<String, String> params = new HashMap<String, String>(16);
params.put(CommonParams.NAMESPACE_ID, namespaceId);
params.put(CommonParams.SERVICE_NAME, serviceName);
params.put(CommonParams.GROUP_NAME, groupName);
params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
params.put("ip", instance.getIp());
params.put("port", String.valueOf(instance.getPort()));
params.put("weight", String.valueOf(instance.getWeight()));
params.put("enable", String.valueOf(instance.isEnabled()));
params.put("healthy", String.valueOf(instance.isHealthy()));
params.put("ephemeral", String.valueOf(instance.isEphemeral()));
params.put("metadata", JacksonUtils.toJson(instance.getMetadata()));
// 调用 API 注册服务
reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
}
复制代码
com.alibaba.nacos.client.naming.net.NamingProxy#registerService
这里逻辑比较简单:封装请求并调用 API
NamingProxy#reqApi
public String reqApi(String api, Map<String, String> params, Map<String, String> body, List<String> servers,
String method) throws NacosException {
params.put(CommonParams.NAMESPACE_ID, getNamespaceId());
// 判断 nacos-Server 服务信息是否为空
if (CollectionUtils.isEmpty(servers) && StringUtils.isBlank(nacosDomain)) {
throw new NacosException(NacosException.INVALID_PARAM, "no server available");
}
NacosException exception = new NacosException();
// 如果只配置一个节点,则重试执行
if (StringUtils.isNotBlank(nacosDomain)) {
for (int i = 0; i < maxRetry; i++) {
try {
return callServer(api, params, body, nacosDomain, method);
} catch (NacosException e) {
exception = e;
if (NAMING_LOGGER.isDebugEnabled()) {
NAMING_LOGGER.debug("request {} failed.", nacosDomain, e);
}
}
}
} else {
// 如果配置了多个节点,则随机一个节点执行
Random random = new Random(System.currentTimeMillis());
int index = random.nextInt(servers.size());
for (int i = 0; i < servers.size(); i++) {
String server = servers.get(index);
try {
return callServer(api, params, body, server, method);
} catch (NacosException e) {
exception = e;
if (NAMING_LOGGER.isDebugEnabled()) {
NAMING_LOGGER.debug("request {} failed.", server, e);
}
}
index = (index + 1) % servers.size();
}
}
NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", api, servers, exception.getErrCode(),
exception.getErrMsg());
throw new NacosException(exception.getErrCode(),
"failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());
}
复制代码
NamingProxy#callServer
public String callServer(String api, Map<String, String> params, Map<String, String> body, String curServer,
String method) throws NacosException {
long start = System.currentTimeMillis();
long end = 0;
injectSecurityInfo(params);
Header header = builderHeader();
// 拼装请求 url
String url;
if (curServer.startsWith(UtilAndComs.HTTPS) || curServer.startsWith(UtilAndComs.HTTP)) {
url = curServer + api;
} else {
if (!IPUtil.containsPort(curServer)) {
curServer = curServer + IPUtil.IP_PORT_SPLITER + serverPort;
}
url = NamingHttpClientManager.getInstance().getPrefix() + curServer + api;
}
try {
// 通过 nacosRestTemplate 发起 http 请求
HttpRestResult<String> restResult = nacosRestTemplate
.exchangeForm(url, header, Query.newInstance().initParams(params), body, method, String.class);
end = System.currentTimeMillis();
MetricsMonitor.getNamingRequestMonitor(method, url, String.valueOf(restResult.getCode()))
.observe(end - start);
// 判断是否正常返回
if (restResult.ok()) {
return restResult.getData();
}
if (HttpStatus.SC_NOT_MODIFIED == restResult.getCode()) {
return StringUtils.EMPTY;
}
throw new NacosException(restResult.getCode(), restResult.getMessage());
} catch (Exception e) {
NAMING_LOGGER.error("[NA] failed to request", e);
throw new NacosException(NacosException.SERVER_ERROR, e);
}
}
复制代码
这里最终就是通过 http 请求到服务端,将节点的信息注册到服务端。
服务注册流程 - 服务端
从客户端的代码来看,最终发送 http 请求时,请求的是 /v1/ns/instance
接口。这个接口是由 com.alibaba.nacos.naming.controllers.InstanceController#register
提供。
InstanceController#register
@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {
final String namespaceId = WebUtils
.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
NamingUtils.checkServiceNameFormat(serviceName);
// 解析请求参数,获取节点信息
final Instance instance = parseInstance(request);
// 发起注册
getInstanceOperator().registerInstance(namespaceId, serviceName, instance);
return "ok";
}
复制代码
InstanceOperatorServiceImpl#registerInstance
@Override
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
com.alibaba.nacos.naming.core.Instance coreInstance = (com.alibaba.nacos.naming.core.Instance) instance;
serviceManager.registerInstance(namespaceId, serviceName, coreInstance);
}
复制代码
ServiceManager#registerInstance
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
//创建一个空服务,在Nacos控制台服务列表展示的服务信息,实际上是初始化一个serviceMap,它
是一个ConcurrentHashMap集合
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
// 根据命名空间和服务名称获取一个待注册的服务节点
Service service = getService(namespaceId, serviceName);
if (service == null) {
throw new NacosException(NacosException.INVALID_PARAM,
"service not found, namespace: " + namespaceId + ", service: " + serviceName);
}
//调用addInstance创建一个服务实例
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
复制代码
ServiceManager#addInstance
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
throws NacosException {
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
Service service = getService(namespaceId, serviceName);
synchronized (service) {
List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
Instances instances = new Instances();
instances.setInstanceList(instanceList);
consistencyService.put(key, instances);
}
}
复制代码
把服务实例添加到集合中,然后基于一致性协议进行数据的同步。
RaftConsistencyServiceImpl#put
这里可以看到,
ConsistencyService
有很多的实现类,在集群模式下,一般 Nacos 的使用 raft 协议
@Override
public void put(String key, Record value) throws NacosException {
checkIsStopWork();
try {
raftCore.signalPublish(key, value);
} catch (Exception e) {
Loggers.RAFT.error("Raft put failed.", e);
throw new NacosException(NacosException.SERVER_ERROR, "Raft put failed, key:" + key + ", value:" + value,
e);
}
}
复制代码
这里就是基于 raft 协议完成后续的节点信息的同步。
服务发现流程 - 消费方
看完提供方如何将自己的信息注册到注册中心,接下来我们看看消费方是如何获取到提供方服务列表的。
NacosDiscoveryClient#getInstances
@Override
public List<ServiceInstance> getInstances(String serviceId) {
try {
return serviceDiscovery.getInstances(serviceId);
}
catch (Exception e) {
throw new RuntimeException(
"Can not get hosts from nacos server. serviceId: " + serviceId, e);
}
}
复制代码
在 Spring Cloud 中,服务发现的客户端是 org.springframework.cloud.client.discovery.DiscoveryClient
其中提供两个主要的方法:
org.springframework.cloud.client.discovery.DiscoveryClient#getInstances
获取指定服务的所有节点。org.springframework.cloud.client.discovery.DiscoveryClient#getServices
获取所有的服务列表。 这里的实现类是com.alibaba.cloud.nacos.discovery.NacosDiscoveryClient
NacosServiceDiscovery#getInstances
public List<ServiceInstance> getInstances(String serviceId) throws NacosException {
String group = discoveryProperties.getGroup();
List<Instance> instances = namingService().selectInstances(serviceId, group,
true);
return hostToServiceInstanceList(instances, serviceId);
}
复制代码
这里主要做了两个事:
- 通过 namingService 获取服务列表。
- 将 nacos 返回的服务列表转换为 Spring Cloud 中通用的
org.springframework.cloud.client.ServiceInstance
对象
NacosNamingService#selectInstances
@Override
public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy,
boolean subscribe) throws NacosException {
ServiceInfo serviceInfo;
// 是否订阅服务地址的变化,默认为 ture
if (subscribe) {
serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName),
StringUtils.join(clusters, ","));
} else {
serviceInfo = hostReactor
.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName),
StringUtils.join(clusters, ","));
}
// 有入参 healthy,挑选列表中健康的节点并返回。
return selectInstances(serviceInfo, healthy);
}
复制代码
HostReactor#getServiceInfo
public ServiceInfo getServiceInfo(final String serviceName, final String clusters) {
NAMING_LOGGER.debug("failover-mode: " + failoverReactor.isFailoverSwitch());
String key = ServiceInfo.getKey(serviceName, clusters);
if (failoverReactor.isFailoverSwitch()) {
return failoverReactor.getService(key);
}
// 获取缓存对象
ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters);
// 如果缓存为空,则更新缓存
if (null == serviceObj) {
serviceObj = new ServiceInfo(serviceName, clusters);
serviceInfoMap.put(serviceObj.getKey(), serviceObj);
// 将 serviceName 加入待更新列表
updatingMap.put(serviceName, new Object());
// 立刻启动更新
updateServiceNow(serviceName, clusters);
// 将 serviceName 从待更新列表中移出
updatingMap.remove(serviceName);
// 如果当前 serviceName 在待更新列表中,等待。
} else if (updatingMap.containsKey(serviceName)) {
if (UPDATE_HOLD_INTERVAL > 0) {
// hold a moment waiting for update finish
synchronized (serviceObj) {
try {
serviceObj.wait(UPDATE_HOLD_INTERVAL);
} catch (InterruptedException e) {
NAMING_LOGGER
.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);
}
}
}
}
// 开启任务更新环境
scheduleUpdateIfAbsent(serviceName, clusters);
return serviceInfoMap.get(serviceObj.getKey());
}
复制代码
com.alibaba.nacos.client.naming.core.HostReactor#getServiceInfo
中主要有两个逻辑:
- 本地缓存为空,updateServiceNow,立即加载服务信息。
- scheduleUpdateIfAbsent 开启定时调度,定时去查询服务信息。
HostReactor#updateServiceNow & HostReactor#updateService
private void updateServiceNow(String serviceName, String clusters) {
try {
// 调用 updateService
updateService(serviceName, clusters);
} catch (NacosException e) {
NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);
}
}
public void updateService(String serviceName, String clusters) throws NacosException {
// 获取更新前的节点
ServiceInfo oldService = getServiceInfo0(serviceName, clusters);
try {
// 调用 api 获取服务的列表。
String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUdpPort(), false);
如果服务端返回的列表不为空,则更新缓存
if (StringUtils.isNotEmpty(result)) {
processServiceJson(result);
}
} finally {
if (oldService != null) {
synchronized (oldService) {
oldService.notifyAll();
}
}
}
}
复制代码
updateService 里面主要做了两个事情:
- 请求服务端获取 serviceName 的节点列表
- 根据查询的数据更新缓存
HostReactor#processServiceJson
public ServiceInfo processServiceJson(String json) {
ServiceInfo serviceInfo = JacksonUtils.toObj(json, ServiceInfo.class);
ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey());
if (pushEmptyProtection && !serviceInfo.validate()) {
//empty or error push, just ignore
return oldService;
}
boolean changed = false;
// 如果 oldService 不为空,则根据与 newService 做比较,确认新增及删除的部分并发送节点变化的事件
if (oldService != null) {
if (oldService.getLastRefTime() > serviceInfo.getLastRefTime()) {
NAMING_LOGGER.warn("out of date data received, old-t: " + oldService.getLastRefTime() + ", new-t: "
+ serviceInfo.getLastRefTime());
}
serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
Map<String, Instance> oldHostMap = new HashMap<String, Instance>(oldService.getHosts().size());
for (Instance host : oldService.getHosts()) {
oldHostMap.put(host.toInetAddr(), host);
}
Map<String, Instance> newHostMap = new HashMap<String, Instance>(serviceInfo.getHosts().size());
for (Instance host : serviceInfo.getHosts()) {
newHostMap.put(host.toInetAddr(), host);
}
Set<Instance> modHosts = new HashSet<Instance>();
Set<Instance> newHosts = new HashSet<Instance>();
Set<Instance> remvHosts = new HashSet<Instance>();
// ... 省略部分代码
serviceInfo.setJsonFromServer(json);
if (newHosts.size() > 0 || remvHosts.size() > 0 || modHosts.size() > 0) {
NotifyCenter.publishEvent(new InstancesChangeEvent(serviceInfo.getName(), serviceInfo.getGroupName(),
serviceInfo.getClusters(), serviceInfo.getHosts()));
DiskCache.write(serviceInfo, cacheDir);
}
// 如果 oldService 为空则直接更新缓存并发送节点变化的事件
} else {
changed = true;
NAMING_LOGGER.info("init new ips(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() + " -> "
+ JacksonUtils.toJson(serviceInfo.getHosts()));
serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
NotifyCenter.publishEvent(new InstancesChangeEvent(serviceInfo.getName(), serviceInfo.getGroupName(),
serviceInfo.getClusters(), serviceInfo.getHosts()));
serviceInfo.setJsonFromServer(json);
DiskCache.write(serviceInfo, cacheDir);
}
MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());
if (changed) {
NAMING_LOGGER.info("current ips:(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() + " -> "
+ JacksonUtils.toJson(serviceInfo.getHosts()));
}
return serviceInfo;
}
复制代码
com.alibaba.nacos.client.naming.core.HostReactor#processServiceJson
的代码比较长,主要有两个分支:
- 如果 oldService 不为空,则比较 OldService 与 newService,如果是节点信息变化,需要更新心跳信息。最后更新缓存并发送节点变化的事件。
- 如果 oldService 为空,则直接更新缓存并发送节点变化的事件。
更新缓存后再由
com.alibaba.nacos.client.naming.core.HostReactor#getServiceInfo
返回给NacosNamingService#selectInstances
至此。就是通过NacosDiscoveryClient
获取指定 service 的节点信息的大概流程。
总结
本小节主要讨论了 Nacos 作为注册中心在 Spring Cloud 中的实现。
关于 Nacos 如何实现注册中心的一些基本应用:
- 可以保存微服务的信息。
- 服务端保存微服务的注册信息,并使用 raft 协议进行数据同步,保证数据的一致性。
- 微服务之前可以相互查询保存的信息。
- 通过
NacosServiceDiscovery#getInstances
查询服务端的节点信息,并同步修改本地的缓存。
- 通过
- 可以检测到服务的状态是否健康,并及时更新。
- 在
HostReactor#getServiceInfo
方法中,会开启定时任务,定时检测服务节点的变化情况,如有节点发生变化,则会重新拉取新的数据,并更新缓存。
- 在
以及 Nacos 在 Sring Cloud 中的实现。
-
服务提供方主要通过
NacosServiceRegistry#register
完成服务的注册,将服务的节点信息注册到 Nacos 中,服务端的InstanceController#register
接收到注册请求,把服务实例添加到集合中,然后基于一致性协议进行数据的同步。 -
服务消费方主要通过
NacosDiscoveryClient#getInstances
查找对应服务的节点信息。