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