这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
基本设置
配置类
@Getter
@Component
@ConfigurationProperties(prefix = "boot")
public class ContextParam {
boolean taskQueue;
// 此处打个断点
public void setTaskQueue(boolean taskQueue) {
this.taskQueue = taskQueue;
}
}
bootstrap配置文件
spring.application.name=boot-demo
spring.profiles.active=local
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.service=boot-demo
spring.cloud.nacos.discovery.namespace=1ed08d33-198e-4b84-a65a-4becf4ba0a6f
spring.cloud.nacos.config.namespace=1ed08d33-198e-4b84-a65a-4becf4ba0a6f
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.file-extension=properties
nacos配置文件
源码分析
此时在属性的set方法中打个断点,修改nacos配置,这时候断点应该会进入,分析调用堆栈 大致上堆栈就是这样的
rebind:102, ConfigurationPropertiesRebinder
.......
publishEvent:400, AbstractApplicationContext
....
refresh:65, ContextRefresher (org.springframework.cloud.context.refresh)
handle:36, RefreshEventListener (org.springframework.cloud.endpoint.event)
.....
.....
receiveConfigInfo:125, NacosContextRefresher$1 (com.alibaba.cloud.nacos.refresh)
run:190, CacheData$1 (com.alibaba.nacos.client.config.impl)
safeNotifyListener:211, CacheData (com.alibaba.nacos.client.config.impl)
checkListenerMd5:161, CacheData (com.alibaba.nacos.client.config.impl)
run:539, ClientWorker$LongPollingRunnable (com.alibaba.nacos.client.config.impl)
....
这里其实已经基本包括了配置的监听、获取所有流程,
从直观能感受到的开始
配置更改
ClientWorker$LongPollingRunnable 这是一个nacos长轮询的线程,ClientWorker内部定义
其run方法大致如下
@Override
public void run() {
// 缓存数据
List<CacheData> cacheDatas = new ArrayList<CacheData>();
List<String> inInitializingCacheList = new ArrayList<String>();
try {
.....
// 检测修改的配置
List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
for (String groupKey : changedGroupKeys) {
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 = getServerConfig(dataId, group, tenant, 3000L);
CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
cache.setContent(content);
...
} catch (NacosException ioe) {
....
}
}
for (CacheData cacheData : cacheDatas) {
if (!cacheData.isInitializing() || inInitializingCacheList
.contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
// 检测配置md5
cacheData.checkListenerMd5();
cacheData.setInitializing(false);
}
}
// 继续启动长轮询
executorService.execute(this);
} catch (Throwable e) {
....
}
}
checkListenerMd5 方法如下
void checkListenerMd5() {
for (ManagerListenerWrap wrap : listeners) {
// 配置版本(MD5)发生变化通知
if (!md5.equals(wrap.lastCallMd5)) {
safeNotifyListener(dataId, group, content, md5, wrap);
}
}
}
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() {
@Override
public void run() {
。。。。。
try {
。。。。。
ConfigResponse cr = new ConfigResponse();
cr.setDataId(dataId);
cr.setGroup(group);
cr.setContent(content);
configFilterChainManager.doFilter(null, cr);
String contentTmp = cr.getContent();
// 配置修改或者读取回调
listener.receiveConfigInfo(contentTmp);
// 修改最后版本
listenerWrap.lastCallMd5 = md5;
。。
}
.....
finally {
Thread.currentThread().setContextClassLoader(myClassLoader);
}
}
};
try {
// 异步通知
if (null != listener.getExecutor()) {
listener.getExecutor().execute(job);
} else {
job.run();
}
} catch (Throwable t) {
....
}
}
// 注册监听器
private void registerNacosListener(final String group, final String dataId) {
Listener listener = listenerMap.computeIfAbsent(dataId, i -> new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
refreshCountIncrement();
String md5 = "";
if (!StringUtils.isEmpty(configInfo)) {
try {
// 最新版本号
MessageDigest md = MessageDigest.getInstance("MD5");
md5 = new BigInteger(1, md.digest(configInfo.getBytes("UTF-8")))
.toString(16);
}
catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
log.warn("[Nacos] unable to get md5 for dataId: " + dataId, e);
}
}
refreshHistory.add(dataId, md5);
// 容器刷新事件推送
// 属性修改
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
if (log.isDebugEnabled()) {
log.debug("Refresh Nacos config group " + group + ",dataId" + dataId);
}
}
@Override
public Executor getExecutor() {
return null;
}
});
try {
configService.addListener(dataId, group, listener);
}
catch (NacosException e) {
e.printStackTrace();
}
}
最终次方法被NacosContextRefresher 调用
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// Spring context 准备完成
if (this.ready.compareAndSet(false, true)) {
this.registerNacosListenersForApplications();
}
}
NacosContextRefresher 这个类呢又是被NacosConfigAutoConfiguration注入进去的
长轮询的注册
com.alibaba.nacos.client.config.impl.ClientWorker#ClientWorker
构造方法如下
public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager, final Properties properties) {
this.agent = agent;
this.configFilterChainManager = configFilterChainManager;
// Initialize the timeout parameter
init(properties);
executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
t.setDaemon(true);
return t;
}
});
executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
t.setDaemon(true);
return t;
}
});
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
checkConfigInfo();
} catch (Throwable e) {
LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
}
}
}, 1L, 10L, TimeUnit.MILLISECONDS);
}
com.alibaba.nacos.client.config.NacosConfigService#NacosConfigService构造方法如下
public NacosConfigService(Properties properties) throws NacosException {
String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
if (StringUtils.isBlank(encodeTmp)) {
encode = Constants.ENCODE;
} else {
encode = encodeTmp.trim();
}
initNamespace(properties);
agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
agent.start();
worker = new ClientWorker(agent, configFilterChainManager, properties);
}
com.alibaba.cloud.nacos.refresh.NacosContextRefresher#NacosContextRefresher构造方法如下
public NacosContextRefresher(NacosRefreshProperties refreshProperties,
NacosRefreshHistory refreshHistory, ConfigService configService) {
this.refreshProperties = refreshProperties;
this.refreshHistory = refreshHistory;
this.configService = configService;
}
这样一整套的注册、监听全部都串联起来,核心的类就是
NacosContextRefresher