通过对nacos 作为注册中心的简单使用,我们观察到一个现象,就是服务启动后就会注册到nacos服务端。于是我们就有个疑问了,nacos 是怎么做到的,容器启动后就立刻注册服务上去呢?是通过什么方式或者机制完成注册的?熟悉spring 的都知道spring 有个事件机制,编程时我们可以通过监听容器启动从而做一些事情,nacos 启动注册是否也是基于事件机制呢?本片文章我们将解开上述问题的奥秘。
- 问题抛出
- nacos 如何做到服务启动便注册服务的。
- 寻找源码分析入口
笔者在看nacos 源码之前,阅读过eureka 的源码,知道一个注册中心的一些基本规范,所以在阅读nacos 的源码会比较轻松。如果你在这之前没有阅读任何有关注册中心的任何源码也没有关系,注册中心必须实现的两个基本功能:1.服务注册,2.服务发现。服务注册接口 一般都是 xxxxServiceRegistry。spring 的命名一般都很规范。
注意:spring cloud 体系都是基于springboot 的自动装配机制做的整合,如果对springboot的自动装配机制不熟悉的话,建议先学习springboot的自动装配机制。
在寻找分析入口之前我们先看一下代码:
@EnableDiscoveryClient // 服务注册
@SpringBootApplication
public class RoleServiceApplication {
public static void main(String[] args) {
SpringApplication.run(RoleServiceApplication.class, args);
}
}
上面是我们开发时的代码,开启了服务注册,我们的入口就只有两个:@EnableDiscoveryClient,SpringApplication.run(RoleServiceApplication.class, args); 除了这个我们其他的啥也不知道,如果你知道springboot的自动装配机制的话,就会从@EnableDiscoveryClient 入手,不知道也没有关系,我们就依次看一下,SpringApplication.run(RoleServiceApplication.class, args); 这个里面你好像不会看到直接 和nacos 相关的东西,因为是启动spring 容器和tomcat 服务。那么我们就尝试着先看看@EnableDiscoveryClient
- 入口 @EnableDiscoveryClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class) // 引入ImportSelector,自动装配机制中的一部分
public @interface EnableDiscoveryClient {
/**
* If true, the ServiceRegistry will automatically register the local server.
* @return - {@code true} if you want to automatically register.
*/
boolean autoRegister() default true;
}
上面引入了
EnableDiscoveryClientImportSelector.class
自然我们就要看一看究竟。
- EnableDiscoveryClientImportSelector
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
String[] imports = super.selectImports(metadata); // 这里是关键
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));
boolean autoRegister = attributes.getBoolean("autoRegister");
if (autoRegister) { // boolean autoRegister() default true;
List importsList = new ArrayList<>(Arrays.asList(imports)); // 添加进去
importsList.add(
"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
imports = importsList.toArray(new String[0]); // 返回数组
}
else {
Environment env = getEnvironment();
if (ConfigurableEnvironment.class.isInstance(env)) {
ConfigurableEnvironment configEnv = (ConfigurableEnvironment) env;
LinkedHashMap map = new LinkedHashMap<>();
map.put("spring.cloud.service-registry.auto-registration.enabled", false);
MapPropertySource propertySource = new MapPropertySource(
"springCloudDiscoveryClient", map);
configEnv.getPropertySources().addLast(propertySource);
}
}
return imports;
}
........省略部分
String[] imports = super.selectImports(metadata);
我们看到了这个,这个也是springboot自装配机制中的核心,这里不再解析,直接看加载的文件META-INF/spring.factories
- META-INF/spring.factories (spring-cloud-commons-2.2.0.RELEASE.jar)
# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.client.CommonsClientAutoConfiguration,\
org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration,\
org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\
org.springframework.cloud.commons.httpclient.HttpClientConfiguration,\
org.springframework.cloud.commons.util.UtilAutoConfiguration,\
org.springframework.cloud.configuration.CompatibilityVerifierAutoConfiguration,\
org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration
org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration
被自动装配了。ok 我们自然要进去看一下。
- ServiceRegistryAutoConfiguration
@Configuration(proxyBeanMethods = false)
public class ServiceRegistryAutoConfiguration {
@ConditionalOnBean(ServiceRegistry.class)
@ConditionalOnClass(Endpoint.class)
protected class ServiceRegistryEndpointConfiguration {
@Autowired(required = false)
private Registration registration;
@Bean
@ConditionalOnEnabledEndpoint
public ServiceRegistryEndpoint serviceRegistryEndpoint(
ServiceRegistry serviceRegistry) {
ServiceRegistryEndpoint endpoint = new ServiceRegistryEndpoint(
serviceRegistry);
endpoint.setRegistration(this.registration);
return endpoint;
}
}
}
到这里我们只看到在这里 创建了一个
ServiceRegistryEndpoint
并且依赖ServiceRegistry
其他的都看不出来了,到这里好像就断了,不知道是否要走下去。ok,自然如果我们继续往下看,就关注两个对象嘛:ServiceRegistry
,ServiceRegistryEndpoint
这个两个对象好像都和服务注册有点关系,看名称也是知道的。既然ServiceRegistryEndpoint
依赖ServiceRegistry
我们就从ServiceRegistry
开始。
- ServiceRegistry
/**
* Contract to register and deregister instances with a Service Registry.
*
* @param registration meta data
* @author Spencer Gibb
* @since 1.2.0
*/
public interface ServiceRegistry {
/**
* Registers the registration. A registration typically has information about an
* instance, such as its hostname and port.
* @param registration registration meta data
*/
void register(R registration);
/**
* Deregisters the registration.
* @param registration registration meta data
*/
void deregister(R registration);
/**
* Closes the ServiceRegistry. This is a lifecycle method.
*/
void close();
/**
* Sets the status of the registration. The status values are determined by the
* individual implementations.
* @param registration The registration to update.
* @param status The status to set.
* @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
*/
void setStatus(R registration, String status);
/**
* Gets the status of a particular registration.
* @param registration The registration to query.
* @param The type of the status.
* @return The status of the registration.
* @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
*/
T getStatus(R registration);
}
我们到这里就看到一个接口的定义,自然我们要关注他的实现类,我们发现就只有一个实现类:
NacosServiceRegistry
,自然要去看一看。
- NacosServiceRegistry
/**
* @author xiaojing
* @author Mercy
*/
public class NacosServiceRegistry implements ServiceRegistry {
private static final Logger log = LoggerFactory.getLogger(NacosServiceRegistry.class);
private final NacosDiscoveryProperties nacosDiscoveryProperties;
private final NamingService namingService;
public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.namingService = nacosDiscoveryProperties.namingServiceInstance();
}
// 服务注册
@Override
public void register(Registration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
Instance instance = getNacosInstanceFromRegistration(registration); // 服务实例信息,如port,host等信息
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);
// rethrow a RuntimeException if the registration is failed.
// issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
rethrowRuntimeException(e);
}
}
// 服务实例信息,就是传递数据而已
private Instance getNacosInstanceFromRegistration(Registration registration) {
Instance instance = new Instance();
instance.setIp(registration.getHost());
instance.setPort(registration.getPort());
instance.setWeight(nacosDiscoveryProperties.getWeight());
instance.setClusterName(nacosDiscoveryProperties.getClusterName());
instance.setMetadata(registration.getMetadata());
return instance;
}
......省略部分
ok, 上面我们找到了服务注册的 逻辑,算是找到了服务注册的接口,到这里我们只成功了一半,因为我们只找到了服务注册的方法
register(Registration registration)
,我们并没有找到在容器启动时,是如何调用到这个方法的。接下来我们就来寻找答案。如果对spring或者springboot启动源码非常熟悉的同学,能很快找到答案,那么我们不熟悉或者以前压根没有看过容器启动源码的咋搞呢?别忘了,我们知道容器肯定会调用register(Registration registration)
,搞个断点,debug 一下,在idea 中看一下方法的调用链就ok。
- 方法调用链
ok.我们知道这个调用链以后,就可以开始分析springboot 在启动的时候时如何调用到
register(Registration registration)
方法的。
- NacosServiceRegistry 对象创建
在分析调用过程之前,我们先解决一个问题,NacosServiceRegistry 对象是如何创建的?
NacosServiceRegistryAutoConfiguration
找到这个配置对象 - NacosServiceRegistryAutoConfiguration
/**
* @author xiaojing
* @author Mercy
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
AutoServiceRegistrationAutoConfiguration.class,
NacosDiscoveryAutoConfiguration.class }) // 在这些对象后面创建
public class NacosServiceRegistryAutoConfiguration {
@Bean // 创建对象
public NacosServiceRegistry nacosServiceRegistry(
NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosServiceRegistry(nacosDiscoveryProperties);
}
ok 这里我们找到了
NacosServiceRegistry
对象的创建。我们现在开始分析启动注册过程。
- 根据上图的调用链可以看出来,容器启动后会到
ServletWebServerApplicationContext.finishRefresh()
- finishRefresh()
@Override
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) { // 发布一个启动完成的事件
publishEvent(new ServletWebServerInitializedEvent(webServer, this)); // 基础至父类 `AbstractApplicationContext`
}
}
- AbstractApplicationContext
@Override
public void publishEvent(ApplicationEvent event) {
publishEvent(event, null); // 调用下面这个方法
}
/**
* Publish the given event to all listeners.
* @param event the event to publish (may be an {@link ApplicationEvent}
* or a payload object to be turned into a {@link PayloadApplicationEvent})
* @param eventType the resolved event type, if known
* @since 4.2
*/
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent>) applicationEvent).getResolvableType();
}
}
// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
// 把事件广播出去
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
-
SimpleApplicationEventMulticaster
#multicastEvent()方法
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event)); // 调用下面的方法
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
上面的方法都会调一个方法
invokeListener(listener, event);
ok,我们就要去看一下里面是,从名称看就调用监听器,处理事件。
/**
* Invoke the given listener with the given event.
* @param listener the ApplicationListener to invoke
* @param event the current event to propagate
* @since 4.1
*/
protected void invokeListener(ApplicationListener> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
}
doInvokeListener(listener, event);
不管条件是否满足,都会调用doInvokeListener(listener, event);
@SuppressWarnings({"rawtypes", "unchecked"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event); // 处理事件
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message.
Log logger = LogFactory.getLog(getClass());
if (logger.isTraceEnabled()) {
logger.trace("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}
回去看一些调用链 会到
AbstractAutoServiceRegistration
的 onApplicationEvent()
- AbstractAutoServiceRegistration
public abstract class AbstractAutoServiceRegistration
implements AutoServiceRegistration, ApplicationContextAware,
ApplicationListener { // 监听事件 WebServerInitializedEvent ,ServletWebServerInitializedEvent 是 WebServerInitializedEvent的子类
........
@Override
@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(); // 看这个方法
}
}
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);
}
}
- register() 调用注册方法
private final ServiceRegistry serviceRegistry;
/**
* Register the local service with the {@link ServiceRegistry}.
*/
protected void register() {
this.serviceRegistry.register(getRegistration());
}
ok ,我们到这里就看到了,在容器启动后,发送事件,由WebServerInitializedEvent 事件监听者
AbstractAutoServiceRegistration
处理事件,在onApplicationEvent 方法中注册服务。
- 问题:serviceRegistry 何时初始化的?
上面我们知道ServiceRegistry
对象的初始化,在NacosServiceRegistryAutoConfiguration
d对象中也初始化了NacosAutoServiceRegistration
,该对象继承AbstractAutoServiceRegistration
@Bean
public NacosServiceRegistry nacosServiceRegistry(
NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosServiceRegistry(nacosDiscoveryProperties);
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosRegistration nacosRegistration(
NacosDiscoveryProperties nacosDiscoveryProperties,
ApplicationContext context) {
return new NacosRegistration(nacosDiscoveryProperties, context);
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosAutoServiceRegistration nacosAutoServiceRegistration(
NacosServiceRegistry registry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
NacosRegistration registration) {
return new NacosAutoServiceRegistration(registry,
autoServiceRegistrationProperties, registration); // 初始化的时候传入 NacosServiceRegistry
}
- NacosAutoServiceRegistration
public NacosAutoServiceRegistration(ServiceRegistry serviceRegistry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
NacosRegistration registration) {
super(serviceRegistry, autoServiceRegistrationProperties); // 父级构造器
this.registration = registration;
}
ok,我们现在也解决了 NacosAutoServiceRegistration 实例化 serviceRegistry。现在我们回过头了看下面的代码:
/**
* Register the local service with the {@link ServiceRegistry}.
*/
protected void register() {
this.serviceRegistry.register(getRegistration()); // serviceRegistry = > NacosServiceRegistry
}
- NacosServiceRegistry
@Override
public void register(Registration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
Instance instance = getNacosInstanceFromRegistration(registration);
try {
namingService.registerInstance(serviceId, group, instance); // 调用 namingService 的注册方法。
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);
}
}
ok,我们现在终于知道了 在spring 容器启动后,用事件的方式,通知到监听者,监听者的事件处理,实现启动注册服务的逻辑。后续,会继续分析
namingService.registerInstance(serviceId, group, instance);