前言
关于Nacos的文章,可能会分成三篇来分析,本文是第一篇
- 第一篇:Nacos 服务注册
- 第二篇:Nacos 服务发现
- 第三篇:Nacos 配置中心
文章脉络
Spring Cloud 服务注册原理
1. spring cloud common组件导入spring.factories下的配置类
- /META-INF/spring.factories是spring默认的配置类扫描路径(会在之后的spring文章中提及)
- 类似的还有spring.handles和spring.schema,用于解析集成框架的自定义标签,如 dubbo 和 mybatis 自定义标签
- 这里我们重点关注 AutoServiceRegistrationAutoConfiguration 配置类
2. AutoServiceRegistrationAutoConfiguration 配置类注入 AutoServiceRegistration
@Configuration(proxyBeanMethods = false)
@Import(AutoServiceRegistrationConfiguration.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
matchIfMissing = true)
public class AutoServiceRegistrationAutoConfiguration {
@Autowired(required = false)
private AutoServiceRegistration autoServiceRegistration;
... ...
}
- AutoServiceRegistration 是一个接口,AbstractAutoServiceRegistration 是它的实现类,通过@Autowired自动注入
- 可以通过继承 AbstractAutoServiceRegistration 重写抽象方法来实现自定义的注册中心,Nacos 就是通过这种方式
- 文末还会贴出大佬实现的Redis服务注册中心的文章链接,下图可以看到AutoServiceRegistration的继承关系
3. 触发AutoServiceRegistration的容器刷新事件,注册ServiceRegistry
- 上一步也谈到了 AbstractAutoServiceRegistration是AutoServiceRegistration的实现类
- 实现了 ApplicationListener 接口,会在Web服务器初始化完成后触发 Spring 容器事件,执行 onApplicationEvent()
- 通过 @SuppressWarnings(“deprecation”) 调用被丢弃的 bind(),从而执行 start() 中的 registry()
public abstract class AbstractAutoServiceRegistration<R extends Registration>
implements AutoServiceRegistration, ApplicationContextAware,
ApplicationListener<WebServerInitializedEvent> {
private final ServiceRegistry<R> serviceRegistry;
@Override
//调用 @Deprecated 修饰的方法而不触发编译器警告
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
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());
this.start();
}
}
Nacos 服务注册原理
上一节讲到了 AbstractAutoServiceRegistration 会调用 register() 注册 ServiceReistry
public class NacosServiceRegistry implements ServiceRegistry<Registration> {
@Override
public void register(Registration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
NamingService namingService = namingService();
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
Instance instance = getNacosInstanceFromRegistration(registration);
try {
//注册实例到命名服务
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);
rethrowRuntimeException(e);
}
}
... ...
}
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
if (instance.isEphemeral()) {
BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
//注册服务的同时发送心跳
beatReactor.addBeatInfo(groupedServiceName, beatInfo);
}
//通过serverProxy来注册服务
serverProxy.registerService(groupedServiceName, groupName, instance);
}
发送心跳
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
BeatInfo existBeat = null;
//fix #1733
if ((existBeat = dom2Beat.remove(key)) != null) {
existBeat.setStopped(true);
}
dom2Beat.put(key, beatInfo);
executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
服务注册
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();
String url;
if (curServer.startsWith(UtilAndComs.HTTPS) || curServer.startsWith(UtilAndComs.HTTP)) {
url = curServer + api;
} else {
if (!curServer.contains(UtilAndComs.SERVER_ADDR_IP_SPLITER)) {
curServer = curServer + UtilAndComs.SERVER_ADDR_IP_SPLITER + serverPort;
}
url = NamingHttpClientManager.getInstance().getPrefix() + curServer + api;
}
try {
//底层都是基于HTTP协议完成请求的,所以注册服务就是发送一个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);
}
}
服务注册的主要代码如上图所示,可以看到是通过 HttpClient 发送请求,源码太多就不贴出来了,感兴趣的小伙伴可以去 debug 源码加深下印象
参考文章
深入理解SpringCloud之自动注册服务
Spring Boot 之 spring.factories
SpringCloudAlibaba——Nacos实现原理详解