主要是通过namingService.getAllInstances来获取
@Ignore
public class NamingTest {
@Test
public void testServiceList() throws Exception {
......
//获取服务端所有服务实例,根据服务名称
List list = namingService.getAllInstances("nacos.test.1");
......
}
}
该方法提供了好几个重载的版本,主要是多了如下参数:
名 | 类型 | 解释 |
serviceName | String | 服务名 |
groupName | String | 分组名,默认为DEFAULT_GROUOP |
clusters | List |
集群列表,默认为空数组 |
subscribe | boolean | 是否为订阅,默认为true |
@Override
public List getAllInstances(String serviceName, String groupName, List clusters,
boolean subscribe) throws NacosException {
ServiceInfo serviceInfo;
String clusterString = StringUtils.join(clusters, ",");
// 是否是订阅模式
if (subscribe) {
// 先从客户端缓存获取服务信息
serviceInfo = serviceInfoHolder.getServiceInfo(serviceName, , clusterString);
if (null == serviceInfo) {
// 如果本地缓存不存在服务信息,则进行订阅
serviceInfo = clientProxy.subscribe(serviceName, groupName, clusterString);
}
} else {
// 如果不是订阅模式,则直接从服务器进行查询
serviceInfo = clientProxy.queryInstancesOfService(serviceName, groupName, clusterString, 0, false);
}
// 从服务信息中获取实例列表
List list;
if (serviceInfo == null || CollectionUtils.isEmpty(list = serviceInfo.getHosts())) {
return new ArrayList();
}
return list;
}
对于客户端来说,一般就是注册和订阅服务(订阅机制会自动同步服务器实例的变化到本地。如果本地缓存中没有,那说明是首次调用,则进行订阅,在订阅完成后会获得服务信息)。
非订阅相当于就是查询服务列表,所以我们着重的是订阅
@Override
public ServiceInfo subscribe(String serviceName, String groupName, String clusters) throws NacosException {
String serviceNameWithGroup = NamingUtils.getGroupedName(serviceName, groupName);
String serviceKey = ServiceInfo.getKey(serviceNameWithGroup, clusters);
// 定时调度UpdateTask,默认6秒, 最多每隔60秒更新一次,
serviceInfoUpdateService.scheduleUpdateIfAbsent(serviceName, groupName, clusters);
// 获取缓存中的ServiceInfo,这里在调用该方法之前判断了一次,所以是双重判断
ServiceInfo result = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
if (null == result) {
// 如果为null,则说明缓存还没有,则手动进行请求一次,基于gRPC协议
result = grpcClientProxy.subscribe(serviceName, groupName, clusters);
}
// ServiceInfo本地缓存处理
serviceInfoHolder.processServiceInfo(result);
return result;
}
@Override
public void run() {
long delayTime = DEFAULT_DELAY;
try {
// 判断服务是否订阅和未开启过定时任务,如果订阅过直接不在执行
if (!changeNotifier.isSubscribed(groupName, serviceName, clusters) && !futureMap.containsKey(serviceKey)) {
NAMING_LOGGER
.info("update task is stopped, service:{}, clusters:{}", groupedServiceName, clusters);
return;
}
// 获取缓存的service信息
ServiceInfo serviceObj = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
// 如果为空
if (serviceObj == null) {
// 根据serviceName从注册中心服务端获取Service信息
serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);
// 处理本地缓存,并发布InstancesChangeEvent
serviceInfoHolder.processServiceInfo(serviceObj);
lastRefTime = serviceObj.getLastRefTime();
return;
}
// 过期服务,服务的最新更新时间小于等于缓存刷新(最后一次拉取数据的时间)时间,从注册中心重新查询
if (serviceObj.getLastRefTime() <= lastRefTime) {
serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);
// 处理本地缓存,并发布InstancesChangeEvent
serviceInfoHolder.processServiceInfo(serviceObj);
}
//刷新更新时间
lastRefTime = serviceObj.getLastRefTime();
if (CollectionUtils.isEmpty(serviceObj.getHosts())) {
incFailCount();
return;
}
// 下次更新缓存时间设置,默认6秒
// TODO multiple time can be configured.
delayTime = serviceObj.getCacheMillis() * DEFAULT_UPDATE_CACHE_TIME_MULTIPLE;
// 重置失败数量为0(可能会出现失败情况,没有ServiceInfo,连接失败)
resetFailCount();
} catch (Throwable e) {
incFailCount();
NAMING_LOGGER.warn("[NA] failed to update serviceName: {}", groupedServiceName, e);
} finally {
// 下次调度刷新时间,下次执行的时间与failCount有关,failCount=0,则下次调度时间为6秒,最长为1分钟
// 即当无异常情况下缓存实例的刷新时间是6秒
executor.schedule(this, Math.min(delayTime << failCount, DEFAULT_DELAY * 60), TimeUnit.MILLISECONDS);
}
}
首先需要有订阅监听器, 监听是通过订阅是传入的。具体代码如下。
@Override
public void subscribe(String serviceName, String groupName, List clusters, EventListener listener)
throws NacosException {
......
//添加监听器
changeNotifier.registerListener(groupName, serviceName, clusterString, listener);
clientProxy.subscribe(serviceName, groupName, clusterString);
}
public void registerListener(String groupName, String serviceName, String clusters, EventListener listener) {
String key = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters);
ConcurrentHashSet eventListeners = listenerMap.get(key);
if (eventListeners == null) {
synchronized (lock) {
eventListeners = listenerMap.get(key);
if (eventListeners == null) {
eventListeners = new ConcurrentHashSet();
//将EventListener缓存到listenerMap
listenerMap.put(key, eventListeners);
}
}
}
eventListeners.add(listener);
}
通过以上代码将监听器装入了一个listenerMap中,这个map中存的是服务的监听器列表
触发InstancesChangeEvent事件是在更新服务时, 发现远程的服务和本地服务有变化时触发的,
public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) {
String serviceKey = serviceInfo.getKey();
if (serviceKey == null) {
return null;
}
ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey());
//拉取的服务信息不正常,则还是返回就的服务
if (isEmptyOrErrorPush(serviceInfo)) {
//empty or error push, just ignore
return oldService;
}
serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
//新的服务信息和就的服务信息对比,返回是否更改
boolean changed = isChangedServiceInfo(oldService, serviceInfo);
if (StringUtils.isBlank(serviceInfo.getJsonFromServer())) {
serviceInfo.setJsonFromServer(JacksonUtils.toJson(serviceInfo));
}
MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());
if (changed) {
NAMING_LOGGER.info("current ips:({}) service: {} -> {}", serviceInfo.ipCount(), serviceInfo.getKey(),
JacksonUtils.toJson(serviceInfo.getHosts()));
//如果变更就发布InstancesChangeEvent事件
NotifyCenter.publishEvent(new InstancesChangeEvent(serviceInfo.getName(), serviceInfo.getGroupName(),
serviceInfo.getClusters(), serviceInfo.getHosts()));
DiskCache.write(serviceInfo, cacheDir);
}
return serviceInfo;
}
public class NacosNamingService implements NamingService {
......
private void init(Properties properties) throws NacosException {
......
NotifyCenter.registerToPublisher(InstancesChangeEvent.class, 16384);
//添加关注者处理器, 会调用addSubscriber进行注册
NotifyCenter.registerSubscriber(changeNotifier);
......
}
......
}
NotifyCenter注册订阅者处理器
private static void addSubscriber(final Subscriber consumer, Class extends Event> subscribeType,EventPublisherFactory factory) {
final String topic = ClassUtils.getCanonicalName(subscribeType);
EventPublisher publisher = INSTANCE.publisherMap.get(topic);
publisher.addSubscriber(consumer);
}
public class NacosNamingService implements NamingService {
......
@Override
public void subscribe(String serviceName, String groupName, List clusters, EventListener listener) throws NacosException {
......
changeNotifier.registerListener(groupName, serviceName, clusterString, listener);
......
}
......
}
public void registerListener(String groupName, String serviceName, String clusters, EventListener listener) {
String key = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters);
ConcurrentHashSet eventListeners = listenerMap.get(key);
if (eventListeners == null) {
synchronized (lock) {
eventListeners = listenerMap.get(key);
if (eventListeners == null) {
eventListeners = new ConcurrentHashSet();
//将EventListener缓存到listenerMap
listenerMap.put(key, eventListeners);
}
}
}
eventListeners.add(listener);
}
public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) {
......
// 添加实例变更事件,会被订阅者执行
NotifyCenter.publishEvent(new InstancesChangeEvent(serviceInfo.getName(), serviceInfo.getGroupName(),
serviceInfo.getClusters(), serviceInfo.getHosts()));
......
}
private static boolean publishEvent(final Class extends Event> eventType, final Event event) {
......
// 根据InstancesChangeEvent事件类型,获得对应的CanonicalName
final String topic = ClassUtils.getCanonicalName(eventType);
// 将CanonicalName作为Key,从NotifyCenter#publisherMap中获取对应的事件发布者(EventPublisher)
EventPublisher publisher = INSTANCE.publisherMap.get(topic);
if (publisher != null) {
// 事件发布者publisher发布事件(InstancesChangeEvent)
return publisher.publish(event);
}
......
}
事件发布者发布事件者处理事件
这里的事件发布者通过追踪代码发现实际是由NamingEventPublisher处理的。通过代码可以看出,这里NamingEventPublisher 实际上是一个线程,而且会有一个queue来负责管理事件,当队列queue添加失败时直接由当前线程继续处理事件(退化成同步处理了)
public class NamingEventPublisher extends Thread implements ShardedEventPublisher {
......
public boolean publish(Event event) {
.....
boolean success = this.queue.offer(event);
if (!success) {
handleEvent(event);
return true;
}
return true;
}
......
}
NamingEventPublisher处理事件
它既然是一个线程,那么肯定有run方法,那么就应该去消费掉通过publish发布的事件
事件通过该线程后就会得到处理,关注者就能获得事件回调了。
public class NamingEventPublisher extends Thread implements ShardedEventPublisher {
......
@Override
public void run() {
//执行等待订阅者就绪
waitSubscriberForInit();
//处理收到的事件
handleEvents();
......
}
......
private void handleEvents() {
while (!shutdown) {
......
final Event event = queue.take();
handleEvent(event);
......
}
}
......
private void handleEvent(Event event) {
Class extends Event> eventType = event.getClass();
Set> subscribers = subscribes.get(eventType);
......
for (Subscriber subscriber : subscribers) {
notifySubscriber(subscriber, event);
}
}
......
//该方法会尝试使用关注者的执行线程池去处理任务,如果没有线程池就在当前线程处理
@Override
public void notifySubscriber(Subscriber subscriber, Event event) {
final Runnable job = () -> subscriber.onEvent(event);
final Executor executor = subscriber.executor();
if (executor != null) {
executor.execute(job);
} else {
job.run();
}
}
......
}