spring-cloud-alibaba.2.2.x Sentinel持久化整合nacos,nacos無法獲取配置信息bug解決

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配置的對應的PropertiesConfigurationclass;

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配置的對應的PropertiesConfigurationclass;

/**
 * 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、類NacosConfigServiceaddListener方法

/**
 * 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、ClientWorkeraddTenantListeners方法

/**
 * 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

https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章