Nacos深入浅出(九)
然而Nacos的发布操作并不是上面我们想的那样通过代理去实现,通过下面的代码我们分析下:
public class NacosConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, ApplicationContextAware { /**
* The name of {@link NacosConfigurationPropertiesBindingPostProcessor} Bean
*/
public static final String BEAN_NAME = "nacosConfigurationPropertiesBindingPostProcessor"; private Properties globalNacosProperties; private NacosServiceFactory nacosServiceFactory; private Environment environment; private ApplicationEventPublisher applicationEventPublisher; private ConfigurableApplicationContext applicationContext;
// 类初始化之前,进行绑定监听的操作
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { NacosConfigurationProperties nacosConfigurationProperties = findAnnotation(bean.getClass(), NacosConfigurationProperties.class); if (nacosConfigurationProperties != null) {
bind(bean, beanName, nacosConfigurationProperties);
} return bean;
} private void bind(Object bean, String beanName, NacosConfigurationProperties nacosConfigurationProperties) { NacosConfigurationPropertiesBinder binder = new NacosConfigurationPropertiesBinder(applicationContext); binder.bind(bean, beanName, nacosConfigurationProperties); } @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
} @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
}
下面进行绑定监听操作!
protected void bind(final Object bean, final String beanName, final NacosConfigurationProperties properties) { Assert.notNull(bean, "Bean must not be null!"); Assert.notNull(properties, "NacosConfigurationProperties must not be null!"); final String dataId = properties.dataId(); final String groupId = properties.groupId(); final ConfigService configService = configServiceBeanBuilder.build(properties.properties());
// 这个就是注解里面的autoRefresh的属性是否自动刷新的值,ture的话就会触发下面的操作
if (properties.autoRefreshed()) { // Add a Listener if auto-refreshed try {
configService.addListener(dataId, groupId, new AbstractListener() {
@Override
public void receiveConfigInfo(String config) {
doBind(bean, beanName, dataId, groupId, properties, config, configService);
}
});
} catch (NacosException e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
}
} String content = getContent(configService, dataId, groupId); if (hasText(content)) {
doBind(bean, beanName, dataId, groupId, properties, content, configService);
}
}
@Override
public void addListener(String dataId, String group, Listener listener) throws NacosException {
Listener listenerAdapter = new DelegatingEventPublishingListener(configService, dataId, group, applicationEventPublisher, executor, listener);
configService.addListener(dataId, group, listenerAdapter);
publishEvent(new NacosConfigListenerRegisteredEvent(configService, dataId, group, listener, true));
}
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);
} public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
return this.getConfigInner(this.namespace, dataId, group, timeoutMs);
} public void addListener(String dataId, String group, Listener listener) throws NacosException {
this.worker.addTenantListeners(dataId, group, Arrays.asList(listener));
}
ClientWorker.java
public void addTenantListeners(String dataId, String group, List<? extends Listener> listeners) {
group = null2defaultGroup(group);
String tenant = agent.getTenant();
CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);
for (Listener listener : listeners) {
cache.addListener(listener);
}
}
DEBUG就能看出来初始化的时候,client把每个config封装成一个cache,每个cache再增加监听listener;
CacheData.java
public void addListener(Listener listener) {
if (null == listener) {
throw new IllegalArgumentException("listener is null");
}
ManagerListenerWrap wrap = new ManagerListenerWrap(listener);
if (listeners.addIfAbsent(wrap)) {
LOGGER.info("[{}] [add-listener] ok, tenant={}, dataId={}, group={}, cnt={}", name, tenant, dataId, group,
listeners.size());
}
}
DelegatingEventPublishingListener(ConfigService configService, String dataId, String groupId,
ApplicationEventPublisher applicationEventPublisher,
Executor executor, Listener delegate) {
this.configService = configService;
this.dataId = dataId;
this.groupId = groupId;
this.applicationEventPublisher = applicationEventPublisher;
this.executor = executor;
this.delegate = delegate;
}
private void doBind(Object bean, String beanName, String dataId, String groupId,
NacosConfigurationProperties properties, String content, ConfigService configService) {
PropertyValues propertyValues = resolvePropertyValues(bean, content);
doBind(bean, properties, propertyValues);
publishBoundEvent(bean, beanName, dataId, groupId, properties, content, configService);
publishMetadataEvent(bean, beanName, dataId, groupId, properties);
} private void publishMetadataEvent(Object bean, String beanName, String dataId, String groupId,
NacosConfigurationProperties properties) { NacosProperties nacosProperties = properties.properties(); NacosConfigMetadataEvent metadataEvent = new NacosConfigMetadataEvent(properties); // Nacos Metadata
metadataEvent.setDataId(dataId);
metadataEvent.setGroupId(groupId);
Properties resolvedNacosProperties = configServiceBeanBuilder.resolveProperties(nacosProperties);
Map<String, Object> nacosPropertiesAttributes = getAnnotationAttributes(nacosProperties);
metadataEvent.setNacosPropertiesAttributes(nacosPropertiesAttributes);
metadataEvent.setNacosProperties(resolvedNacosProperties); // Bean Metadata
Class<?> beanClass = bean.getClass();
metadataEvent.setBeanName(beanName);
metadataEvent.setBean(bean);
metadataEvent.setBeanType(beanClass);
metadataEvent.setAnnotatedElement(beanClass); // Publish event
applicationEventPublisher.publishEvent(metadataEvent);
}
这样就自动发布了事件,然后我们的监听就收到了事件,然后触发相应的操作;下面我们结合Nacos一起debug下,看下效果!
ClientWorker.java
/**
* groupKey -> cacheData
*/
AtomicReference<Map<String, CacheData>> cacheMap = new AtomicReference<Map<String, CacheData>>(
new HashMap<String, CacheData>());
public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager) {
this.agent = agent;
this.configFilterChainManager = configFilterChainManager; 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.newCachedThreadPool(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() {
public void run() {
try {
checkConfigInfo();
} catch (Throwable e) {
LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
}
}
}, 1L, 10L, TimeUnit.MILLISECONDS);
}
public void checkConfigInfo() {
// 分任务
int listenerSize = cacheMap.get().size();
// 向上取整为批数
int longingTaskCount = (int)Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
if (longingTaskCount > currentLongingTaskCount) {
for (int i = (int)currentLongingTaskCount; i < longingTaskCount; i++) {
// 要判断任务是否在执行 这块需要好好想想。 任务列表现在是无序的。变化过程可能有问题
executorService.execute(new LongPollingRunnable(i));
}
currentLongingTaskCount = longingTaskCount;
}
}
线程任务!
class LongPollingRunnable implements Runnable {
private int taskId; public LongPollingRunnable(int taskId) {
this.taskId = taskId;
} public void run() {
try {
List<CacheData> cacheDatas = new ArrayList<CacheData>();
// check failover config
for (CacheData cacheData : cacheMap.get().values()) {
if (cacheData.getTaskId() == taskId) {
cacheDatas.add(cacheData);
try {
checkLocalConfig(cacheData);
if (cacheData.isUseLocalConfigInfo()) {
cacheData.checkListenerMd5();
}
} catch (Exception e) {
LOGGER.error("get local config info error", e);
}
}
} List<String> inInitializingCacheList = new ArrayList<String>();
// check server config
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);
LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}",
agent.getName(), dataId, group, tenant, cache.getMd5(),
ContentUtils.truncateContent(content));
} catch (NacosException ioe) {
String message = String.format(
"[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",
agent.getName(), dataId, group, tenant);
LOGGER.error(message, ioe);
}
}
for (CacheData cacheData : cacheDatas) {
if (!cacheData.isInitializing() || inInitializingCacheList
.contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
cacheData.checkListenerMd5();
cacheData.setInitializing(false);
}
}
inInitializingCacheList.clear();
} catch (Throwable e) {
LOGGER.error("longPolling error", e);
} finally {
executorService.execute(this);
}
}
}
private void checkLocalConfig(CacheData cacheData) {
final String dataId = cacheData.dataId;
final String group = cacheData.group;
final String tenant = cacheData.tenant;
File path = LocalConfigInfoProcessor.getFailoverFile(agent.getName(), dataId, group, tenant); // 没有 -> 有
if (!cacheData.isUseLocalConfigInfo() && path.exists()) {
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
String md5 = MD5.getInstance().getMD5String(content);
cacheData.setUseLocalConfigInfo(true);
cacheData.setLocalConfigInfoVersion(path.lastModified());
cacheData.setContent(content); LOGGER.warn("[{}] [failover-change] failover file created. dataId={}, group={}, tenant={}, md5={}, content={}",
agent.getName(), dataId, group, tenant, md5, ContentUtils.truncateContent(content));
return;
} // 有 -> 没有。不通知业务监听器,从server拿到配置后通知。
if (cacheData.isUseLocalConfigInfo() && !path.exists()) {
cacheData.setUseLocalConfigInfo(false);
LOGGER.warn("[{}] [failover-change] failover file deleted. dataId={}, group={}, tenant={}", agent.getName(),
dataId, group, tenant);
return;
} // 有变更
if (cacheData.isUseLocalConfigInfo() && path.exists()
&& cacheData.getLocalConfigInfoVersion() != path.lastModified()) {
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
String md5 = MD5.getInstance().getMD5String(content);
cacheData.setUseLocalConfigInfo(true);
cacheData.setLocalConfigInfoVersion(path.lastModified());
cacheData.setContent(content);
LOGGER.warn("[{}] [failover-change] failover file changed. dataId={}, group={}, tenant={}, md5={}, content={}",
agent.getName(), dataId, group, tenant, md5, ContentUtils.truncateContent(content));
return;
}
}
void checkListenerMd5() {
for (ManagerListenerWrap wrap : listeners) {
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() {
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);
LOGGER.info("[{}] [notify-context] dataId={}, group={}, md5={}", name, dataId, group, md5);
}
// 执行回调之前先将线程classloader设置为具体webapp的classloader,以免回调方法中调用spi接口是出现异常或错用(多应用部署才会有该问题)。
Thread.currentThread().setContextClassLoader(appClassLoader); 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;
LOGGER.info("[{}] [notify-ok] dataId={}, group={}, md5={}, listener={} ", name, dataId, group, md5,
listener);
} catch (NacosException de) {
LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} errCode={} errMsg={}", name,
dataId, group, md5, listener, de.getErrCode(), de.getErrMsg());
} catch (Throwable t) {
LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} tx={}", name, dataId, group,
md5, listener, t.getCause());
} finally {
Thread.currentThread().setContextClassLoader(myClassLoader);
}
}
}; final long startNotify = System.currentTimeMillis();
try {
if (null != listener.getExecutor()) {
listener.getExecutor().execute(job);
} else {
job.run();
}
} catch (Throwable t) {
LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} throwable={}", name, dataId, group,
md5, listener, t.getCause());
}
final long finishNotify = System.currentTimeMillis();
LOGGER.info("[{}] [notify-listener] time cost={}ms in ClientWorker, dataId={}, group={}, md5={}, listener={} ",
name, (finishNotify - startNotify), dataId, group, md5, listener);
}
listener.receiveConfigInfo(contentTmp);
DelegatingEventPublishingListener.java就会发布事件,一个闭环就形成了!
public void receiveConfigInfo(String content) {
this.publishEvent(content);
this.onReceived(content);
}
客户端会有个长轮询不断去Nacos平台去取值,更新缓存;所以并不是Nacos推过来,而是client主动去取值的!
但是我们还有个核心的问题没搞明白,我们取值的时候,怎么去cacheData里面取值的?
这些成熟的产品都是经过N次迭代,不断的打磨才完善的,多人多日,现在我们自己看的时候有疑惑是很正常的,想办法,加油!
下面是在网上查的资料
BeanFactoryPostProcessor.postProcessBeanFactory
接口函数处理注解@NacosPropertySource和xml property source配置,获取配置,插入spring配置源,注册配置变更监听回调函数InstantiationAwareBeanPostProcessor.postProcessPropertyValues
接口函数
处理@NacosInjected注入configservice服务
处理@NacosValue注入属性的配置值信息,BeanPostProcessor.postProcessBeforeInitialization
处理@NacosConfigurationProperties,注入配置到目标bean,注册配置变更监听回调更新目标bean
处理@NacosValue,缓存标记的配置项用做后续动态更新ContextRefreshedEvent
监听,spring初始化完成时触发。
处理@NacosConfigListener, 注册配置变更监听回调。ApplicationListener.onApplicationEvent
监听spring事件NacosConfigReceivedEvent,每次从配置服务端获取配置时都会触发该事件,用于动态变更@NacosValue注解目标。
基本上就是这样一个逻辑,
NacosValueAnnotationBeanPostProcessor.java
public void onApplicationEvent(NacosConfigReceivedEvent event) {
String content = event.getContent();
if (content != null) {
Properties configProperties = NacosUtils.toProperties(content);
Iterator var4 = configProperties.keySet().iterator(); while(true) {
String propertyKey;
List beanPropertyList;
do {
if (!var4.hasNext()) {
return;
} Object key = var4.next();
propertyKey = (String)key;
beanPropertyList = (List)this.placeholderNacosValueTargetMap.get(propertyKey);
} while(beanPropertyList == null); String propertyValue = configProperties.getProperty(propertyKey);
Iterator var9 = beanPropertyList.iterator(); while(var9.hasNext()) {
NacosValueAnnotationBeanPostProcessor.NacosValueTarget nacosValueTarget = (NacosValueAnnotationBeanPostProcessor.NacosValueTarget)var9.next();
if (nacosValueTarget.method == null) {
this.setField(nacosValueTarget, propertyValue);
} else {
this.setMethod(nacosValueTarget, propertyValue);
}
}
}
}
}
他会变更这个property对应的key值;我们结合上面的代码看下:
下面标色的这些类复杂,很多我们专注也业务端代码的同学们可能都没见过,后面我们就一一分析归类
public class NacosConfigBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware, BeanFactoryAware
public class NacosPropertySourcePostProcessor implements BeanDefinitionRegistryPostProcessor, BeanFactoryPostProcessor, EnvironmentAware, Ordered
public abstract class AbstractNacosPropertySourceBuilder<T extends BeanDefinition> implements EnvironmentAware, BeanFactoryAware, BeanClassLoaderAware, ApplicationContextAware, InitializingBean
public class CacheableEventPublishingNacosServiceFactory implements NacosServiceFactory, ApplicationContextAware
public class NacosConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, ApplicationContextAware {
Nacos深入浅出(九)的更多相关文章
- Nacos深入浅出(十)
基本上到第9篇,整个请求的一套就结束了,感觉这里跳跳绕绕很多东西,下面我们来做个总结:从Nacos配置平台修改,到Client请求更新,事件触发去取值返回给客户端,整个过程感觉只分析到了4.5层的深度 ...
- Nacos深入浅出(八)
Nacos-spring-context.java 感觉这个后台要比之前的Nacos复杂多了,涉及到很多基础的概念,慢慢看,这个后面慢慢更新解析过程 看到他的目录结构一个是基于注解,一个是XML的解析 ...
- Nacos深入浅出(七)
大家可以把这个也下载下来,结合之前的Nacos一起来看下,感觉前面几篇看了好像冰山一角的感觉 学无止境! https://github.com/nacos-group/nacos-spring-pro ...
- Nacos深入浅出(六)
其实我们发现在我们本地新生成了文件,这个文件就是nacos; 这个文件怎么那么眼熟,不就是我们的controller中的注解里面的参数value么: @Controller @NacosPropert ...
- Nacos深入浅出(五)
四中标色的代码 result = ConfigService.dump(dataId, group, tenant, cf.getContent(), lastModified); 我们看下这个方法 ...
- Nacos深入浅出(四)
private void executeAsyncInvoke() { while (!queue.isEmpty()) { NotifySingleTask task = queue.poll(); ...
- Nacos深入浅出(二)
如果你的服务已经能正常跑起来,个人建议可以先感受下nacos的魅力,也就是怎么使用吧 直接上代码 @Controller @NacosPropertySource(dataId = "spr ...
- Nacos深入浅出(一)
Nacos代码第一次给我的感觉有点小清新,下面就带大家抽丝剥茧看看源代码,看看阿里大神的东东: 建议大家先把Nacos跑起来,网上有很多教程,最好直接去git里面拉代码,在IDEA里面运行: cons ...
- Nacos(九):Nacos集群部署和遇到的问题
前言 前面的系列文章已经介绍了Nacos的如何接入SpringCloud,以及Nacos的基本使用方式 之前的文章中都是基于单机模式部署进行讲解的,本文对Nacos的集群部署方式进行说明 环境准备 J ...
随机推荐
- python的random模块及加权随机算法的python实现
random是用于生成随机数的,我们可以利用它随机生成数字或者选择字符串. random.seed(x)改变随机数生成器的种子seed. 一般不必特别去设定seed,Python会自动选择seed. ...
- Cloudera运维
1. 增加一个节点 1. 拷贝cm的jar包到该节点 2. 设置hostname(hostnamectl set-hostname XXX),然后修改hosts文件 3. 所有的节点添加该hostna ...
- 洛谷【P2201】数列编辑器
我对模拟的理解:http://www.cnblogs.com/AKMer/p/9064018.html 题目传送门:https://www.luogu.org/problemnew/show/P220 ...
- Centos6.5上的iptables
1.Centos6.5默认开启了iptables 当Centos6.5上安装了MySQL后,在远程连接它,如果出现10060的错误,说明iptables在起作用. 关闭iptables即可,sudo ...
- Poj 2662,2909 Goldbach's Conjecture (素数判定)
一.Description In 1742, Christian Goldbach, a German amateur mathematician, sent a letter to Leonhard ...
- Ubuntu14.04如何用root账号登陆系统
在虚拟机VMWARE中安装完Ubuntu后,只能用新建的普通用户登陆,很不方便做实验:那如何用root用户登陆账号呢? (1)用普通账号登陆,打开终端terminal: (2)在terminal的输入 ...
- 使用python对文件夹里面所有代码行数进行统计。
统计目录下所有的代码个数和总行数. # -*- coding: utf-8 -*- # @Author : ydf import json import os from pathlib import ...
- 浏览器怎么禁用和开启Javascript
转自;http://360.bgu.edu.cn/help/openJsHelp.html IE内核的浏览器禁用和启用Javascript功能都类似,首先我们需要打开IE8浏览器. 之后点击其右上角的 ...
- tomcat solr 限制ip
<Context path="/solr" reloadable="false" docBase="/var/www"> < ...
- Arcane Numbers 1
Vance and Shackler like playing games. One day, they are playing a game called "arcane numbers& ...