前言
本篇就开始探索Nacos的服务注册逻辑,建议大家准备两个项目,一个是Nacos的源码项目,一个是整合Nacos注册中心的业务项目。探索过程也是分为客户端和服务端的代码解析,我觉得这样应该更利于代码跟进和理解,篇幅过长,还请耐心观看。
主要类图
客户端逻辑
首先来看客户端,也就是我们的项目在启动时,Nacos依赖偷偷干了哪些见不得人的事情。打开项目依赖,找到引入的注册中心依赖,我们知道SpringBoot项目启动时会读取spring.factories文件进行自动装配
你们猜一猜哪个是首先要看的类,我猜是NacosDiscoveryAutoConfiguration
这个类,你问我为什么,因为它写在第一位。你觉得不是?那我只能说是男人的直觉,源码看多掉头发的男人的直觉,没有毛病~
点开这个类,反手就是注入三个Bean,分别是NacosServiceRegistry
,NacosRegistration
和NacosAutoServiceRegistration
@Configuration
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter({
AutoServiceRegistrationConfiguration.class,
AutoServiceRegistrationAutoConfiguration.class })
public class NacosDiscoveryAutoConfiguration {
@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);
}
}
NacosDiscoveryProperties
这个类就是提供Nacos各种参数配置的类,很多集成框架都提供了这么一个类,比如RedisProperties
,只需要在配置参数类上加上@ConfigurationProperties注解,然后在配置类或者启动类上加上@EnableConfigurationProperties注解,就可以将配置参数类注入到容器中,这种高逼格的做法大家学废了吗。
NacosRegistration
这个配置类的功能主要就是处理我们配置的metadata元数据,既然支持灵活配置,那就肯定需要代码来支持。
NacosServiceRegistry
这个配置类就比较重要了,它里面包含了一个NamingService
命名服务类:
public class NacosServiceRegistry implements ServiceRegistry<Registration> {
private final NacosDiscoveryProperties nacosDiscoveryProperties;
private final NamingService namingService;
public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.namingService = nacosDiscoveryProperties.namingServiceInstance();
}
......
}
实例化的时候调用了NacosDiscoveryProperties
的**namingServiceInstance()**方法
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;
}
跟进去最后调用了NamingFactory
工厂的**createNamingService()**静态方法
public class NamingFactory {
public static NamingService createNamingService(String serverList) throws NacosException {
try {
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
Constructor constructor = driverImplClass.getConstructor(String.class);
NamingService vendorImpl = (NamingService)constructor.newInstance(serverList);
return vendorImpl;
} catch (Throwable e) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
}
}
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 vendorImpl = (NamingService)constructor.newInstance(properties);
return vendorImpl;
} catch (Throwable e) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
}
}
}
可以看到通过获取带有Properties
参数的构造器反射实例化NacosNamingService
对象,实例化时就会调用带有Properties
参数的构造方法
public class NacosNamingService implements NamingService {
/**
* 命名空间【public、prod、dev】
*
* Each Naming service should have different namespace.
*/
private String namespace;
private String endpoint;
/**
* Nacos 服务地址【127.0.0.1:8848,127.0.0.1:8848】
**/
private String serverList;
/**
* 本地注册表缓存存放目录【/Users/xxx/nacos/naming/public】
**/
private String cacheDir;
/**
* 日志文件名【naming.log】
**/
private String logName;
/**
* 本机响应器
**/
private HostReactor hostReactor;
/**
* 心跳响应器
**/
private BeatReactor beatReactor;
/**
* 事件调度器
**/
private EventDispatcher eventDispatcher;
/**
* 命名代理
**/
private NamingProxy serverProxy;
public NacosNamingService(String serverList) {
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR, serverList);
init(properties);
}
public NacosNamingService(Properties properties) {
init(properties);
}
private void init(Properties properties) {
ValidatorUtils.checkInitParam(properties);
// 初始化命名空间
namespace = InitUtils.initNamespaceForNaming(properties);
// 初始化服务地址
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,
// 启动时就加载缓存【False】
isLoadCacheAtStart(properties),
// 初始化线程池的核心线程数
initPollingThreadCount(properties)
);
}
......
}
**init()**方法进行一系列的初始化工作,BeatReactor
用来处理心跳上报,HostReactor
处理故障转移和接收服务端推送的服务列表数据。看一下实例化HostReactor
时都做了哪些事情
public class HostReactor {
/**
* 从服务器拉取最新数据延时时间
**/
private static final long DEFAULT_DELAY = 1000L;
/**
* 等待更新完成时间
**/
private static final long UPDATE_HOLD_INTERVAL = 5000L;
private final Map<String, ScheduledFuture<?>> futureMap = new HashMap<>();
/**
* 本地的服务注册表
**/
private Map<String, ServiceInfo> serviceInfoMap;
/**
* 需要更新的集合
**/
private Map<String, Object> updatingMap;
/**
* 推送接收器
**/
private PushReceiver pushReceiver;
/**
* 事件调度程序
**/
private EventDispatcher eventDispatcher;
/**
* 命名代理
**/
private NamingProxy serverProxy;
/**
* 故障转移响应器
**/
private FailoverReactor failoverReactor;
private String cacheDir;
private ScheduledExecutorService executor;
public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, String cacheDir) {
this(eventDispatcher, serverProxy, cacheDir, false, UtilAndComs.DEFAULT_POLLING_THREAD_COUNT);
}
public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, String cacheDir,
boolean loadCacheAtStart, int pollingThreadCount) {
executor = new ScheduledThreadPoolExecutor(pollingThreadCount, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.client.naming.updater");
return thread;
}
});
this.eventDispatcher = eventDispatcher;
this.serverProxy = serverProxy;
this.cacheDir = cacheDir;
if (loadCacheAtStart) {
this.serviceInfoMap = new ConcurrentHashMap<>(DiskCache.read(this.cacheDir));
}
else {
this.serviceInfoMap = new ConcurrentHashMap<>(16);
}
this.updatingMap = new ConcurrentHashMap<>();
// 实例化故障转移响应器
this.failoverReactor = new FailoverReactor(this, cacheDir);
/**
* 实例化推送接收器【重要】
**/
this.pushReceiver = new PushReceiver(this);
}
}
FailoverReactor
用来处理故障转移,PushReceiver
用来和服务端保持通信,接收推送的服务列表数据
public class PushReceiver implements Runnable {
private ScheduledExecutorService executorService;
private static final int UDP_MSS = 64 * 1024;
private DatagramSocket udpSocket;
private HostReactor hostReactor;
public PushReceiver(HostReactor hostReactor) {
try {
this.hostReactor = hostReactor;
// 初始化套接字
udpSocket = new DatagramSocket();
// 初始化执行器
executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.naming.push.receiver");
return thread;
}
}