1、注册
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled",
matchIfMissing = true)
public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties);
}
2、NacosWatch
构造器
public NacosWatch(NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties properties) {
this.nacosServiceManager = nacosServiceManager;
this.properties = properties;
this.taskScheduler = getTaskScheduler();
}
构造器中创建了一个线程池
private final ThreadPoolTaskScheduler taskScheduler;
private static ThreadPoolTaskScheduler getTaskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setBeanName("Nacos-Watch-Task-Scheduler");
taskScheduler.initialize();
return taskScheduler;
}
Scheduler的名称是:Nacos-Watch-Task-Scheduler
3、启动
NacosWatch的继承关系
public class NacosWatch
implements ApplicationEventPublisherAware, SmartLifecycle, DisposableBean
NacosWatch实现了SmartLifecycle接口,实现了spring容器的生命周期的方法。
start方法
@Override
public void start() {
//private final AtomicBoolean running = new AtomicBoolean(false); 标识符
//这里用了cas方法,只会有一个线程获取到执行代码的权限
if (this.running.compareAndSet(false, true)) {
//生成一个事件监听,这里监听的是NamingEvent
//private Map<String, EventListener> listenerMap = new ConcurrentHashMap<>(16); 监听器缓存
//key的格式是 spring.application.name:group
EventListener eventListener = listenerMap.computeIfAbsent(buildKey(),
event -> new EventListener() {
@Override
public void onEvent(Event event) {
//监听器回调 判断是否NamingEvent 事件
if (event instanceof NamingEvent) {
//从事件中获取服务列表
List<Instance> instances = ((NamingEvent) event)
.getInstances();
//获取当前服务
Optional<Instance> instanceOptional = selectCurrentInstance(
instances);
//如果存在
instanceOptional.ifPresent(currentInstance -> {
//是否需要重设matadata
resetIfNeeded(currentInstance);
});
}
}
});
//获取NamingService
NamingService namingService = nacosServiceManager
.getNamingService(properties.getNacosProperties());
try {
//订阅
namingService.subscribe(properties.getService(), properties.getGroup(),
Arrays.asList(properties.getClusterName()), eventListener);
}
catch (Exception e) {
log.error("namingService subscribe failed, properties:{}", properties, e);
}
//启动定时器,默认30秒执行一次
this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(
this::nacosServicesWatch, this.properties.getWatchDelay());
}
}
重设Metadata
private void resetIfNeeded(Instance instance) {
if (!properties.getMetadata().equals(instance.getMetadata())) {
properties.setMetadata(instance.getMetadata());
}
}
发布HeartbeatEvent
public void nacosServicesWatch() {
//由上面的定时器定时发布事件 默认30秒
// nacos doesn't support watch now , publish an event every 30 seconds.
this.publisher.publishEvent(
new HeartbeatEvent(this, nacosWatchIndex.getAndIncrement()));
}
4、NamingService的订阅
入参是上面构建的EventListener,即NamingEvent
//com.alibaba.nacos.client.naming.NacosNamingService#subscribe
public void subscribe(String serviceName, String groupName, List<String> clusters, EventListener listener)
throws NacosException {
if (null == listener) {
return;
}
//获取clusterString,将所有cluster按,拼接
String clusterString = StringUtils.join(clusters, ",");
//注册listener
//private InstancesChangeNotifier changeNotifier; 在init方法中构造
changeNotifier.registerListener(groupName, serviceName, clusterString, listener);
//订阅
//private NamingClientProxy clientProxy;
//NamingClientProxyDelegate 在init方法中构造
clientProxy.subscribe(serviceName, groupName, clusterString);
}
4.1、InstancesChangeNotifier注册listener
//com.alibaba.nacos.client.naming.event.InstancesChangeNotifier#registerListener
public void registerListener(String groupName, String serviceName, String clusters, EventListener listener) {
//这样的格式 group@@service@@clusters
String key = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters);
//从缓存中获取
//private final Map<String, ConcurrentHashSet<EventListener>> listenerMap = new ConcurrentHashMap<String, ConcurrentHashSet<EventListener>>();
ConcurrentHashSet<EventListener> eventListeners = listenerMap.get(key);
//双重检查锁,将listener放入缓存
if (eventListeners == null) {
synchronized (lock) {
eventListeners = listenerMap.get(key);
if (eventListeners == null) {
eventListeners = new ConcurrentHashSet<EventListener>();
listenerMap.put(key, eventListeners);
}
}
}
eventListeners.add(listener);
}
4.2、NamingClientProxyDelegate 的订阅
//com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate#subscribe
public ServiceInfo subscribe(String serviceName, String groupName, String clusters) throws NacosException {
//group@@service
String serviceNameWithGroup = NamingUtils.getGroupedName(serviceName, groupName);
//group@@service@@clusters
String serviceKey = ServiceInfo.getKey(serviceNameWithGroup, clusters);
//服务信息修改
serviceInfoUpdateService.scheduleUpdateIfAbsent(serviceName, groupName, clusters);
//判断ServiceInfo中的serviceInfoMap有没有
ServiceInfo result = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
//没有,grpc的订阅ServiceInfo,并返回
if (null == result) {
result = grpcClientProxy.subscribe(serviceName, groupName, clusters);
}
//处理服务
serviceInfoHolder.processServiceInfo(result);
return result;
}
4.2.1、服务信息修改
//com.alibaba.nacos.client.naming.core.ServiceInfoUpdateService#scheduleUpdateIfAbsent
public void scheduleUpdateIfAbsent(String serviceName, String groupName, String clusters) {
//group@@service@@clusters
String serviceKey = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters);
//缓存,如果有,代表已经添加了task,不用重复添加
//private final Map<String, ScheduledFuture<?>> futureMap = new HashMap<String, ScheduledFuture<?>>();
if (futureMap.get(serviceKey) != null) {
return;
}
//加锁
synchronized (futureMap) {
if (futureMap.get(serviceKey) != null) {
return;
}
//添加定时任务 UpdateTask
ScheduledFuture<?> future = addTask(new UpdateTask(serviceName, groupName, clusters));
//放入缓存
futureMap.put(serviceKey, future);
}
}
添加UpdateTask
//com.alibaba.nacos.client.naming.core.ServiceInfoUpdateService#addTask
//private final ScheduledExecutorService executor;
//名称为:com.alibaba.nacos.client.naming.updater
//延迟一秒
private synchronized ScheduledFuture<?> addTask(UpdateTask task) {
return executor.schedule(task, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
}
public ServiceInfoUpdateService(Properties properties, ServiceInfoHolder serviceInfoHolder,
NamingClientProxy namingClientProxy, InstancesChangeNotifier changeNotifier) {
this.executor = new ScheduledThreadPoolExecutor(initPollingThreadCount(properties),
new NameThreadFactory("com.alibaba.nacos.client.naming.updater"));
this.serviceInfoHolder = serviceInfoHolder;
this.namingClientProxy = namingClientProxy;
this.changeNotifier = changeNotifier;
}
4.2.2、grpc的 订阅
//com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy#subscribe
public ServiceInfo subscribe(String serviceName, String groupName, String clusters) throws NacosException {
//redo中放入ServiceInfo redo标志为false
redoService.cacheSubscriberForRedo(serviceName, groupName, clusters);
//返回订阅结果
return doSubscribe(serviceName, groupName, clusters);
}
//com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy#doSubscribe
public ServiceInfo doSubscribe(String serviceName, String groupName, String clusters) throws NacosException {
//构建SubscribeServiceRequest
SubscribeServiceRequest request = new SubscribeServiceRequest(namespaceId, groupName, serviceName, clusters,
true);
//获取请求结果
SubscribeServiceResponse response = requestToServer(request, SubscribeServiceResponse.class);
//将上面的redo的标志设置为true
redoService.subscriberRegistered(serviceName, groupName, clusters);
//返回订阅结果
return response.getServiceInfo();
}
4.2.3、处理服务
//com.alibaba.nacos.client.naming.cache.ServiceInfoHolder#processServiceInfo
public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) {
String serviceKey = serviceInfo.getKey();
if (serviceKey == null) {
return null;
}
//获取当前保存的服务信息
ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey());
//订阅获得的serviceInfo为空,或者获取到的健康的实例数量是空,直接返回
if (isEmptyOrErrorPush(serviceInfo)) {
//empty or error push, just ignore
return oldService;
}
//将新的服务信息覆盖旧的服务信息
serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
//是否服务有变更
boolean changed = isChangedServiceInfo(oldService, serviceInfo);
//如果当前保存的JsonFromServer为空,直接保存为新的实例的JsonFromServer
if (StringUtils.isBlank(serviceInfo.getJsonFromServer())) {
serviceInfo.setJsonFromServer(JacksonUtils.toJson(serviceInfo));
}
//监控信息
MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());
//服务有变更 发布InstancesChangeEvent 事件,并且刷新磁盘缓存
if (changed) {
NAMING_LOGGER.info("current ips:(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() + " -> "
+ JacksonUtils.toJson(serviceInfo.getHosts()));
NotifyCenter.publishEvent(new InstancesChangeEvent(serviceInfo.getName(), serviceInfo.getGroupName(),
serviceInfo.getClusters(), serviceInfo.getHosts()));
DiskCache.write(serviceInfo, cacheDir);
}
return serviceInfo;
}
判断服务信息是否有变更
private boolean isChangedServiceInfo(ServiceInfo oldService, ServiceInfo newService) {
//当前服务信息为空,直接返回true,因为上面方法中有判断新获取的服务信息是否为空
if (null == oldService) {
NAMING_LOGGER.info("init new ips(" + newService.ipCount() + ") service: " + newService.getKey() + " -> "
+ JacksonUtils.toJson(newService.getHosts()));
return true;
}
if (oldService.getLastRefTime() > newService.getLastRefTime()) {
NAMING_LOGGER
.warn("out of date data received, old-t: " + oldService.getLastRefTime() + ", new-t: " + newService
.getLastRefTime());
}
boolean changed = false;
//将当前的服务信息组装成map
Map<String, Instance> oldHostMap = new HashMap<String, Instance>(oldService.getHosts().size());
for (Instance host : oldService.getHosts()) {
oldHostMap.put(host.toInetAddr(), host);
}
//将新获得的服务信息组装成map
Map<String, Instance> newHostMap = new HashMap<String, Instance>(newService.getHosts().size());
for (Instance host : newService.getHosts()) {
newHostMap.put(host.toInetAddr(), host);
}
//声明 修改的实例,新的服务实例,移除的服务实例
Set<Instance> modHosts = new HashSet<Instance>();
Set<Instance> newHosts = new HashSet<Instance>();
Set<Instance> remvHosts = new HashSet<Instance>();
//将新获得的服务实例信息做成一个entry list
List<Map.Entry<String, Instance>> newServiceHosts = new ArrayList<Map.Entry<String, Instance>>(
newHostMap.entrySet());
//遍历
for (Map.Entry<String, Instance> entry : newServiceHosts) {
Instance host = entry.getValue();
String key = entry.getKey();
//Instance 的toString方法是重写过的,如果当前服务实例和新获得的服务实例不一样,
//将该服务实例信息放入 modHosts
if (oldHostMap.containsKey(key) && !StringUtils.equals(host.toString(), oldHostMap.get(key).toString())) {
modHosts.add(host);
continue;
}
//如果当前保存的服务实例信息中没有新获得的服务实例,将新获得的服务实例放入 newHosts
if (!oldHostMap.containsKey(key)) {
newHosts.add(host);
}
}
//遍历当前保存的服务实例信息map
for (Map.Entry<String, Instance> entry : oldHostMap.entrySet()) {
Instance host = entry.getValue();
String key = entry.getKey();
//如果新的服务实例map中没有当前保存的服务实例信息map中的实例,将其放入 remvHosts
if (newHostMap.containsKey(key)) {
continue;
}
if (!newHostMap.containsKey(key)) {
remvHosts.add(host);
}
}
//如果 newHosts 中有元素,代表服务实例发生了变更
if (newHosts.size() > 0) {
changed = true;
NAMING_LOGGER
.info("new ips(" + newHosts.size() + ") service: " + newService.getKey() + " -> " + JacksonUtils
.toJson(newHosts));
}
//如果 remvHosts 中有元素,代表服务实例发生了变更
if (remvHosts.size() > 0) {
changed = true;
NAMING_LOGGER.info("removed ips(" + remvHosts.size() + ") service: " + newService.getKey() + " -> "
+ JacksonUtils.toJson(remvHosts));
}
//如果 modHosts 中有元素,代表服务实例发生了变更
if (modHosts.size() > 0) {
changed = true;
NAMING_LOGGER.info("modified ips(" + modHosts.size() + ") service: " + newService.getKey() + " -> "
+ JacksonUtils.toJson(modHosts));
}
return changed;
}
5、UpdateTask
上面 4.2.1 讲到添加了一个UpdateTask 到 ScheduledFuture,然后延迟一秒钟。下面看看这个task做了什么。
UpdateTask 是ServiceInfoUpdateService的内部类,实现了Runnable接口,是一个线程。
public class UpdateTask implements Runnable {
long lastRefTime = Long.MAX_VALUE;
private final String serviceName;
private final String groupName;
private final String clusters;
private final String groupedServiceName;
private final String serviceKey;
//1:连不上服务端,2:获取到的服务实例是空
private int failCount = 0;
//构造器,初始化参数
public UpdateTask(String serviceName, String groupName, String clusters) {
this.serviceName = serviceName;
this.groupName = groupName;
this.clusters = clusters;
this.groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
this.serviceKey = ServiceInfo.getKey(groupedServiceName, clusters);
}
@Override
public void run() {
//保存一个延迟时间
long delayTime = DEFAULT_DELAY;
try {
//当前没有订阅 并且 没有执行过updateTask
if (!changeNotifier.isSubscribed(groupName, serviceName, clusters) && !futureMap.containsKey(serviceKey)) {
NAMING_LOGGER
.info("update task is stopped, service:" + groupedServiceName + ", clusters:" + clusters);
return;
}
//获取当前保存的服务实例信息
ServiceInfo serviceObj = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
//如果没有服务实例信息
if (serviceObj == null) {
//从服务端查询实例信息
serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);
//处理服务信息
serviceInfoHolder.processServiceInfo(serviceObj);
//保存lastRefTime
lastRefTime = serviceObj.getLastRefTime();
return;
}
//如果当前保存的实例信息的 LastRefTime <= UpdateTask 缓存的 lastRefTime
//说明应该从新拉取
if (serviceObj.getLastRefTime() <= lastRefTime) {
serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);
serviceInfoHolder.processServiceInfo(serviceObj);
}
//保存最新的 lastRefTime
lastRefTime = serviceObj.getLastRefTime();
//如果当前获取到的 增加失败次数 最大6次
if (CollectionUtils.isEmpty(serviceObj.getHosts())) {
incFailCount();
return;
}
//拉取成功,重设 delayTime delayTime * 6,重设失败次数
// TODO multiple time can be configured.
delayTime = serviceObj.getCacheMillis() * DEFAULT_UPDATE_CACHE_TIME_MULTIPLE;
resetFailCount();
} catch (Throwable e) {
incFailCount();
NAMING_LOGGER.warn("[NA] failed to update serviceName: " + groupedServiceName, e);
} finally {
//重设延迟时间,delayTime 左移 失败次数位 与 60秒的较小值
//如果拉取成功,delayTime 是 6s,failCount 是0
//如果拉取失败,delayTime 是 1s,failCount 是失败次数,相当于做了一个衰减重试
//当失败次数达到6次的时候,左移结果是64s,这时会选择60秒作为重试的延迟时间
executor.schedule(this, Math.min(delayTime << failCount, DEFAULT_DELAY * 60), TimeUnit.MILLISECONDS);
}
}
private void incFailCount() {
int limit = 6;
if (failCount == limit) {
return;
}
failCount++;
}
private void resetFailCount() {
failCount = 0;
}
}
//com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate#queryInstancesOfService
public ServiceInfo queryInstancesOfService(String serviceName, String groupName, String clusters, int udpPort,
boolean healthyOnly) throws NacosException {
return grpcClientProxy.queryInstancesOfService(serviceName, groupName, clusters, udpPort, healthyOnly);
}
//com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy#queryInstancesOfService
public ServiceInfo queryInstancesOfService(String serviceName, String groupName, String clusters, int udpPort,
boolean healthyOnly) throws NacosException {
ServiceQueryRequest request = new ServiceQueryRequest(namespaceId, serviceName, groupName);
request.setCluster(clusters);
request.setHealthyOnly(healthyOnly);
request.setUdpPort(udpPort);
//请求服务端获取实例信息
QueryServiceResponse response = requestToServer(request, QueryServiceResponse.class);
return response.getServiceInfo();
}
5 和 4.2 中 都有serviceInfoHolder.processServiceInfo(serviceObj),感觉只是为了防漏。
6、InstancesChangeEvent的监听
//com.alibaba.nacos.client.naming.event.InstancesChangeNotifier#onEvent
public void onEvent(InstancesChangeEvent event) {
String key = ServiceInfo
.getKey(NamingUtils.getGroupedName(event.getServiceName(), event.getGroupName()), event.getClusters());
ConcurrentHashSet<EventListener> eventListeners = listenerMap.get(key);
if (CollectionUtils.isEmpty(eventListeners)) {
return;
}
for (final EventListener listener : eventListeners) {
final com.alibaba.nacos.api.naming.listener.Event namingEvent = transferToNamingEvent(event);
if (listener instanceof AbstractEventListener && ((AbstractEventListener) listener).getExecutor() != null) {
((AbstractEventListener) listener).getExecutor().execute(() -> listener.onEvent(namingEvent));
} else {
listener.onEvent(namingEvent);
}
}
}
private com.alibaba.nacos.api.naming.listener.Event transferToNamingEvent(
InstancesChangeEvent instancesChangeEvent) {
return new NamingEvent(instancesChangeEvent.getServiceName(), instancesChangeEvent.getGroupName(),
instancesChangeEvent.getClusters(), instancesChangeEvent.getHosts());
}
将InstancesChangeEvent 事件包装成 NamingEvent,NacosWatch中start方法中包装的NamingEvent的监听就会收到事件。从而更改NacosWatch#properties中Metadata的值。
7、HeartbeatEvent
该事件会在gateway中监听。
org.springframework.cloud.gateway.route.RouteRefreshListener#onApplicationEvent
cloud.dubbo中也会监听。
com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration#onHeartbeatEvent