nacos注册中心有什么用

参考:Nacos服务注册源码分析 Spring Boot版本:2.2.9.RELEASE Spring Cloud版本:Hoxton.SR6 Spring Cloud Alibaba版本:2.2.1.RELEASE Nacos版本:1.3.1 1 @EnableDiscoveryClient

参考:Nacos服务注册源码分析

Spring Boot版本:2.2.9.RELEASE

Spring Cloud版本:Hoxton.SR6

Spring Cloud Alibaba版本:2.2.1.RELEASE

Nacos版本:1.3.1

1 @EnableDiscoveryClient

如果需要启用服务注册功能,需要在启动类上面添加@EnableDiscoveryClient注解。

查看@EnableDiscoveryClient的源码:

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@Import(EnableDiscoveryClientImportSelector.class)

public @interface EnableDiscoveryClient {

/**

* 默认返回值为true。

* 如果返回值为true,ServiceRegistry将会自动将本服务注册到服务注册中心。

*/

boolean autoRegister() default true;

}

@EnableDiscoveryClient注解的源码中有一个方法autoRegister(),默认返回true,表示希望自动注册服务。

@EnableDiscoveryClient还通过@Import注解引入了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));

// 获取@EnableDiscoveryClient注解的属性autoRegister的值

boolean autoRegister = attributes.getBoolean("autoRegister");

// 如果autoRegister == true,加载AutoServiceRegistrationConfiguration配置类

if (autoRegister) {

List importsList = new ArrayList<>(Arrays.asList(imports));

importsList.add(

"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");

imports = importsList.toArray(new String[0]);

}

// 如果autoRegister == false,设置spring.cloud.service-registry.auto-registration.enabled=false,关闭服务自动注册功能

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;

}

@Override

protected boolean isEnabled() {

return getEnvironment().getProperty("spring.cloud.discovery.enabled",

Boolean.class, Boolean.TRUE);

}

@Override

protected boolean hasDefaultFactory() {

return true;

}

}

主要关注selectImports(...)这个方法。首先获取@EnableDiscoveryClient注解的属性autoRegister的值。

如果autoRegister == true,加载org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration配置类的配置。

如果autoRegister == false,设置spring.cloud.service-registry.auto-registration.enabled=false,关闭自动服务注册功能。因为服务自动注册相关的类在注册到Spring容器的时候都有一个条件@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)。

查看AutoServiceRegistrationConfiguration配置类源码:

@Configuration(proxyBeanMethods = false)

@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)

@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",

matchIfMissing = true)

public class AutoServiceRegistrationConfiguration {

}

可以看到,这个类加载了AutoServiceRegistrationProperties这个属性类,这个类里面维护了所有spring.cloud.service-registry.auto-registration开头的属性的配置值。

@ConfigurationProperties("spring.cloud.service-registry.auto-registration")

public class AutoServiceRegistrationProperties {

/** Whether service auto-registration is enabled. Defaults to true. */

private boolean enabled = true;

/** Whether to register the management as a service. Defaults to true. */

private boolean registerManagement = true;

/**

* Whether startup fails if there is no AutoServiceRegistration. Defaults to false.

*/

private boolean failFast = false;

...

}

可以看到,spring.cloud.service-registry.auto-registration.enabled默认值就是true。也就是说,如果没有使用@EnableDiscoveryClient注解,服务也会被自动注册,因为@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)这个条件默认情况下就是满足的。

但是如果不想自动注册服务,可以通过使用@EnableDiscoveryClient(autoRegister = false),或者直接在配置文件中设置spring.cloud.service-registry.auto-registration.enabled=false。

2 Spring Cloud定义的服务发现的统一规范

拥有服务注册的中间件有很多,比如Euerka、Consul、ZooKeeper、Nacos等,想要接入Spring Cloud就必须遵守一套统一的规范,Spring Cloud将这套规范定义在了Spring Cloud Common中:

spring-cloud-commons

-- org.springframework.cloud

-- client

-- discovery

-- loadbalancer

-- serviceregistry

discovery包下面定义了服务发现的规范,loadbalancer包下面定义了负载均衡的规范,serviceregistry包下面定义了服务注册的规范。

org.springframework.cloud.client.serviceregistry包下面有三个接口,这是服务注册的核心接口:

AutoServiceRegistration接口

Registration接口

ServiceRegistry接口

2.1 AutoServiceRegistration接口

AutoServiceRegistration用于服务自动注册。自动注册的意思就是,服务启动后自动把服务信息注册到注册中心。

public interface AutoServiceRegistration {

}

这个接口没有定义方法,它的存在就是要规范实现必须要有自动注册。

Spring Cloud中有一个抽象类AbstractAutoServiceRegistration实现了这个接口。

2.2 Registration接口

Registration存储服务信息,用于规范将什么信息注册到注册中心。

public interface Registration extends ServiceInstance {

}

Registration继承ServiceInstance接口,ServiceInstance接口定义了一个服务实例应该具有哪些服务信息,源码如下:

public interface ServiceInstance {

// 返回服务实例唯一ID

default String getInstanceId() {

return null;

}

// 返回服务ID,服务名称

String getServiceId();

// 返回服务实例所在的主机的host值

String getHost();

// 返回服务实例的port

int getPort();

// 返回服务的实例port是否启用了https协议

boolean isSecure();

// 返回服务的URI地址

URI getUri();

// 返回服务实力的元数据

Map getMetadata();

// 返回服务实例的scheme

default String getScheme() {

return null;

}

}

2.3 ServiceRegistry接口

ServiceRegistry是服务注册接口,用来向注册中心注册服务。

public interface ServiceRegistry {

// 注册服务,registration保存了服务的信息

void register(R registration);

// 反注册,也就是从注册中心移除注册的服务信息

void deregister(R registration);

// 关闭ServiceRegistry,这是一个生命周期函数

void close();

/**

* 设置服务的状态,status的值取决于具体的实现。

* 查看org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint

*/

void setStatus(R registration, String status);

/**

* 获取服务状态值。

* 查看org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint

*/

T getStatus(R registration);

}

3 Nacos服务注册的实现

Nacos服务注册模块按照Spring Cloud的规范,实现了AutoServiceRegistration、Registration、ServiceRegistry三个接口,具体的实现了分别是:

NacosAutoServiceRegistration类

NacosRegistration类

NacosServiceRegistry类

这三个类位于spring-cloud-starter-alibaba-nacos-discovery包中的com.alibaba.cloud.nacos.registry包路径下。

3.1 NacosRegistration

首先看看NacosRegistration,这个类管理了Nacos服务的基本信息,如服务名、服务地址和端口等信息。

它实现了Spring Cloud定义的规范接口Registration,同时也实现了ServiceInstance接口。

不过实际上Nacos服务的基本信息都是由NacosDiscoveryProperties这个类来保存的,NacosRegistration只是对NacosDiscoveryProperties进行了封装而已。

public class NacosRegistration implements Registration, ServiceInstance {

/**

* management port

*/

public static final String MANAGEMENT_PORT = "management.port";

/**

* management context-path.

*/

public static final String MANAGEMENT_CONTEXT_PATH = "management.context-path";

/**

* management address.

*/

public static final String MANAGEMENT_ADDRESS = "management.address";

/**

* management endpoints web base path.

*/

public static final String MANAGEMENT_ENDPOINT_BASE_PATH = "management.endpoints.web.base-path";

// 配置文件中spring.cloud.nacos.discovery开头的配置的映射类

private NacosDiscoveryProperties nacosDiscoveryProperties;

// 应用上下文

private ApplicationContext context;

// 构造函数注入

public NacosRegistration(NacosDiscoveryProperties nacosDiscoveryProperties,

ApplicationContext context) {

this.nacosDiscoveryProperties = nacosDiscoveryProperties;

this.context = context;

}

// 当构造函数执行之后执行的初始化

@PostConstruct

public void init() {

Map metadata = nacosDiscoveryProperties.getMetadata();

Environment env = context.getEnvironment();

String endpointBasePath = env.getProperty(MANAGEMENT_ENDPOINT_BASE_PATH);

if (!StringUtils.isEmpty(endpointBasePath)) {

metadata.put(MANAGEMENT_ENDPOINT_BASE_PATH, endpointBasePath);

}

Integer managementPort = ManagementServerPortUtils.getPort(context);

if (null != managementPort) {

metadata.put(MANAGEMENT_PORT, managementPort.toString());

String contextPath = env

.getProperty("management.server.servlet.context-path");

String address = env.getProperty("management.server.address");

if (!StringUtils.isEmpty(contextPath)) {

metadata.put(MANAGEMENT_CONTEXT_PATH, contextPath);

}

if (!StringUtils.isEmpty(address)) {

metadata.put(MANAGEMENT_ADDRESS, address);

}

}

if (null != nacosDiscoveryProperties.getHeartBeatInterval()) {

metadata.put(PreservedMetadataKeys.HEART_BEAT_INTERVAL,

nacosDiscoveryProperties.getHeartBeatInterval().toString());

}

if (null != nacosDiscoveryProperties.getHeartBeatTimeout()) {

metadata.put(PreservedMetadataKeys.HEART_BEAT_TIMEOUT,

nacosDiscoveryProperties.getHeartBeatTimeout().toString());

}

if (null != nacosDiscoveryProperties.getIpDeleteTimeout()) {

metadata.put(PreservedMetadataKeys.IP_DELETE_TIMEOUT,

nacosDiscoveryProperties.getIpDeleteTimeout().toString());

}

}

// getter、setter

...

}

3.2 NacosServiceRegistry

NacosServiceRegistry实现了Spring Cloud定义的ServiceRegistry接口,负责将服务实例注册到Nacos服务注册中心上面。

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);

try {

// 通过namingService将服务实例注册到注册中心

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);

}

}

@Override

public void deregister(Registration registration) {

log.info("De-registering from Nacos Server now...");

if (StringUtils.isEmpty(registration.getServiceId())) {

log.warn("No dom to de-register for nacos client...");

return;

}

// 获取namingService

NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();

// 服务名

String serviceId = registration.getServiceId();

// 分组

String group = nacosDiscoveryProperties.getGroup();

try {

// 通过namingService反注册当前服务实例

namingService.deregisterInstance(serviceId, group, registration.getHost(),

registration.getPort(), nacosDiscoveryProperties.getClusterName());

}

catch (Exception e) {

log.error("ERR_NACOS_DEREGISTER, de-register failed...{},",

registration.toString(), e);

}

log.info("De-registration finished.");

}

......

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;

}

}

主要是关注register(...)和deregister(...)方法,分别负责服务的注册和反注册。

最终都是通过Nacos中定义的NamingService来完成这两个功能的,NamingService在启动的时候封装了Nacos注册中心的信息,并且封装了服务注册和反注册相关的http请求。

3.3 NacosAutoServiceRegistration

NacosAutoServiceRegistration这个类实现了服务自动注册到Nacos注册中心的功能。这个类继承了AbstractAutoServiceRegistration类,大部分操作都是在抽象类中完成的。

查看抽象类源码:

public abstract class AbstractAutoServiceRegistration

implements AutoServiceRegistration, ApplicationContextAware,

ApplicationListener {

...

}

可以看到,实现了ApplicationListener,说明会被事件回调。当容器启动之后,应用上下文被刷新并且WebServer准备就绪之后,会触发WebServerInitializedEvent事件,那么抽象类中的onApplicationEvent(WebServerInitializedEvent event)方法就会被调用。查看源码:

@Override

@SuppressWarnings("deprecation")

public void onApplicationEvent(WebServerInitializedEvent event) {

// 调用bind方法

bind(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());

// 调用start方法

this.start();

}

实际调用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()方法

register();

if (shouldRegisterManagement()) {

registerManagement();

}

this.context.publishEvent(

new InstanceRegisteredEvent<>(this, getConfiguration()));

this.running.compareAndSet(false, true);

}

}

实际又调用的是register()方法:

protected void register() {

this.serviceRegistry.register(getRegistration());

}

实际调用的是serviceRegistry的register方法,而serviceRegistry是在构造NacosAutoServiceRegistration的时候传入的NacosServiceRegistry。所以实际上是调用的NacosServiceRegistry的register方法。

查看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实现服务注册

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);

}

}

最后由namingService实现了服务注册。

总结:服务的自动注册其实是利用了Spring的WebServerInitializedEvent事件,最终由namingService完成服务注册工作。

3.4 其他辅助类

上面三个类是Nacos按照规范实现了Spring Cloud定义的统一接口。但是只有这三个类是没法完成服务“自动”注册的,还需要一些辅助类来协助完成这项任务。

3.4.1 NacosDiscoveryProperties

前面说到NacosRegistration只是对NacosDiscoveryProperties进行了封装而已,服务的基本信息真正的是保存在NacosDiscoveryProperties里面的。查看源码:

@ConfigurationProperties("spring.cloud.nacos.discovery")

public class NacosDiscoveryProperties {

private static final Logger log = LoggerFactory

.getLogger(NacosDiscoveryProperties.class);

/**

* Prefix of {@link NacosDiscoveryProperties}.

*/

public static final String PREFIX = "spring.cloud.nacos.discovery";

private static final Pattern PATTERN = Pattern.compile("-(\\w)");

/**

* nacos discovery server address.

*/

private String serverAddr;

// 各种配置字段

......

@Autowired

private InetUtils inetUtils;

@Autowired

private Environment environment;

private static NamingService namingService;

private static NamingMaintainService namingMaintainService;

@PostConstruct

public void init() throws SocketException {

metadata.put(PreservedMetadataKeys.REGISTER_SOURCE, "SPRING_CLOUD");

if (secure) {

metadata.put("secure", "true");

}

serverAddr = Objects.toString(serverAddr, "");

if (serverAddr.endsWith("/")) {

serverAddr = serverAddr.substring(0, serverAddr.length() - 1);

}

endpoint = Objects.toString(endpoint, "");

namespace = Objects.toString(namespace, "");

logName = Objects.toString(logName, "");

if (StringUtils.isEmpty(ip)) {

// traversing network interfaces if didn't specify a interface

if (StringUtils.isEmpty(networkInterface)) {

ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();

}

else {

NetworkInterface netInterface = NetworkInterface

.getByName(networkInterface);

if (null == netInterface) {

throw new IllegalArgumentException(

"no such interface " + networkInterface);

}

Enumeration inetAddress = netInterface.getInetAddresses();

while (inetAddress.hasMoreElements()) {

InetAddress currentAddress = inetAddress.nextElement();

if (currentAddress instanceof Inet4Address

&& !currentAddress.isLoopbackAddress()) {

ip = currentAddress.getHostAddress();

break;

}

}

if (StringUtils.isEmpty(ip)) {

throw new RuntimeException("cannot find available ip from"

+ " network interface " + networkInterface);

}

}

}

this.overrideFromEnv(environment);

}

// getter、setter

......

public void overrideFromEnv(Environment env) {

if (StringUtils.isEmpty(this.getServerAddr())) {

String serverAddr = env

.resolvePlaceholders("${spring.cloud.nacos.discovery.server-addr:}");

if (StringUtils.isEmpty(serverAddr)) {

serverAddr = env.resolvePlaceholders(

"${spring.cloud.nacos.server-addr:localhost:8848}");

}

this.setServerAddr(serverAddr);

}

if (StringUtils.isEmpty(this.getNamespace())) {

this.setNamespace(env

.resolvePlaceholders("${spring.cloud.nacos.discovery.namespace:}"));

}

if (StringUtils.isEmpty(this.getAccessKey())) {

this.setAccessKey(env

.resolvePlaceholders("${spring.cloud.nacos.discovery.access-key:}"));

}

if (StringUtils.isEmpty(this.getSecretKey())) {

this.setSecretKey(env

.resolvePlaceholders("${spring.cloud.nacos.discovery.secret-key:}"));

}

if (StringUtils.isEmpty(this.getLogName())) {

this.setLogName(

env.resolvePlaceholders("${spring.cloud.nacos.discovery.log-name:}"));

}

if (StringUtils.isEmpty(this.getClusterName())) {

this.setClusterName(env.resolvePlaceholders(

"${spring.cloud.nacos.discovery.cluster-name:}"));

}

if (StringUtils.isEmpty(this.getEndpoint())) {

this.setEndpoint(

env.resolvePlaceholders("${spring.cloud.nacos.discovery.endpoint:}"));

}

if (StringUtils.isEmpty(this.getGroup())) {

this.setGroup(

env.resolvePlaceholders("${spring.cloud.nacos.discovery.group:}"));

}

if (StringUtils.isEmpty(this.getUsername())) {

this.setUsername(env.resolvePlaceholders("${spring.cloud.nacos.username:}"));

}

if (StringUtils.isEmpty(this.getPassword())) {

this.setPassword(env.resolvePlaceholders("${spring.cloud.nacos.password:}"));

}

}

public NamingService namingServiceInstance() {

if (null != namingService) {

return namingService;

}

try {

namingService = NacosFactory.createNamingService(getNacosProperties());

}

catch (Exception e) {

log.error("create naming service error!properties={},e=,", this, e);

return null;

}

return namingService;

}

@Deprecated

public NamingMaintainService namingMaintainServiceInstance() {

if (null != namingMaintainService) {

return namingMaintainService;

}

try {

namingMaintainService = NamingMaintainFactory

.createMaintainService(getNacosProperties());

}

catch (Exception e) {

log.error("create naming service error!properties={},e=,", this, e);

return null;

}

return namingMaintainService;

}

private Properties getNacosProperties() {

Properties properties = new Properties();

properties.put(SERVER_ADDR, serverAddr);

properties.put(USERNAME, Objects.toString(username, ""));

properties.put(PASSWORD, Objects.toString(password, ""));

properties.put(NAMESPACE, namespace);

properties.put(UtilAndComs.NACOS_NAMING_LOG_NAME, logName);

if (endpoint.contains(":")) {

int index = endpoint.indexOf(":");

properties.put(ENDPOINT, endpoint.substring(0, index));

properties.put(ENDPOINT_PORT, endpoint.substring(index + 1));

}

else {

properties.put(ENDPOINT, endpoint);

}

properties.put(ACCESS_KEY, accessKey);

properties.put(SECRET_KEY, secretKey);

properties.put(CLUSTER_NAME, clusterName);

properties.put(NAMING_LOAD_CACHE_AT_START, namingLoadCacheAtStart);

enrichNacosDiscoveryProperties(properties);

return properties;

}

private void enrichNacosDiscoveryProperties(Properties nacosDiscoveryProperties) {

Map properties = PropertySourcesUtils

.getSubProperties((ConfigurableEnvironment) environment, PREFIX);

properties.forEach((k, v) -> nacosDiscoveryProperties.putIfAbsent(resolveKey(k),

String.valueOf(v)));

}

private String resolveKey(String key) {

Matcher matcher = PATTERN.matcher(key);

StringBuffer sb = new StringBuffer();

while (matcher.find()) {

matcher.appendReplacement(sb, matcher.group(1).toUpperCase());

}

matcher.appendTail(sb);

return sb.toString();

}

}

可以看到,这个类实际上是配置文件中以spring.cloud.nacos.discovery开头的配置的映射类。

除此之外,还有一个init(...)方法,被@PostConstruct注释。当构造函数被调用之后,执行init初始化。初始化主要工作是对一些配置项的值进行数据转换处理等等。

3.4.2 NacosServiceRegistryAutoConfiguration

显然,这个NacosServiceRegistryAutoConfiguration类是为了使用Spring Boot自动装配功能的一个自动配置类。源码如下:

@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);

}

@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);

}

}

这个自动装配类首先使用了@EnableConfigurationProperties注解,这个注解能够使使用了@ConfigurationProperties注解但是没有使用@Component等注解实例化为bean的类生效。因为NacosDiscoveryProperties类使用了@ConfigurationProperties("spring.cloud.nacos.discovery")注解,但是没有@Component注解,Spring容器不会实例化这个类,所以需要通过@EnableConfigurationProperties注解让NacosDiscoveryProperties被实例化并注入到容器中。这样就能获取到配置文件中配置的Nacos相关的配置值。

然后使用了@ConditionalOnNacosDiscoveryEnabled注解。这个注解的意思是spring.cloud.nacos.discovery.enabled=true的时候(默认是true),NacosServiceRegistryAutoConfiguration这个配置类才会生效。

然后使用了@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)注解。这个注解的意思是当spring.cloud.service-registry.auto-registration.enabled=true的时候,NacosServiceRegistryAutoConfiguration这个配置类才会生效。默认情况下就是true。

然后使用了如下注解:

@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,

AutoServiceRegistrationAutoConfiguration.class,

NacosDiscoveryAutoConfiguration.class })

这个注解的意思是要在AutoServiceRegistrationConfiguration、AutoServiceRegistrationAutoConfiguration、NacosDiscoveryAutoConfiguration配置类生效后再使NacosServiceRegistryAutoConfiguration这个配置类生效。

查看NacosServiceRegistryAutoConfiguration配置类的内容,可以看出,这个配置类实例化了NacosServiceRegistry、NacosRegistration、NacosAutoServiceRegistration这三个bean。其中,NacosServiceRegistry负责服务注册和反注册,NacosRegistration负责维护服务实例的基本信息,NacosAutoServiceRegistration主要是实现服务的自动注册。

3.4.3 NamingService的实例化

从上面的源码分析可以看出,服务注册和反注册工作,最终都是交由NamingService来处理的。

NamingService是nacos-api包里面定义的接口,其实现类NacosNamingService也是nacos-api包中定义的类。

NamingService是对Nacos注册中心的封装,除了有Nacos注册中心的基本信息之外,还对服务注册相关的http请求进行了封装。

那么,NamingService的实例bean又是何时被创建的呢?

查看NacosServiceRegistry源码,其构造函数如下:

public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {

this.nacosDiscoveryProperties = nacosDiscoveryProperties;

// 实例化namingService

this.namingService = nacosDiscoveryProperties.namingServiceInstance();

}

也就是在实例化NacosServiceRegistry的时候,通过调用nacosDiscoveryProperties.namingServiceInstance()方法实例化了namingService。

继续跟踪源码:

public NamingService namingServiceInstance() {

if (null != namingService) {

return namingService;

}

try {

// 创建namingService

namingService = NacosFactory.createNamingService(getNacosProperties());

}

catch (Exception e) {

log.error("create naming service error!properties={},e=,", this, e);

return null;

}

return namingService;

}

如果namingService为null,则通过NacosFactory.createNamingService(getNacosProperties())创建它。其中,getNacosProperties()方法获取Nacos注册中心的基本配置信息。

继续跟踪源码:

public static NamingService createNamingService(Properties properties) throws NacosException {

return NamingFactory.createNamingService(properties);

}

调用NamingFactory.createNamingService(properties)创建namingService。

继续跟踪源码:

public static NamingService createNamingService(Properties properties) throws NacosException {

try {

Class> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");

Constructor constructor = driverImplClass.getConstructor(Properties.class);

// 通过反射机制创建NamingService的对象

NamingService vendorImpl = (NamingService)constructor.newInstance(properties);

return vendorImpl;

} catch (Throwable e) {

throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);

}

}

可以看出,最终是通过反射机制,创建了NamingService的实例对象,也就是NacosNamingService的对象。

继续跟踪源码,看看创建NacosNamingService对象的时候都干了什么?

public NacosNamingService(Properties properties) {

init(properties);

}

调用的是init方法:

private void init(Properties properties) {

// 初始化命名空间

namespace = InitUtils.initNamespaceForNaming(properties);

// 初始化Nacos注册中心服务地址

initServerAddr(properties);

// 初始化应用上下文

InitUtils.initWebRootContext();

// 初始化缓存目录

initCacheDir();

// 初始化日志文件名字

initLogName(properties);

// 初始化事件分发器

eventDispatcher = new EventDispatcher();

// 初始化服务代理

serverProxy = new NamingProxy(namespace, endpoint, serverList, properties);

// 初始化客户端心跳机制,会定时向服务端发送心跳信息

beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties));

// 初始化服务信息更新机制,会定时拉去客户端关心的服务信息

hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties),

initPollingThreadCount(properties));

}

3.4.5 NamingService#registerInstance方法

com.alibaba.nacos.client.naming.NacosNamingService#registerInstance(java.lang.String, java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance)

从上面的源码分析可以看出,服务注册最终调用的是NamingService#registerInstance的方法,那具体的实现就是NacosNamingService#registerInstance。

跟踪源码:

@Override

public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {

if (instance.isEphemeral()) {

BeatInfo beatInfo = new BeatInfo();

beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));

beatInfo.setIp(instance.getIp());

beatInfo.setPort(instance.getPort());

beatInfo.setCluster(instance.getClusterName());

beatInfo.setWeight(instance.getWeight());

beatInfo.setMetadata(instance.getMetadata());

beatInfo.setScheduled(false);

beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());

beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);

}

serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);

}

最终通过serverProxy.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 params = new HashMap(9);

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", JSON.toJSONString(instance.getMetadata()));

reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);

}

其中:

public static String WEB_CONTEXT = "/nacos";

public static String NACOS_URL_BASE = WEB_CONTEXT + "/v1/ns";

public static String NACOS_URL_INSTANCE = NACOS_URL_BASE + "/instance";

所以,会向/nacos/v1/ns/instance发送一个POST请求,将服务信息通过这个请求注册到Nacos上面。

知秋君
上一篇 2024-07-04 22:02
下一篇 2024-07-04 21:36

相关推荐