spring-cloud-alibaba.2.2.x Sentinel持久化整合nacos,nacos无法获取配置信息bug解决
文章目录
环境 | 版本 |
---|---|
jdk | 1.8.0_201 |
maven | 3.6.0 |
Spring-boot | 2.2.4.RELEASE |
Spring-cloud-alibaba | 2.2.1.RELEASE |
Spring-cloud | Hoxton.SR2 |
nacos | 1.2.1 |
sentinel-dashboard | 1.7.1 |
构建本项目之前,请详细参看如下步骤,如果已经搭建好,略过即可;
项目地址的码云的git地址https://gitee.com/liqi01/badger-spring-cloud-alibaba.git
《spring-cloud-alibaba.2.2.x 服务注册与发现nacos简介以及环境搭建》
《spring-cloud-alibaba2.2.x 远程调用负载均衡ribbon搭建使用》
《spring-cloud-alibaba2.2.x 远程调用负载均衡openfeign搭建使用》
《spring-cloud-alibaba.2.2.x Sentinel分布式系统的流量防卫兵的简介以及环境搭建》
1、特别注意:大坑以及寻求解决办法的思路;
1.1、发现问题
在上述工程,搭建完成后,
1、nacos_1.2.1 启动完成;
2、sentinel-dashboard-1.7.1 启动完成;
3.应用启动完成;
我怎么样都无法获取到nacos中的配置的流控规则?
我应用yaml文件,配置有问题?
流控规则配置有问题,无法获取?
1.2、解决思路
1、翻看源代码
既然在yml文件中配置了sentinel的datasource属性,那么肯定是要实例化这个datasource,找到对应的yml配置的对应的PropertiesConfiguration
的class
;
com.alibaba.cloud.sentinel.datasource.config.DataSourcePropertiesConfiguration.class
找到对应的datasource
属性的get和set方法;
public Map<String, DataSourcePropertiesConfiguration> getDatasource() {
return datasource;
}
public void setDatasource(Map<String, DataSourcePropertiesConfiguration> datasource) {
this.datasource = datasource;
}
在通过set方法实例化了对应 datasource后,肯定是有地方调用了getDatasource()
方法;
两地地方调用了,
com.alibaba.cloud.sentinel.custom.SentinelDataSourceHandler.class
;
com.alibaba.cloud.sentinel.endpoint.SentinelEndpoint.class
;
先看SentinelDataSourceHandler
类,在afterSingletonsInstantiated
方法中,第一行调用了;这个方法,类似spring的bean的后置处理器,看名字就知道,是在对应实例化时候调用;
大概就是遍历,然后检查一些属性,然后把属性设置到abstractDataSourceProperties,registerBean方法注入这个实例;
@Override
public void afterSingletonsInstantiated() {
sentinelProperties.getDatasource()
.forEach((dataSourceName, dataSourceProperties) -> {
try {
List<String> validFields = dataSourceProperties.getValidField();
if (validFields.size() != 1) {
log.error("[Sentinel Starter] DataSource " + dataSourceName
+ " multi datasource active and won't loaded: "
+ dataSourceProperties.getValidField());
return;
}
AbstractDataSourceProperties abstractDataSourceProperties = dataSourceProperties
.getValidDataSourceProperties();
abstractDataSourceProperties.setEnv(env);
abstractDataSourceProperties.preCheck(dataSourceName);
registerBean(abstractDataSourceProperties, dataSourceName
+ "-sentinel-" + validFields.get(0) + "-datasource");
}
catch (Exception e) {
log.error("[Sentinel Starter] DataSource " + dataSourceName
+ " build error: " + e.getMessage(), e);
}
});
}
debug下,可以看到,已经获取对应的yml的配置,并且把对应的实例,也注入到spring容器中;说明整个配置,容器,都是正常运行,没有什么问题?会不会有什么遗漏的地方?
还是回到yml配置的对应的PropertiesConfiguration
的class
;
/**
* Nacos Properties class Using by {@link DataSourcePropertiesConfiguration} and
* {@link NacosDataSourceFactoryBean}.
*
* @author <a href="mailto:[email protected]">Jim</a>
*/
public class NacosDataSourceProperties extends AbstractDataSourceProperties {
注释上,也提示说明了,可以查看DataSourcePropertiesConfiguration
以及NacosDataSourceFactoryBean
配置类,已经看到过了,主要是初始化配置的,直接查看工厂类;在构造方法中,实例化了;
public NacosDataSourceProperties() {
super(NacosDataSourceFactoryBean.class.getName());
}
查看NacosDataSourceFactoryBean
,实现了spring的FactoryBean接口(不解释),那么直接查看 getObject()方法;
public class NacosDataSourceFactoryBean implements FactoryBean<NacosDataSource> {
@Override
public NacosDataSource getObject() throws Exception {
Properties properties = new Properties();
if (!StringUtils.isEmpty(this.serverAddr)) {
properties.setProperty(PropertyKeyConst.SERVER_ADDR, this.serverAddr);
}
else {
properties.setProperty(PropertyKeyConst.ACCESS_KEY, this.accessKey);
properties.setProperty(PropertyKeyConst.SECRET_KEY, this.secretKey);
properties.setProperty(PropertyKeyConst.ENDPOINT, this.endpoint);
}
if (!StringUtils.isEmpty(this.namespace)) {
properties.setProperty(PropertyKeyConst.NAMESPACE, this.namespace);
}
return new NacosDataSource(properties, groupId, dataId, converter);
}
设置属性,创建一个NacosDataSource对象,参数,就可以看到了,
properties:配置属性,代码中有具体属性;
groupId:yml中的组信息,默认是DEFAULT_GROUP
dataId:yml中的配置信息;
converter:转换器,上面的文档中,也说明了,默认为json解析器;
代码如下
public NacosDataSource(final Properties properties, final String groupId, final String dataId,
Converter<String, T> parser) {
super(parser);
if (StringUtil.isBlank(groupId) || StringUtil.isBlank(dataId)) {
throw new IllegalArgumentException(String.format("Bad argument: groupId=[%s], dataId=[%s]",
groupId, dataId));
}
AssertUtil.notNull(properties, "Nacos properties must not be null, you could put some keys from PropertyKeyConst");
this.groupId = groupId;
this.dataId = dataId;
this.properties = properties;
this.configListener = new Listener() {
@Override
public Executor getExecutor() {
return pool;
}
@Override
public void receiveConfigInfo(final String configInfo) {
RecordLog.info(String.format("[NacosDataSource] New property value received for (properties: %s) (dataId: %s, groupId: %s): %s",
properties, dataId, groupId, configInfo));
T newValue = NacosDataSource.this.parser.convert(configInfo);
// Update the new value to the property.
getProperty().updateValue(newValue);
}
};
initNacosListener();
loadInitialConfig();
}
主要分三部分吧:
1、初始化成员变量属性;
2、初始化Nacos监听器;
private void initNacosListener() {
try {
this.configService = NacosFactory.createConfigService(this.properties);
// Add config listener.
configService.addListener(dataId, groupId, configListener);
} catch (Exception e) {
RecordLog.warn("[NacosDataSource] Error occurred when initializing Nacos data source", e);
e.printStackTrace();
}
}
查看初始化监听器的过程,(部分代码过程,比较简单,只贴调用的部分的方法,不贴全部)
2.1、类NacosConfigService
的addListener
方法
/**
* Config Impl
*
* @author Nacos
*/
@SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule")
public class NacosConfigService implements ConfigService {
@Override
public void addListener(String dataId, String group, Listener listener) throws NacosException {
worker.addTenantListeners(dataId, group, Arrays.asList(listener));
}
2.2、ClientWorker
的addTenantListeners
方法
/**
* Longpolling
*
* @author Nacos
*/
public class ClientWorker {
public void addTenantListeners(String dataId, String group, List<? extends Listener> listeners) throws NacosException {
//初始化group,如果没有,默认设置DEFAULT_GROUP
group = null2defaultGroup(group);
//房客?没有看全局代码,暂时不知道有什么作用
String tenant = agent.getTenant();
//获取缓存数据
CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);
for (Listener listener : listeners) {
cache.addListener(listener);
}
}
public CacheData addCacheDataIfAbsent(String dataId, String group, String tenant) throws NacosException {
CacheData cache = getCache(dataId, group, tenant);
if (null != cache) {
return cache;
}
String key = GroupKey.getKeyTenant(dataId, group, tenant);
synchronized (cacheMap) {
CacheData cacheFromMap = getCache(dataId, group, tenant);
// multiple listeners on the same dataid+group and race condition,so
// double check again
// other listener thread beat me to set to cacheMap
if (null != cacheFromMap) {
cache = cacheFromMap;
// reset so that server not hang this check
cache.setInitializing(true);
} else {
cache = new CacheData(configFilterChainManager, agent.getName(), dataId, group, tenant);
// fix issue # 1317
if (enableRemoteSyncConfig) {
String[] ct = getServerConfig(dataId, group, tenant, 3000L);
cache.setContent(ct[0]);
}
}
Map<String, CacheData> copy = new HashMap<String, CacheData>(cacheMap.get());
copy.put(key, cache);
cacheMap.set(copy);
}
LOGGER.info("[{}] [subscribe] {}", agent.getName(), key);
MetricsMonitor.getListenConfigCountMonitor().set(cacheMap.get().size());
return cache;
}
}
CacheData addCacheDataIfAbsent(String dataId, String group, String tenant)
;创建缓存的过程;
大概意思,就是先看缓存中有没有,没有就创建;直接看这段;
String[] ct = getServerConfig(dataId, group, tenant, 3000L);
getServerConfig
方法(部分代码,整体太长了)
public String[] getServerConfig(String dataId, String group, String tenant, long readTimeout)
throws NacosException {
String[] ct = new String[2];
if (StringUtils.isBlank(group)) {
group = Constants.DEFAULT_GROUP;
}
HttpResult result = null;
try {
List<String> params = null;
//验证http调用的参数
if (StringUtils.isBlank(tenant)) {
params = new ArrayList<String>(Arrays.asList("dataId", dataId, "group", group));
} else {
params = new ArrayList<String>(Arrays.asList("dataId", dataId, "group", group, "tenant", tenant));
}
//http调用
result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
} catch (IOException e) {
String message = String.format(
"[%s] [sub-server] get server config exception, dataId=%s, group=%s, tenant=%s", agent.getName(),
dataId, group, tenant);
LOGGER.error(message, e);
throw new NacosException(NacosException.SERVER_ERROR, e);
}
主要看这段
result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
一个http调用的过程;如果用debug看,可以看到返回的result
的code为404;
Constants.CONFIG_CONTROLLER_PATH
为/v1/cs/configs
;有没有很熟悉?
《spring-cloud-alibaba.2.2.x 服务注册与发现nacos简介以及环境搭建》;
在nacos搭建的时候,官网给的例子;直接在尝试下;
发布配置
curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test&content=HelloWorld"
获取配置
curl -X GET "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test"
这个就尴尬了~原来是nacos的问题,在搭建nacos的时候,nacos的案例,没有生效,可以配置,但是无法获取?
3、加载初始化配置;没有什么好说的,就是加载泛型的类型;
2、发现问题后,解决问题;
1、百度一下,翻看其它的博客,发现并没有什么作用,都是一些,教你怎么搭建的;
2、github官方文档;https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
好像找到了问题所在?jdk版本的问题,我本机的jdk版本;正好是上述的jdk1.8_2.X版本的;
替换jdk版本,再尝试一下;
1、切换jdk版本到1.8.0_181
2、启动nacos
./startup.sh -m standalone
3、在执行nacos的测试案例
curl -X GET "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test"
成功,非常nice;
1.3、总结
中间也查了博客什么的,确实也没有找到类似的问题,大家在搭建过程中,就没有遇到我这样的问题?就只有我的环境是jdk1.8.2的?
翻看源码,并没有什么用;还是多看官方的文档吧;
中间大量篇幅都是在贴代码,都只是部分的代码,还不是全部的;
看源码什么的,确实也比较消耗时间;
github官方文档;
https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel