Nacos结合@RefreshScope实现动态配置原理
Nacos动态刷新源码分析
通过Nacos源码分析,Nacos采用客户端通过无状态http请求主动向服务端发送请求,拉取配置的方式实现动态配置.下面将具体分析。
通过ConfigFactory创建ConfigService(实际类型为NacosConfigService)
// An highlighted block
public static ConfigService createConfigService(String serverAddr) throws NacosException {
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
try {
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");
Constructor constructor = driverImplClass.getConstructor(Properties.class);
ConfigService vendorImpl = (ConfigService)constructor.newInstance(properties);
return vendorImpl;
} catch (Throwable var5) {
throw new NacosException(-400, var5.getMessage());
}
}
在NacosConfigService的构造方法中创建了一个长轮询的客户端对象
public class NacosConfigService implements ConfigService {
public static final Logger log = LogUtils.logger(NacosConfigService.class);
public final long POST_TIMEOUT = 3000L;
private ServerHttpAgent agent;
private ClientWorker worker;
private String namespace;
private String encode;
private ConfigFilterChainManager configFilterChainManager = new ConfigFilterChainManager();
public NacosConfigService(Properties properties) throws NacosException {
String encodeTmp = properties.getProperty("encode");
if (StringUtils.isBlank(encodeTmp)) {
this.encode = "UTF-8";
} else {
this.encode = encodeTmp.trim();
}
String namespaceTmp = properties.getProperty("namespace");
if (StringUtils.isBlank(namespaceTmp)) {
this.namespace = TenantUtil.getUserTenant();
properties.put("namespace", this.namespace);
} else {
this.namespace = namespaceTmp;
properties.put("namespace", this.namespace);
}
this.agent = new ServerHttpAgent(properties);
this.agent.start();
this.worker = new ClientWorker(this.agent, this.configFilterChainManager);
}
......
}
而在ClientWorker中,创建了定时任务,定时检查本地配置文件和远程配置文件(检查逻辑在ClientWorker内部类LongPollingRunnable中)
public ClientWorker(final ServerHttpAgent agent, ConfigFilterChainManager configFilterChainManager) {
this.agent = agent;
this.configFilterChainManager = configFilterChainManager;
this.executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
t.setDaemon(true);
return t;
}
});
this.executorService = Executors.newCachedThreadPool(new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker.longPulling" + agent.getName());
t.setDaemon(true);
return t;
}
});
this.executor.scheduleWithFixedDelay(new Runnable() {
public void run() {
try {
ClientWorker.this.checkConfigInfo();
} catch (Throwable var2) {
ClientWorker.log.error(agent.getName(), "NACOS-XXXX", "[sub-check] rotate check error", var2);
}
}
}, 1L, 10L, TimeUnit.MILLISECONDS);
}
public void checkConfigInfo() {
int listenerSize = ((Map)this.cacheMap.get()).size();
int longingTaskCount = (int)Math.ceil((double)listenerSize / ParamUtil.getPerTaskConfigSize());
if ((double)longingTaskCount > this.currentLongingTaskCount) {
for(int i = (int)this.currentLongingTaskCount; i < longingTaskCount; ++i) {
this.executorService.execute(new ClientWorker.LongPullingRunnable(i));
}
this.currentLongingTaskCount = (double)longingTaskCount;
}
}
本地文件检查逻辑:checkLocalConfig()方法,以保证配置服务挂掉后,服务能通过本地配置文件启动
class LongPullingRunnable implements Runnable {
private int taskId;
public LongPullingRunnable(int taskId) {
this.taskId = taskId;
}
public void run() {
try {
List<CacheData> cacheDatas = new ArrayList();
Iterator var2 = ((Map)ClientWorker.this.cacheMap.get()).values().iterator();
while(var2.hasNext()) {
CacheData cacheDatax = (CacheData)var2.next();
if (cacheDatax.getTaskId() == this.taskId) {
cacheDatas.add(cacheDatax);
try {
ClientWorker.this.checkLocalConfig(cacheDatax);
if (cacheDatax.isUseLocalConfigInfo()) {
cacheDatax.checkListenerMd5();
}
} catch (Exception var18) {
ClientWorker.log.error("NACOS-CLIENT", "get local config info error", var18);
}
}
}
List<String> inInitializingCacheList = new ArrayList();
List<String> changedGroupKeys = ClientWorker.this.checkUpdateDataIds(cacheDatas, inInitializingCacheList);
Iterator var4 = changedGroupKeys.iterator();
while(var4.hasNext()) {
String groupKey = (String)var4.next();
String[] key = GroupKey.parseKey(groupKey);
String dataId = key[0];
String group = key[1];
String tenant = null;
if (key.length == 3) {
tenant = key[2];
}
try {
String content = ClientWorker.this.getServerConfig(dataId, group, tenant, 3000L);
CacheData cache = (CacheData)((Map)ClientWorker.this.cacheMap.get()).get(GroupKey.getKeyTenant(dataId, group, tenant));
cache.setContent(content);
ClientWorker.log.info(ClientWorker.this.agent.getName(), "[data-received] dataId={}, group={}, tenant={}, md5={}, content={}", new Object[]{dataId, group, tenant, cache.getMd5(), ContentUtils.truncateContent(content)});
} catch (NacosException var17) {
ClientWorker.log.error(ClientWorker.this.agent.getName(), "NACOS-XXXX", "[get-update] get changed config exception. dataId={}, group={}, tenant={}, msg={}", new Object[]{dataId, group, tenant, var17.toString()});
}
}
var4 = cacheDatas.iterator();
while(true) {
CacheData cacheData;
do {
if (!var4.hasNext()) {
inInitializingCacheList.clear();
return;
}
cacheData = (CacheData)var4.next();
} while(cacheData.isInitializing() && !inInitializingCacheList.contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant)));
cacheData.checkListenerMd5();
cacheData.setInitializing(false);
}
} catch (Throwable var19) {
ClientWorker.log.error("500", "longPulling error", var19);
} finally {
ClientWorker.this.executorService.execute(this);
}
}
}
服务端配置文件检查:发起http请求获取服务端已经变化的配置文件
List<String> checkUpdateDataIds(List<CacheData> cacheDatas, List<String> inInitializingCacheList) {
StringBuilder sb = new StringBuilder();
Iterator var4 = cacheDatas.iterator();
while(var4.hasNext()) {
CacheData cacheData = (CacheData)var4.next();
if (!cacheData.isUseLocalConfigInfo()) {
sb.append(cacheData.dataId).append(Constants.WORD_SEPARATOR);
sb.append(cacheData.group).append(Constants.WORD_SEPARATOR);
if (StringUtils.isBlank(cacheData.tenant)) {
sb.append(cacheData.getMd5()).append(Constants.LINE_SEPARATOR);
} else {
sb.append(cacheData.getMd5()).append(Constants.WORD_SEPARATOR);
sb.append(cacheData.getTenant()).append(Constants.LINE_SEPARATOR);
}
if (cacheData.isInitializing()) {
inInitializingCacheList.add(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant));
}
}
}
boolean isInitializingCacheList = !inInitializingCacheList.isEmpty();
return this.checkUpdateConfigStr(sb.toString(), isInitializingCacheList);
}
然后会匹配ConfigService的Listener的对象的配置内容md5值和缓存CacheData的配置内容md5值是否一直,如果不一致则证明配置文件修改过
void checkListenerMd5() {
Iterator var1 = this.listeners.iterator();
while(var1.hasNext()) {
ManagerListenerWrap wrap = (ManagerListenerWrap)var1.next();
if (!this.md5.equals(wrap.lastCallMd5)) {
this.safeNotifyListener(this.dataId, this.group, this.content, this.md5, wrap);
}
}
}
然后再Cache中的safeNotifyListener方法回调Listener的方法receiveConfigInfo方法通知spring框架RefreshEventListener进行配置更新
private void safeNotifyListener(final String dataId, final String group, final String content, final String md5, final ManagerListenerWrap listenerWrap) {
final Listener listener = listenerWrap.listener;
Runnable job = new Runnable() {
public void run() {
ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();
ClassLoader appClassLoader = listener.getClass().getClassLoader();
try {
if (listener instanceof AbstractSharedListener) {
AbstractSharedListener adapter = (AbstractSharedListener)listener;
adapter.fillContext(dataId, group);
CacheData.log.info(CacheData.this.name, "[notify-context] dataId={}, group={}, md5={}", new Object[]{dataId, group, md5});
}
Thread.currentThread().setContextClassLoader(appClassLoader);
ConfigResponse cr = new ConfigResponse();
cr.setDataId(dataId);
cr.setGroup(group);
cr.setContent(content);
CacheData.this.configFilterChainManager.doFilter((IConfigRequest)null, cr);
String contentTmp = cr.getContent();
listener.receiveConfigInfo(contentTmp);
listenerWrap.lastCallMd5 = md5;
CacheData.log.info(CacheData.this.name, "[notify-ok] dataId={}, group={}, md5={}, listener={} ", new Object[]{dataId, group, md5, listener});
} catch (NacosException var9) {
CacheData.log.error(CacheData.this.name, "NACOS-XXXX", "[notify-error] dataId={}, group={}, md5={}, listener={} errCode={} errMsg={}", new Object[]{dataId, group, md5, listener, var9.getErrCode(), var9.getErrMsg()});
} catch (Throwable var10) {
CacheData.log.error(CacheData.this.name, "NACOS-XXXX", "[notify-error] dataId={}, group={}, md5={}, listener={} tx={}", new Object[]{dataId, group, md5, listener, var10.getCause()});
} finally {
Thread.currentThread().setContextClassLoader(myClassLoader);
}
}
};
long startNotify = System.currentTimeMillis();
try {
if (null != listener.getExecutor()) {
listener.getExecutor().execute(job);
} else {
job.run();
}
} catch (Throwable var12) {
log.error(this.name, "NACOS-XXXX", "[notify-error] dataId={}, group={}, md5={}, listener={} throwable={}", new Object[]{dataId, group, md5, listener, var12.getCause()});
}
long finishNotify = System.currentTimeMillis();
log.info(this.name, "[notify-listener] time cost={}ms in ClientWorker, dataId={}, group={}, md5={}, listener={} ", new Object[]{finishNotify - startNotify, dataId, group, md5, listener});
}
未完待续…