首次搭建一个使用nacos的项目:
- https://cloud.tencent.com/developer/article/1881617
自动装配
spring.factories
代码语言:javascript复制org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration,
com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,
com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration,
com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration,
com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration,
com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration,
com.alibaba.cloud.nacos.NacosServiceAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=
com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfigurationnacos简单结构
nacos-client端底层的核心类是HostReactor

服务注册与发现
条件注解
代码语言:javascript复制@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.enabled",matchIfMissing = true)
public @interface ConditionalOnNacosDiscoveryEnabled {
}NacosDiscoveryProperties
启用服务发现时会创建该类,这个类是包含了以spring.cloud.nacos.discovery开头的nacos配置信息
NacosServiceDiscovery
spring cloud中定义服务发现的类是DiscoveryClient , 在nacos中实现类为NacosDiscoveryClient; 而NacosDiscoveryClient是NacosServiceDiscovery的包装类, 通过NacosServiceManager来进行实际的操作
NacosServiceRegistry
代码语言:javascript复制public class NacosServiceRegistry implements ServiceRegistry<Registration> {
private final NacosDiscoveryProperties nacosDiscoveryProperties;
@Autowired
private NacosServiceManager nacosServiceManager;
//客户端往nacos注册中心进行服务注册
@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 {
//通过beanReactor创建心跳检测任务(有一定配置,默认是true,创建临时节点,定时查询nacos,如果nacos上没找到就重新注册上去)
//通过serverProxy往nacos客户端注册服务实例
namingService.registerInstance(serviceId, group, instance);
}
catch (Exception e) {
rethrowRuntimeException(e);
}
}
}NacosServiceManager
nacos服务管理,逻辑全部委派给NamingService或NamingMaintainService来操作
NamingMaintainService
这个类应该是与NacosNamingService有相反的作用,
NacosNamingService是主动获取或者被动接受nacos的消息,但是不能操作,相当于crud的r
该类是可以操作nacos,拥有了crud的cud功能
NacosNamingService
NacosServiceManager管理的NamingService的实现类,一个NacosServiceManager持有的NacosNamingService是一个单例对象,创建方式是典型的懒汉模式
代码语言:javascript复制 private NamingService buildNamingService(Properties properties) {
if (Objects.isNull(namingService)) {
synchronized (NacosServiceManager.class) {
if (Objects.isNull(namingService)) {
namingService = createNewNamingService(properties);
}
}
}
return namingService;
}通过NamingProxy类对服务进行管理
通过BeatReactor类做心跳检测
通过HostReactor类做事件处理
NamingProxy
命名代理, 主要作用是与nacos之间进行交互
对于nacos需要账号密码的开启定时任务;定时刷新登录nacos服务端的token
BeatReactor
当前服务进行注册并且节点是临时节点(默认注册的就是临时节点)时, 会创建心跳检测任务,NacosServiceRegistry中有注释进行解释
任务的key=groupname 服务名称 ip port; 如 : DEFAULT_GROUP@@nacos-consumer#172.23.215.241#8090
HostReactor
如果nacos的服务注册与发现有内核,这个类可能就是内核吧
代码语言:javascript复制public class HostReactor implements Closeable {
private static final long DEFAULT_DELAY = 1000L;
private static final long UPDATE_HOLD_INTERVAL = 5000L;
private final Map<String, ScheduledFuture<?>> futureMap = new HashMap<String, ScheduledFuture<?>>();
//服务的内存缓存
private final Map<String, ServiceInfo> serviceInfoMap;
//临时记录节点变更
private final Map<String, Object> updatingMap;
//本地启用一个udp端口,主动接受服务端的变更通知
private final PushReceiver pushReceiver;
//心跳检测 - 在这个类中创建心跳检测任务
private final BeatReactor beatReactor;
//nacos交互代理
private final NamingProxy serverProxy;
//failover-mode 模式下,使用本地文件代替内存缓存来返回服务信息
private final FailoverReactor failoverReactor;
//本地文件缓存的地址,默认 user.homenacosnamingnamespace
private final String cacheDir;
private final boolean pushEmptyProtection;
private final ScheduledExecutorService executor;
//实例变更事件的订阅者
private final InstancesChangeNotifier notifier;
public HostReactor(NamingProxy serverProxy, BeatReactor beatReactor, String cacheDir, boolean loadCacheAtStart,
boolean pushEmptyProtection, int pollingThreadCount) {
// init executorService
this.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.beatReactor = beatReactor;
this.serverProxy = serverProxy;
this.cacheDir = cacheDir;
if (loadCacheAtStart) {
this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(DiskCache.read(this.cacheDir));
} else {
this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(16);
}
this.pushEmptyProtection = pushEmptyProtection;
this.updatingMap = new ConcurrentHashMap<String, Object>();
this.failoverReactor = new FailoverReactor(this, cacheDir);
this.pushReceiver = new PushReceiver(this);
this.notifier = new InstancesChangeNotifier();
//注册一个InstancesChangeEvent事件的发布器,将notifier作为订阅者
NotifyCenter.registerToPublisher(InstancesChangeEvent.class, 16384);
NotifyCenter.registerSubscriber(notifier);
}
}本地文件缓存:
类似于zookeeper的节点信息, 节点示例:
代码语言:javascript复制{"name":"DEFAULT_GROUP@@nacos-consumer","clusters":"DEFAULT","cacheMillis":10000,"hosts":[{"instanceId":"172.23.215.241#8090#DEFAULT#DEFAULT_GROUP@@nacos-consumer","ip":"172.23.215.241","port":8090,"weight":1.0,"healthy":true,"enabled":true,"ephemeral":true,"clusterName":"DEFAULT","serviceName":"DEFAULT_GROUP@@nacos-consumer","metadata":{"preserved.register.source":"SPRING_CLOUD"},"instanceHeartBeatInterval":5000,"instanceHeartBeatTimeOut":15000,"ipDeleteTimeout":30000}],"lastRefTime":1632383613389,"checksum":"","allIPs":false,"reachProtectionThreshold":false,"valid":true}HostReactor#getServiceInfo
这个方法在看代码的时候有一些疑惑的地方,在debug后整理如下:
- serviceInfoMap初始化是loadCacheAtStart默认是false, 初始化后是一个空的map对象
- 反证一下: 如果初始化时就缓存所有服务缓存,但是这个服务只需要连接某一个或几个服务就够了, 全部缓存确实也不合理
- 在需要使用某个服务时,如果serviceMap中不存在
- 如果updatingMap中存在,就进行等待, 这样子下面的操作就不是每一个线程都执行,而是只有一个线程执行
- 这个服务会放到updatingMap中
- 立即执行服务的刷新操作
- 通过serverProxy获取该服务的数据
- 与本地进行对比,如果发生变更就发送实例变更事件InstancesChangeEvent, 并且将拉到的数据缓存到本地cacheDir目录下
- 通知第一步中等待的线程可以执行了
- 从updatingMap中移出
- computerIfAbsent(是否有定时主动拉取远端配置的定时任务) 和变更事件一样; 对于首次使用的服务会创建一个定时任务主动拉取最新配置
- 返回服务信息


