文章系列
【一、Nacos-配置中心原理解析】
【二、Nacos-配置中心原理解析】
【三、Nacos注册中心集群数据一致性问题】
服务注册与发现是微服务架构得以运转的核心功能,它不提供任何业务功能,仅仅用来进行服务的发现和注册,并对服务的健康状态进行监控和管理。
其核心的工作原理:
Nacos为服务注册与发现提供了一个SDK类 NamingService,通过该类,可以实现服务的注册与发现、订阅服务的动态变化、获取服务实例、获取注册中心的健康状态等等。
其中,NamingService中的接口,可以分为以下几类:
创建 NamingService:
NamingService namingService = NacosFactory.createNamingService(properties);
获取 NacosNamingService 中带Properties形参的构造函数,然后反射创建。
public class NamingFactory {
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);
}
}
}
通过构造函数,创建一个NacosNamingService 对象,代码如下:
public class NacosNamingService implements NamingService {
public NacosNamingService(Properties properties) {
init(properties);
}
private void init(Properties properties) {
// 初始化namespace
namespace = InitUtils.initNamespaceForNaming(properties);
// 初始化服务端地址列表
initServerAddr(properties);
// 初始化web root上下文
InitUtils.initWebRootContext();
// 初始化缓存目录:用于故障转移,user.home + /nacos/naming/ + namespace
initCacheDir();
initLogName(properties);
// 事件调度程序:while(true)线程,如果服务列表发生变更,则发送NamingEvent到当前服务所有listener
eventDispatcher = new EventDispatcher();
// 创建服务代理类:用于服务实例注册/注销、心跳发送、服务实例获取、服务实例更新/删除/创建等
// 所有向服务器发送、获取的动作,都在 NamingProxy 中完成。
serverProxy = new NamingProxy(namespace, endpoint, serverList);
serverProxy.setProperties(properties);
// 用于心跳检测:如果向Nacos注册一个临时节点,需要创建一个心跳检测任务
beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties));
// 用于获取服务端注册信息,故障转移,定时刷盘备份等
hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties), initPollingThreadCount(properties));
}
}
创建服务代理类:用于服务实例注册/注销、心跳发送、服务实例获取、服务实例更新/删除/创建等。
所有向服务器发送、获取的动作,都在 NamingProxy 中完成。
public class NamingProxy {
public NamingProxy(String namespaceId, String endpoint, String serverList) {
this.namespaceId = namespaceId;
this.endpoint = endpoint;
if (StringUtils.isNotEmpty(serverList)) {
this.serverList = Arrays.asList(serverList.split(","));
if (this.serverList.size() == 1) {
this.nacosDomain = serverList;
}
}
// 初始化端点服务列表,并且每隔30秒刷新一次服务列表
initRefreshSrvIfNeed();
}
// 初始化端点服务列表
private void initRefreshSrvIfNeed() {
// 端点为空,直接返回
if (StringUtils.isEmpty(endpoint)) {
return;
}
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.naming.serverlist.updater");
t.setDaemon(true);
return t;
}
});
// 延时任务:刷新端点服务列表,vipSrvRefInterMillis = 30s
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
// 刷新服务列表
refreshSrvIfNeed();
}
}, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS);
// 刷新端点服务列表
refreshSrvIfNeed();
}
// 刷新端点服务列表
private void refreshSrvIfNeed() {
try {
if (!CollectionUtils.isEmpty(serverList)) {
NAMING_LOGGER.debug("server list provided by user: " + serverList);
return;
}
if (System.currentTimeMillis() - lastSrvRefTime < vipSrvRefInterMillis) {
return;
}
// 从端点获取服务器列表
List<String> list = getServerListFromEndpoint();
if (CollectionUtils.isEmpty(list)) {
throw new Exception("Can not acquire Nacos list");
}
if (!CollectionUtils.isEqualCollection(list, serversFromEndpoint)) {
NAMING_LOGGER.info("[SERVER-LIST] server list is updated: " + list);
}
serversFromEndpoint = list;
lastSrvRefTime = System.currentTimeMillis();
} catch (Throwable e) {
NAMING_LOGGER.warn("failed to update server list", e);
}
}
// 从端点获取服务器列表 http://endpoint/nacos/serverlist
public List<String> getServerListFromEndpoint() {
try {
String urlString = "http://" + endpoint + "/nacos/serverlist";
List<String> headers = builderHeaders();
HttpClient.HttpResult result = HttpClient.httpGet(urlString, headers, null, UtilAndComs.ENCODING);
if (HttpURLConnection.HTTP_OK != result.code) {
throw new IOException("Error while requesting: " + urlString + "'. Server returned: "
+ result.code);
}
String content = result.content;
List<String> list = new ArrayList<String>();
for (String line : IoUtils.readLines(new StringReader(content))) {
if (!line.trim().isEmpty()) {
list.add(line.trim());
}
}
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
BeatReactor 主要是用来发送心跳包,服务端根据心跳时间,来判断当前服务上线、下线。
public class BeatReactor {
public BeatReactor(NamingProxy serverProxy, int threadCount) {
this.serverProxy = serverProxy;
executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.naming.beat.sender");
return thread;
}
});
}
// 服务注册是,如果是临时节点,需要添加一个心跳信息
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
dom2Beat.put(buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()), beatInfo);
// 执行心跳任务,beatInfo.getPeriod():心跳间隔
executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
// Beat Task
class BeatTask implements Runnable {
BeatInfo beatInfo;
public BeatTask(BeatInfo beatInfo) {
this.beatInfo = beatInfo;
}
@Override
public void run() {
if (beatInfo.isStopped()) {
return;
}
// 调用NamingProxy 中的sendBeat方法,发送心跳包,url=/instance/beat
long result = serverProxy.sendBeat(beatInfo);
// 下一次发送心跳时间
long nextTime = result > 0 ? result : beatInfo.getPeriod();
// 创建一个心跳延时任务
executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
}
}
}
用于获取服务端注册信息,故障转移,定时刷盘备份等。
public class HostReactor {
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) { // 是否在启动是加载缓存
// DiskCache.read(this.cacheDir):从缓存目标中读取所有文件,缓存到serviceInfoMap内存中
this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(DiskCache.read(this.cacheDir));
} else {
this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(16);
}
this.updatingMap = new ConcurrentHashMap<String, Object>();
// 用于在注册中心故障时,故障转移。
this.failoverReactor = new FailoverReactor(this, cacheDir);
// 基于UDP连接,获取服务端注册信息变更通知,更新本地缓存、内存。
this.pushReceiver = new PushReceiver(this);
}
}
用于在注册中心故障时,客户端获取注册实例信息,一种故障转移安全机制。
public class FailoverReactor {
public FailoverReactor(HostReactor hostReactor, String cacheDir) {
this.hostReactor = hostReactor;
// 故障转移目标:user.home + /nacos/naming/ + namespace + /failover
this.failoverDir = cacheDir + "/failover";
// 初始化
this.init();
}
// 初始化
public void init() {
// 故障转移开关,5s后执行,判断 failoverDir + FAILOVER_SWITCH文件是否存在
// 如果不存在或者文件内容为0,关闭故障转移
// 如果存在,并且文件内容为1,开启故障转移
// 故障转移:开启一个FailoverFileReader线程,读取 failoverDir 下所有文件,缓存到内存中
executorService.scheduleWithFixedDelay(new SwitchRefresher(), 0L, 5000L, TimeUnit.MILLISECONDS);
// 开启一个刷盘延时任务:用于备份服务信息到故障转移目录下(failoverDir)
// DAY_PERIOD_MINUTES = 24 * 60:每隔24小时备份一次
// 刷盘:获取所有serviceInfoMap循环遍历,过滤掉其他文件(参照源码),将serviceInfoMap中文件,刷盘到failoverDir目录下
executorService.scheduleWithFixedDelay(new DiskFileWriter(), 30, DAY_PERIOD_MINUTES, TimeUnit.MINUTES);
// backup file on startup if failover directory is empty.
// 如果在启动时,failover 目录为空,先进行刷盘备份,10s后执行
executorService.schedule(new Runnable() {
@Override
public void run() {
try {
File cacheDir = new File(failoverDir);
if (!cacheDir.exists() && !cacheDir.mkdirs()) {
throw new IllegalStateException("failed to create cache dir: " + failoverDir);
}
File[] files = cacheDir.listFiles();
if (files == null || files.length <= 0) {
new DiskFileWriter().run();
}
} catch (Throwable e) {
NAMING_LOGGER.error("[NA] failed to backup file on startup.", e);
}
}
}, 10000L, TimeUnit.MILLISECONDS);
}
}
基于UDP连接,获取服务端注册信息变更通知,更新本地缓存、内存。
public class PushReceiver implements Runnable {
public PushReceiver(HostReactor hostReactor) {
try {
this.hostReactor = hostReactor;
// 建立一个UDP socket连接
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;
}
});
// 执行当前线程
executorService.execute(this);
} catch (Exception e) {
NAMING_LOGGER.error("[NA] init udp socket failed", e);
}
}
@Override
public void run() {
while (true) {
try {
// byte[] is initialized with 0 full filled by default
byte[] buffer = new byte[UDP_MSS];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 阻塞:服务端注册信息发送变更后,会主动监理一个UDP连接,发送消息
udpSocket.receive(packet);
// 接收到服务端注册信息
String json = new String(IoUtils.tryDecompress(packet.getData()), "UTF-8").trim();
NAMING_LOGGER.info("received push data: " + json + " from " + packet.getAddress().toString());
PushPacket pushPacket = JSON.parseObject(json, PushPacket.class);
// 服务端应答ack
String ack;
if ("dom".equals(pushPacket.type) || "service".equals(pushPacket.type)) {
// 处理服务端注册信息 字符串
// 1. 更新注册信息到本地缓存
// 2. 更新注册信息到本地文件
hostReactor.processServiceJSON(pushPacket.data);
// send ack to server
ack = "{\"type\": \"push-ack\""
+ ", \"lastRefTime\":\"" + pushPacket.lastRefTime
+ "\", \"data\":" + "\"\"}";
} else if ("dump".equals(pushPacket.type)) {
// dump data to server
ack = "{\"type\": \"dump-ack\""
+ ", \"lastRefTime\": \"" + pushPacket.lastRefTime
+ "\", \"data\":" + "\""
+ StringUtils.escapeJavaScript(JSON.toJSONString(hostReactor.getServiceInfoMap()))
+ "\"}";
} else {
// do nothing send ack only
ack = "{\"type\": \"unknown-ack\""
+ ", \"lastRefTime\":\"" + pushPacket.lastRefTime
+ "\", \"data\":" + "\"\"}";
}
// 发送服务端应答ack
udpSocket.send(new DatagramPacket(ack.getBytes(Charset.forName("UTF-8")),
ack.getBytes(Charset.forName("UTF-8")).length, packet.getSocketAddress()));
} catch (Exception e) {
NAMING_LOGGER.error("[NA] error while receiving push data", e);
}
}
}
}
namingService.registerInstance(serviceName, "localhost", 8080);
使用 namingService 中的 registerInstance 方法注册服务实例信息,最终都会调用到 registerInstance(String serviceName, String groupName, Instance instance)
方法。
public class NacosNamingService implements NamingService {
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
// 判断是否临时节点:临时节点需要发送心跳包,维持服务状态,默认为true
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);
// 获取实例心跳间隔
long instanceInterval = instance.getInstanceHeartBeatInterval();
// 设置间隔,默认DEFAULT_HEART_BEAT_INTERVAL=5s
beatInfo.setPeriod(instanceInterval == 0 ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);
// 使用2.2.2 BeatReactor 定时发送心跳包
beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
}
// serverProxy,即为NamingProxy:注册服务
serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}
}
public class NamingProxy {
// 注册服务
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<String, String> params = new HashMap<String, String>(9);
params.put(CommonParams.NAMESPACE_ID, namespaceId);
params.put(CommonParams.SERVICE_NAME, serviceName);
params.put(CommonParams.GROUP_NAME, groupName); // 分组,默认DEFAULT_GROUP
params.put(CommonParams.CLUSTER_NAME, instance.getClusterName()); // 所属集群名称,默认DEFAULT
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())); // 是否临时节点,默认true,是
params.put("metadata", JSON.toJSONString(instance.getMetadata())); // 元数据
// 发送服务注册请求:/nacos/v1/ns/instance
// reqAPI:主要是发送对应请求到服务端,如果异常,会遍历整改serverList请求
// 当所有server都请求失败,并且只有一个server服务时会重试(默认三次)
reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);
}
}
服务端收到客户端发送的 /nacos/v1/ns/instance
POST请求后,先将服务注册到 ServiceManager 中的 Map
对象中,然后为其中的 Service 添加实例信息,最后交由Nacos集群一致性服务,保持集群数据的一致性。
Nacos存储服务结构如下:
@RestController
@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/instance")
public class InstanceController {
@CanDistro
@RequestMapping(value = "", method = RequestMethod.POST)
public String register(HttpServletRequest request) throws Exception {
// 获取服务名称
String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
// 获取namespace
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
// 注册实例
// parseInstance(request):解析请求参数,封装为 Instance 对象
serviceManager.registerInstance(namespaceId, serviceName, parseInstance(request));
return "ok";
}
}
@Component
@DependsOn("nacosApplicationContext")
public class ServiceManager implements RecordListener<Service> {
/**
* Map>
*/
private Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
// 创建一个空的Service
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
// 获取Service:Map> serviceMap
Service service = getService(namespaceId, serviceName);
if (service == null) {
throw new NacosException(NacosException.INVALID_PARAM,
"service not found, namespace: " + namespaceId + ", service: " + serviceName);
}
// 给当前Service添加实例
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
// 创建一个空的Service
public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {
createServiceIfAbsent(namespaceId, serviceName, local, null);
}
// 如果不存在,创建一个Service实例
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster) throws NacosException {
// 从缓存中获取Service:Map> serviceMap
Service service = getService(namespaceId, serviceName);
// 为空,创建Service实例
if (service == null) {
Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
service = new Service();
service.setName(serviceName);
service.setNamespaceId(namespaceId);
service.setGroupName(NamingUtils.getGroupName(serviceName));
// now validate the service. if failed, exception will be thrown
service.setLastModifiedMillis(System.currentTimeMillis());
// 重新计算checksum
service.recalculateChecksum();
if (cluster != null) {
cluster.setService(service);
// 设置集群
service.getClusterMap().put(cluster.getName(), cluster);
}
// 参数校验
service.validate();
// 判断是否临时节点,客户端默认为true
if (local) {
// 临时节点:添加并且初始化
putServiceAndInit(service);
} else {
// 持久化节点:添加或替换服务
addOrReplaceService(service);
}
}
}
// 临时节点:添加并且初始化
private void putServiceAndInit(Service service) throws NacosException {
// 添加:将service对象添加到 Map> serviceMap 中
putService(service);
// 初始化service:详解2.3.1.2 Service 服务对象
service.init();
// 临时节点
// 一致性服务:用于保证Nacos集群中,Service对象数据一致性问题
// listen(String key, RecordListener listener)
// key -> com.alibaba.nacos.naming.iplist.ephemeral. + namespaceId + ## + serviceName
// listener -> 当前Service对象
// 参照后续详解Nacos集群一致性算法Raft
consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
// 持久化节点
// 一致性服务:用于保证Nacos集群中,Service对象数据一致性问题
// listen(String key, RecordListener listener)
// key -> com.alibaba.nacos.naming.iplist. + namespaceId + ## + serviceName
// listener -> 当前Service对象
// 参照后续详解Nacos集群一致性算法Raft
consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJSON());
}
// 添加:将service对象添加到 Map> serviceMap 中
public void putService(Service service) {
if (!serviceMap.containsKey(service.getNamespaceId())) {
synchronized (putServiceLock) {
if (!serviceMap.containsKey(service.getNamespaceId())) {
serviceMap.put(service.getNamespaceId(), new ConcurrentHashMap<>(16));
}
}
}
serviceMap.get(service.getNamespaceId()).put(service.getName(), service);
}
// 持久化节点:添加或替换服务
public void addOrReplaceService(Service service) throws NacosException {
// 将当前持久化节点注册信息转发到Nacos集群中的Leader节点进行处理,Follower只处理非事务请求
// put(String key, Record value)
// key -> com.alibaba.nacos.naming.domains.meta. + namespaceId + ## + serviceName
// valuy -> 当前Service对象
// 参照后续详解Nacos集群一致性算法Raft
consistencyService.put(KeyBuilder.buildServiceMetaKey(service.getNamespaceId(), service.getName()), service);
}
// 给当前Service添加实例
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws NacosException {
// 实例列表key
// 临时节点:com.alibaba.nacos.naming.iplist.ephemeral. + namespaceId + ## + serviceName
// 持久化节点:com.alibaba.nacos.naming.iplist. + namespaceId + ## + serviceName
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
// 从缓存中获取Service:Map> serviceMap
Service service = getService(namespaceId, serviceName);
// 为Service添加实例
List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
Instances instances = new Instances();
instances.setInstanceList(instanceList);
// 添加到一致性服务中ConcurrentMap datums
consistencyService.put(key, instances);
}
// 为Service添加实例
public List<Instance> addIpAddresses(Service service, boolean ephemeral, Instance... ips) throws NacosException {
return updateIpAddresses(service, UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD, ephemeral, ips);
}
public List<Instance> updateIpAddresses(Service service, String action, boolean ephemeral, Instance... ips) throws NacosException {
// 从一致性服务中获取Datum对象:ConcurrentMap datums
// 实例列表key
// 临时节点:com.alibaba.nacos.naming.iplist.ephemeral. + namespaceId + ## + serviceName
// 持久化节点:com.alibaba.nacos.naming.iplist. + namespaceId + ## + serviceName
Datum datum = consistencyService.get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), ephemeral));
Map<String, Instance> oldInstanceMap = new HashMap<>(16);
// 获取当前Service所有Instance对象
List<Instance> currentIPs = service.allIPs(ephemeral);
Map<String, Instance> map = new ConcurrentHashMap<>(currentIPs.size());
for (Instance instance : currentIPs) {
map.put(instance.toIPAddr(), instance);
}
if (datum != null) {
// 获取Old Instance
oldInstanceMap = setValid(((Instances) datum.value).getInstanceList(), map);
}
// use HashMap for deep copy:
HashMap<String, Instance> instanceMap = new HashMap<>(oldInstanceMap.size());
instanceMap.putAll(oldInstanceMap);
for (Instance instance : ips) {
if (!service.getClusterMap().containsKey(instance.getClusterName())) {
Cluster cluster = new Cluster(instance.getClusterName(), service);
// 集群初始化,详解参照2.3.1.3 Cluster集群对象
cluster.init();
service.getClusterMap().put(instance.getClusterName(), cluster);
Loggers.SRV_LOG.warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",
instance.getClusterName(), instance.toJSON());
}
// 添加 or 删除
if (UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)) {
instanceMap.remove(instance.getDatumKey());
} else {
instanceMap.put(instance.getDatumKey(), instance);
}
}
if (instanceMap.size() <= 0 && UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD.equals(action)) {
throw new IllegalArgumentException("ip list can not be empty, service: " + service.getName() + ", ip list: "
+ JSON.toJSONString(instanceMap.values()));
}
return new ArrayList<>(instanceMap.values());
}
}
public class Service extends com.alibaba.nacos.api.naming.pojo.Service implements Record, RecordListener<Instances> {
// 检查当前服务中的实例是否可用:根据客户端心跳请求更新时间判断
@JSONField(serialize = false)
private ClientBeatCheckTask clientBeatCheckTask = new ClientBeatCheckTask(this);
// 集群Map:key -> 集群名称
private Map<String, Cluster> clusterMap = new HashMap<>();
// 更新实例信息
public void onChange(String key, Instances value) throws Exception{
......
}
// 批量更新
public void updateIPs(Collection<Instance> instances, boolean ephemeral){
......
}
// 初始化
public void init() {
// 开启检查检查任务
HealthCheckReactor.scheduleCheck(clientBeatCheckTask);
for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {
entry.getValue().setService(this);
entry.getValue().init();
}
}
// 获取所有实例信息
public List<Instance> allIPs(){
......
}
}
public class Cluster extends com.alibaba.nacos.api.naming.pojo.Cluster implements Cloneable {
// 健康检查任务
@JSONField(serialize = false)
private HealthCheckTask checkTask;
// 持久化节点存储对象
@JSONField(serialize = false)
private Set<Instance> persistentInstances = new HashSet<>();
// 临时节点存储对象
@JSONField(serialize = false)
private Set<Instance> ephemeralInstances = new HashSet<>();
// 初始化
public void init() {
if (inited) {
return;
}
// 健康检查任务
checkTask = new HealthCheckTask(this);
// 开启健康检查任务
HealthCheckReactor.scheduleCheck(checkTask);
inited = true;
}
}
List<Instance> allInstances = namingService.getAllInstances("serviceName");
使用 namingService 中的 getAllInstances 方法获取服务实例信息,最终都会调用到 List
方法。
public class NacosNamingService implements NamingService {
@Override
public List<Instance> getAllInstances(String serviceName, String groupName, List<String> clusters, boolean subscribe) throws NacosException {
ServiceInfo serviceInfo;
// 是否订阅服务动态变化:默认为true
if (subscribe) {
// 获取、并订阅服务动态变化
serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
} else {
// 该方法是直接通过NamingProxy发送请求到服务器,获取服务信息
// url=/nacos/v1/ns/instance/list GET请求
serviceInfo = hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
}
List<Instance> list;
// 获取并返回实例信息
if (serviceInfo == null || CollectionUtils.isEmpty(list = serviceInfo.getHosts())) {
return new ArrayList<Instance>();
}
return list;
}
}
上面分析过 HostReactor 对象是用于获取服务端注册信息,故障转移,定时刷盘备份等,下面我们具体分析一下其中的方法。
获取服务信息(本地缓存、故障转移中心、远程服务)。
public class HostReactor {
public ServiceInfo getServiceInfo(final String serviceName, final String clusters) {
NAMING_LOGGER.debug("failover-mode: " + failoverReactor.isFailoverSwitch());
// serviceName + @@ + clusters
String key = ServiceInfo.getKey(serviceName, clusters);
// 是否开启故障转移
if (failoverReactor.isFailoverSwitch()) {
// 从故障转移中心获取服务信息
return failoverReactor.getService(key);
}
// 从缓存中获取服务信息:Map serviceInfoMap
ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters);
if (null == serviceObj) {
// 创建一个空的Service
serviceObj = new ServiceInfo(serviceName, clusters);
// 放入serviceInfoMap缓存中
serviceInfoMap.put(serviceObj.getKey(), serviceObj);
// 正在更新的serviceName:用于处理高并发,保证同一时刻,尽少线程调用url=/nacos/v1/ns/instance/list GET请求
// 保证了Nacos Server端的安全性
updatingMap.put(serviceName, new Object());
// 更新当前实例
updateServiceNow(serviceName, clusters);
updatingMap.remove(serviceName);
} else if (updatingMap.containsKey(serviceName)) { // 已经存在更新
if (UPDATE_HOLD_INTERVAL > 0) { // 5000 > 0 >>>> true
// hold a moment waiting for update finish
// 高并发:阻塞其他需要获取的线程,等待唤醒
synchronized (serviceObj) {
try {
// 最多等待 5000ms,即5s
serviceObj.wait(UPDATE_HOLD_INTERVAL);
} catch (InterruptedException e) {
NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);
}
}
}
}
// 定时更新任务:添加一个UpdateTask,每隔一段时间,发送请求到服务端,更新本地缓存、磁盘缓存等
scheduleUpdateIfAbsent(serviceName, clusters);
return serviceInfoMap.get(serviceObj.getKey());
}
}
立即更新服务信息。
发送 url=/nacos/v1/ns/instance/list GET请求
public class HostReactor {
public void updateServiceNow(String serviceName, String clusters) {
ServiceInfo oldService = getServiceInfo0(serviceName, clusters);
try {
// 发送 url=/nacos/v1/ns/instance/list GET请求
String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUDPPort(), false);
if (StringUtils.isNotEmpty(result)) {
// 处理返回结果:更新缓存,通知事件监听等
processServiceJSON(result);
}
} catch (Exception e) {
NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);
} finally {
if (oldService != null) {
synchronized (oldService) {
// 缓存等待线程
// 与hostReactor.getServiceInfo中的阻塞对应
// synchronized (serviceObj) {
// try {
// 最多等待 5000ms,即5s
// serviceObj.wait(UPDATE_HOLD_INTERVAL);
// } catch (InterruptedException e) {
// NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);
// }
// }
oldService.notifyAll();
}
}
}
}
}
public class HostReactor {
// 处理返回结果
public ServiceInfo processServiceJSON(String json) {
ServiceInfo serviceInfo = JSON.parseObject(json, ServiceInfo.class);
ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey());
// 忽略空或错误推送
if (serviceInfo.getHosts() == null || !serviceInfo.validate()) {
//empty or error push, just ignore
return oldService;
}
boolean changed = false;
// 如果oldService不为空,需要进行合并处理
if (oldService != null) {
// getLastRefTime():最后更新时间
if (oldService.getLastRefTime() > serviceInfo.getLastRefTime()) {
NAMING_LOGGER.warn("out of date data received, old-t: " + oldService.getLastRefTime()
+ ", new-t: " + serviceInfo.getLastRefTime());
}
// 更新本地缓存
serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
/****************比较并处理oldHostMap 和当前 serviceInfo的不同,并合并处理start**************/
Map<String, Instance> oldHostMap = new HashMap<String, Instance>(oldService.getHosts().size());
for (Instance host : oldService.getHosts()) {
oldHostMap.put(host.toInetAddr(), host);
}
Map<String, Instance> newHostMap = new HashMap<String, Instance>(serviceInfo.getHosts().size());
for (Instance host : serviceInfo.getHosts()) {
newHostMap.put(host.toInetAddr(), host);
}
Set<Instance> modHosts = new HashSet<Instance>();
Set<Instance> newHosts = new HashSet<Instance>();
Set<Instance> remvHosts = new HashSet<Instance>();
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();
if (oldHostMap.containsKey(key) && !StringUtils.equals(host.toString(),
oldHostMap.get(key).toString())) {
modHosts.add(host);
continue;
}
if (!oldHostMap.containsKey(key)) {
newHosts.add(host);
}
}
for (Map.Entry<String, Instance> entry : oldHostMap.entrySet()) {
Instance host = entry.getValue();
String key = entry.getKey();
if (newHostMap.containsKey(key)) {
continue;
}
if (!newHostMap.containsKey(key)) {
remvHosts.add(host);
}
}
if (newHosts.size() > 0) {
changed = true;
NAMING_LOGGER.info("new ips(" + newHosts.size() + ") service: "
+ serviceInfo.getKey() + " -> " + JSON.toJSONString(newHosts));
}
if (remvHosts.size() > 0) {
changed = true;
NAMING_LOGGER.info("removed ips(" + remvHosts.size() + ") service: "
+ serviceInfo.getKey() + " -> " + JSON.toJSONString(remvHosts));
}
if (modHosts.size() > 0) {
changed = true;
NAMING_LOGGER.info("modified ips(" + modHosts.size() + ") service: "
+ serviceInfo.getKey() + " -> " + JSON.toJSONString(modHosts));
}
serviceInfo.setJsonFromServer(json);
/****************比较并处理oldHostMap 和当前 serviceInfo的不同,并合并处理end**************/
// 判断是否存在更新
if (newHosts.size() > 0 || remvHosts.size() > 0 || modHosts.size() > 0) {
// 存在更新,发送服务更新事件,通知所有事件监听
eventDispatcher.serviceChanged(serviceInfo);
// 更新磁盘缓存
DiskCache.write(serviceInfo, cacheDir);
}
} else {
// oldService为空,直接更新本地缓存,发送服务更新更新事件,更新磁盘缓存
changed = true;
NAMING_LOGGER.info("init new ips(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() + " -> " + JSON
.toJSONString(serviceInfo.getHosts()));
// 更新本地缓存
serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
// 发送服务更新事件
eventDispatcher.serviceChanged(serviceInfo);
serviceInfo.setJsonFromServer(json);
// 更新磁盘缓存
DiskCache.write(serviceInfo, cacheDir);
}
MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());
if (changed) {
NAMING_LOGGER.info("current ips:(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() +
" -> " + JSON.toJSONString(serviceInfo.getHosts()));
}
return serviceInfo;
}
}
定时更新任务:添加一个UpdateTask,每隔一段时间,发送请求到服务端,更新本地缓存、磁盘缓存等。
public class HostReactor {
public void scheduleUpdateIfAbsent(String serviceName, String clusters) {
// 判断是否已经添加
if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {
return;
}
synchronized (futureMap) {
// 双重校验:判断是否已经添加
if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {
return;
}
// 添加一个UpdateTask
ScheduledFuture<?> future = addTask(new UpdateTask(serviceName, clusters));
futureMap.put(ServiceInfo.getKey(serviceName, clusters), future);
}
}
}
更新任务,用于保证本地缓存和Nacos服务端数据一致性。
public class HostReactor {
public class UpdateTask implements Runnable {
long lastRefTime = Long.MAX_VALUE;
private String clusters;
private String serviceName;
public UpdateTask(String serviceName, String clusters) {
this.serviceName = serviceName;
this.clusters = clusters;
}
@Override
public void run() {
try {
ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
// 为空直接更新
if (serviceObj == null) {
// 立即更新服务信息
updateServiceNow(serviceName, clusters);
// 循环添加UpdateTask DEFAULT_DELAY=1000
executor.schedule(this, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
return;
}
// 条件判断:上一次更新时间 <= 最后更新时间
if (serviceObj.getLastRefTime() <= lastRefTime) {
// 立即更新服务信息
updateServiceNow(serviceName, clusters);
serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
} else {
// if serviceName already updated by push, we should not override it
// since the push data may be different from pull through force push
// 已经被更新过,仅仅调用一次url=/nacos/v1/ns/instance/list GET请求
refreshOnly(serviceName, clusters);
}
// 循环添加UpdateTask
executor.schedule(this, serviceObj.getCacheMillis(), TimeUnit.MILLISECONDS);
// 最后更新时间
lastRefTime = serviceObj.getLastRefTime();
} catch (Throwable e) {
NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);
}
}
}
}
该方法是直接通过NamingProxy发送请求到服务器,获取服务信息返回
url=/nacos/v1/ns/instance/list GET请求
public class HostReactor {
public ServiceInfo getServiceInfoDirectlyFromServer(final String serviceName, final String clusters) throws NacosException {
// 发送url=/nacos/v1/ns/instance/list GET请求
String result = serverProxy.queryList(serviceName, clusters, 0, false);
if (StringUtils.isNotEmpty(result)) {
// 返回结果集
return JSON.parseObject(result, ServiceInfo.class);
}
return null;
}
}
客户端通过发送 url=/nacos/v1/ns/instance/list GET请求,查询当前服务信息,服务端收到请求,处理如下:
@RestController
@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/instance")
public class InstanceController {
/**
* 根据条件获取服务信息
*/
@RequestMapping(value = "/list", method = RequestMethod.GET)
public JSONObject list(HttpServletRequest request) throws Exception {
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,
Constants.DEFAULT_NAMESPACE_ID);
String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
String agent = request.getHeader("Client-Version");
if (StringUtils.isBlank(agent)) {
agent = request.getHeader("User-Agent");
}
String clusters = WebUtils.optional(request, "clusters", StringUtils.EMPTY);
String clientIP = WebUtils.optional(request, "clientIP", StringUtils.EMPTY);
// UDP通信端口
Integer udpPort = Integer.parseInt(WebUtils.optional(request, "udpPort", "0"));
String env = WebUtils.optional(request, "env", StringUtils.EMPTY);
boolean isCheck = Boolean.parseBoolean(WebUtils.optional(request, "isCheck", "false"));
String app = WebUtils.optional(request, "app", StringUtils.EMPTY);
String tenant = WebUtils.optional(request, "tid", StringUtils.EMPTY);
boolean healthyOnly = Boolean.parseBoolean(WebUtils.optional(request, "healthyOnly", "false"));
// 获取服务信息,如果UDP通信端口不为空,添加一个UDP通信客户端
return doSrvIPXT(namespaceId, serviceName, agent, clusters, clientIP, udpPort, env, isCheck, app, tenant, healthyOnly);
}
}
BeatReactor 主要是用来发送心跳包,服务端根据心跳时间,来判断当前服务上线、下线。
public class BeatReactor {
public BeatReactor(NamingProxy serverProxy, int threadCount) {
this.serverProxy = serverProxy;
executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.naming.beat.sender");
return thread;
}
});
}
// 服务注册是,如果是临时节点,需要添加一个心跳信息
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
dom2Beat.put(buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()), beatInfo);
// 执行心跳任务,beatInfo.getPeriod():心跳间隔
executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
// Beat Task
class BeatTask implements Runnable {
BeatInfo beatInfo;
public BeatTask(BeatInfo beatInfo) {
this.beatInfo = beatInfo;
}
@Override
public void run() {
if (beatInfo.isStopped()) {
return;
}
// 调用NamingProxy 中的sendBeat方法,发送心跳包,url=/instance/beat
long result = serverProxy.sendBeat(beatInfo);
// 下一次发送心跳时间
long nextTime = result > 0 ? result : beatInfo.getPeriod();
// 创建一个心跳延时任务
executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
}
}
}
在Service 服务对象中,存在一个 ClientBeatCheckTask 客户端心跳检查任务。
用于检查和更新临时实例的状态,如果它们已过期则将其删除。
public class ClientBeatCheckTask implements Runnable {
private Service service;
public ClientBeatCheckTask(Service service) {
this.service = service;
}
@JSONField(serialize = false)
public PushService getPushService() {
return SpringContext.getAppContext().getBean(PushService.class);
}
@JSONField(serialize = false)
public DistroMapper getDistroMapper() {
return SpringContext.getAppContext().getBean(DistroMapper.class);
}
public GlobalConfig getGlobalConfig() {
return SpringContext.getAppContext().getBean(GlobalConfig.class);
}
public String taskKey() {
return service.getName();
}
@Override
public void run() {
try {
// 当前服务是否已经发行、注册实例
if (!getDistroMapper().responsible(service.getName())) {
return;
}
// 获取当前服务所有短暂实例
List<Instance> instances = service.allIPs(true);
// first set health status of instances:
for (Instance instance : instances) {
// 当前时间 - 最后一次心跳时间 > 当前实例心跳超时时间
// true:已经失效
if (System.currentTimeMillis() - instance.getLastBeat() > instance.getInstanceHeartBeatTimeOut()) {
// 是否已被标记
if (!instance.isMarked()) {
if (instance.isHealthy()) {
// 设置健康状态
instance.setHealthy(false);
Loggers.EVT_LOG.info("{POS} {IP-DISABLED} valid: {}:{}@{}@{}, region: {}, msg: client timeout after {}, last beat: {}",
instance.getIp(), instance.getPort(), instance.getClusterName(), service.getName(),
UtilsAndCommons.LOCALHOST_SITE, instance.getInstanceHeartBeatTimeOut(), instance.getLastBeat());
// 发送 ServiceChangeEvent 服务信息变化时间事件
getPushService().serviceChanged(service);
// 发送 InstanceHeartbeatTimeoutEvent 实例心跳超时事件
SpringContext.getAppContext().publishEvent(new InstanceHeartbeatTimeoutEvent(this, instance));
}
}
}
}
if (!getGlobalConfig().isExpireInstance()) {
return;
}
// then remove obsolete instances:
// 删除过时的实例
for (Instance instance : instances) {
if (instance.isMarked()) {
continue;
}
// 当前时间 - 最后一次心跳时间 > 当前ip超时删除时间
if (System.currentTimeMillis() - instance.getLastBeat() > instance.getIpDeleteTimeout()) {
// delete instance
Loggers.SRV_LOG.info("[AUTO-DELETE-IP] service: {}, ip: {}", service.getName(), JSON.toJSONString(instance));
// 删除实例:调用本地实例删除请求
deleteIP(instance);
}
}
} catch (Exception e) {
Loggers.SRV_LOG.warn("Exception while processing client beat time out.", e);
}
}
// 删除实例:调用本地实例删除请求
private void deleteIP(Instance instance) {
try {
NamingProxy.Request request = NamingProxy.Request.newRequest();
request.appendParam("ip", instance.getIp())
.appendParam("port", String.valueOf(instance.getPort()))
.appendParam("ephemeral", "true")
.appendParam("clusterName", instance.getClusterName())
.appendParam("serviceName", service.getName())
.appendParam("namespaceId", service.getNamespaceId());
String url = "http://127.0.0.1:" + RunningConfig.getServerPort() + RunningConfig.getContextPath()
+ UtilsAndCommons.NACOS_NAMING_CONTEXT + "/instance?" + request.toUrl();
// delete instance asynchronously:
HttpClient.asyncHttpDelete(url, null, null, new AsyncCompletionHandler() {
@Override
public Object onCompleted(Response response) throws Exception {
if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
Loggers.SRV_LOG.error("[IP-DEAD] failed to delete ip automatically, ip: {}, caused {}, resp code: {}",
instance.toJSON(), response.getResponseBody(), response.getStatusCode());
}
return null;
}
});
} catch (Exception e) {
Loggers.SRV_LOG.error("[IP-DEAD] failed to delete ip automatically, ip: {}, error: {}", instance.toJSON(), e);
}
}
}
更新任务,用于保证本地缓存和Nacos服务端数据一致性。
public class HostReactor {
public class UpdateTask implements Runnable {
long lastRefTime = Long.MAX_VALUE;
private String clusters;
private String serviceName;
public UpdateTask(String serviceName, String clusters) {
this.serviceName = serviceName;
this.clusters = clusters;
}
@Override
public void run() {
try {
ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
// 为空直接更新
if (serviceObj == null) {
// 立即更新服务信息
updateServiceNow(serviceName, clusters);
// 循环添加UpdateTask DEFAULT_DELAY=1000
executor.schedule(this, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
return;
}
// 条件判断:上一次更新时间 <= 最后更新时间
if (serviceObj.getLastRefTime() <= lastRefTime) {
// 立即更新服务信息
updateServiceNow(serviceName, clusters);
serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
} else {
// if serviceName already updated by push, we should not override it
// since the push data may be different from pull through force push
// 已经被更新过,仅仅调用一次url=/nacos/v1/ns/instance/list GET请求
refreshOnly(serviceName, clusters);
}
// 循环添加UpdateTask
executor.schedule(this, serviceObj.getCacheMillis(), TimeUnit.MILLISECONDS);
// 最后更新时间
lastRefTime = serviceObj.getLastRefTime();
} catch (Throwable e) {
NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);
}
}
}
}
当服务信息发现变化时(服务注册、服务注销、心跳超时等),Nacos Server端会发送一个 ServiceChangeEvent 事件,PushService 收到该事件后,会基于UDP通知所有客户端。
@Component
public class PushService implements ApplicationContextAware, ApplicationListener<ServiceChangeEvent> {
// 客户端UDP连接缓存
private static ConcurrentMap<String, ConcurrentMap<String, PushClient>> clientMap
= new ConcurrentHashMap<String, ConcurrentMap<String, PushClient>>();
/**
* 监听 ServiceChangeEvent 事件
*/
@Override
public void onApplicationEvent(ServiceChangeEvent event) {
Service service = event.getService();
String serviceName = service.getName();
String namespaceId = service.getNamespaceId();
Future future = udpSender.schedule(new Runnable() {
@Override
public void run() {
try {
Loggers.PUSH.info(serviceName + " is changed, add it to push queue.");
// 获取当前服务对应的 PushClient
ConcurrentMap<String, PushClient> clients = clientMap.get(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));
if (MapUtils.isEmpty(clients)) {
return;
}
Map<String, Object> cache = new HashMap<>(16);
long lastRefTime = System.nanoTime();
for (PushClient client : clients.values()) {
// 判断当前连接事件为"僵尸"连接:无效连接
if (client.zombie()) {
Loggers.PUSH.debug("client is zombie: " + client.toString());
clients.remove(client.toString());
Loggers.PUSH.debug("client is zombie: " + client.toString());
continue;
}
Receiver.AckEntry ackEntry;
Loggers.PUSH.debug("push serviceName: {} to client: {}", serviceName, client.toString());
// 获取推送缓存key
String key = getPushCacheKey(serviceName, client.getIp(), client.getAgent());
byte[] compressData = null;
Map<String, Object> data = null;
if (switchDomain.getDefaultPushCacheMillis() >= 20000 && cache.containsKey(key)) {
org.javatuples.Pair pair = (org.javatuples.Pair) cache.get(key);
compressData = (byte[]) (pair.getValue0());
data = (Map<String, Object>) pair.getValue1();
Loggers.PUSH.debug("[PUSH-CACHE] cache hit: {}:{}", serviceName, client.getAddrStr());
}
// 准备(构建)请求报文
if (compressData != null) {
ackEntry = prepareAckEntry(client, compressData, data, lastRefTime);
} else {
ackEntry = prepareAckEntry(client, prepareHostsData(client), lastRefTime);
if (ackEntry != null) {
cache.put(key, new org.javatuples.Pair<>(ackEntry.origin.getData(), ackEntry.data));
}
}
Loggers.PUSH.info("serviceName: {} changed, schedule push for: {}, agent: {}, key: {}",
client.getServiceName(), client.getAddrStr(), client.getAgent(), (ackEntry == null ? null : ackEntry.key));
// 基于UDP发送报文
udpPush(ackEntry);
}
} catch (Exception e) {
Loggers.PUSH.error("[NACOS-PUSH] failed to push serviceName: {} to client, error: {}", serviceName, e);
} finally {
futureMap.remove(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));
}
}
}, 1000, TimeUnit.MILLISECONDS);
futureMap.put(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName), future);
}
}