做者: "李權"java
本篇分析一下Nacos同步數據原理web
一、先配置一下環境spring
soul-admin/src/main/resources/application.ymlbootstrap
soul:
sync:
nacos:
url: localhost:8848
namespace: 1c10d748-af86-43b9-8265-75f487d20c6c
# acm:
# enabled: false
# endpoint: acm.aliyun.com
# namespace:
# accessKey:
# secretKey:
複製代碼
soul-admin/pom.xml,這裏默認配置是有的api
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos-client.version}</version>
</dependency>
複製代碼
soul-bootstrap/src/main/resources/application-local.ymlspringboot
soul :
sync:
nacos:
url: localhost:8848
namespace: 1c10d748-af86-43b9-8265-75f487d20c6c
# acm:
# enabled: false
# endpoint: acm.aliyun.com
# namespace:
# accessKey:
# secretKey:
複製代碼
soul-bootstrap/pom.xml,下面的配置默認是沒有的,須要手動添加markdown
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-sync-data-nacos</artifactId>
<version>${project.version}</version>
</dependency>
複製代碼
一、啓動 nacos
二、啓動 soul-admin
三、啓動 soul-bootstrap
複製代碼
二、上面看着挺順利,這個過程遇到了坑,soul-bootstrap 啓動不起來報空指針異常,下面詳細記錄一下。app
首先soul-admin啓動後不會主動向nacos同步網關數據,須要手動同步,官網這一點沒有提到。這個問題絆了我很久,最後是看到了羣裏其餘同窗遇到了一樣的問題,參考了他們的文章才解決,下面記錄一下解決過程。ide
(a)soul-bootstrap 啓動的時候遇到了以下的錯誤,NullPointerException。spring-boot
soul-bootstrap 啓動的時候會去,nacos獲取網關數據,看到下面的斷點,拿到的是空數據。
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-01-25 16:49:06.052 ERROR 5273 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'nacosSyncDataService' defined in class path resource [org/dromara/soul/springboot/starter/sync/data/nacos/NacosSyncDataConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.dromara.soul.sync.data.api.SyncDataService]: Factory method 'nacosSyncDataService' threw exception; nested exception is java.lang.NullPointerException
......
at org.dromara.soul.bootstrap.SoulBootstrapApplication.main(SoulBootstrapApplication.java:37) [classes/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.dromara.soul.sync.data.api.SyncDataService]: Factory method 'nacosSyncDataService' threw exception; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:651) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
... 19 common frames omitted
Caused by: java.lang.NullPointerException: null
at org.dromara.soul.sync.data.nacos.handler.NacosCacheHandler.updateMetaDataMap(NacosCacheHandler.java:128) ~[classes/:na]
at org.dromara.soul.sync.data.nacos.handler.NacosCacheHandler.watcherData(NacosCacheHandler.java:167) ~[classes/:na]
at org.dromara.soul.sync.data.nacos.NacosSyncDataService.start(NacosSyncDataService.java:59) ~[classes/:na]
at org.dromara.soul.sync.data.nacos.NacosSyncDataService.<init>(NacosSyncDataService.java:49) ~[classes/:na]
at org.dromara.soul.springboot.starter.sync.data.nacos.NacosSyncDataConfiguration.nacosSyncDataService(NacosSyncDataConfiguration.java:66) ~[classes/:na]
at org.dromara.soul.springboot.starter.sync.data.nacos.NacosSyncDataConfiguration$$EnhancerBySpringCGLIB$$cce084b7.CGLIB$nacosSyncDataService$0(<generated>) ~[classes/:na]
at org.dromara.soul.springboot.starter.sync.data.nacos.NacosSyncDataConfiguration$$EnhancerBySpringCGLIB$$cce084b7$$FastClassBySpringCGLIB$$3830e886.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.dromara.soul.springboot.starter.sync.data.nacos.NacosSyncDataConfiguration$$EnhancerBySpringCGLIB$$cce084b7.nacosSyncDataService(<generated>) ~[classes/:na]
......
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
... 20 common frames omitted
複製代碼
(b)到nacos去看一下是否有網關的數據,根據配置的 「namespace: 1c10d748-af86-43b9-8265-75f487d20c6c」 結果是什麼都沒有。
三、嘗試去soul-admin手動同步,nacos也看不到數據,必須須要手動建立命名空間「1c10d748-af86-43b9-8265-75f487d20c6c」,以下圖。
四、去soul-admin 手動同步數據後,就看到了nacos上有了網關的配置信息,這時候soul-bootstrap 仍是啓動不起來,由於這裏還缺乏元數據信息。
元數據只有 dubbo、springcloud 服務有數據,http是沒有元數據的,因此還得去啓動一下dubbo服務。而後在soul-admin同步一下元數據。
soul-admin 點擊同步數據,將元數據會同步到nacos
soul-admin 點擊同步數據,將認證數據會同步到nacos
這時候 nacos 已經看到了所有的網關數據
五、再去啓動soul-bootstrap,終於啓動成功
2021-01-25 17:56:54.798 INFO 10051 --- [ main] o.d.s.w.configuration.SoulConfiguration : load plugin:[monitor] [org.dromara.soul.plugin.monitor.MonitorPlugin]
2021-01-25 17:56:54.798 INFO 10051 --- [ main] o.d.s.w.configuration.SoulConfiguration : load plugin:[response] [org.dromara.soul.plugin.httpclient.response.WebClientResponsePlugin]
2021-01-25 17:56:54.990 INFO 10051 --- [ main] d.s.s.s.s.d.n.NacosSyncDataConfiguration : you use nacos sync soul data.......
2021-01-25 17:56:58.890 INFO 10051 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-01-25 17:56:59.758 INFO 10051 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 9195
2021-01-25 17:56:59.764 INFO 10051 --- [ main] o.d.s.b.SoulBootstrapApplication : Started SoulBootstrapApplication in 8.401 seconds (JVM running for 9.95)
複製代碼
六、小結:
配置下來感受使用nacos同步數據不是很友好,配置過程遇到了不少坑,首先soul-admin 不會主動同步網關數據到nacos,須要手動同步。soul-bootstrap必須依賴全部的網關配置數據 soul.plugin、soul.selector、soul.selector、soul.meta、soul.auth,缺一不可。若是網關只代理http服務(無元數據),soul-bootstrap是啓動不起來的。官網這一塊沒有作詳細說明,對小白不是很友好。
咱們知道soul-admin啓動後不會自動向nacos同步數據,須要手動操做。
下面分析一下soul-admin,nacos,soul-bootstrap同步數據的過程。
一、插件信息更新後會發佈一個DataChangedEvent事件
/** * create or update plugin * @param pluginDTO {@linkplain PluginDTO} * @return rows */
@Override
@Transactional(rollbackFor = Exception.class)
public String createOrUpdate(final PluginDTO pluginDTO) {
......
// publish change event.
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, eventType,
Collections.singletonList(PluginTransfer.INSTANCE.mapToData(pluginDO))));
return StringUtils.EMPTY;
}
複製代碼
二、由監聽事件處理類 DataChangedEventDispatcher 負責調用具體的監聽實現類對 DataChangedEvent事件進行處理,這裏的具體實現類是NacosDataChangedListener。
org.dromara.soul.admin.listener.DataChangedEventDispatcher
DataChangedEventDispatcher初始化完成後會執行 afterPropertiesSet(),在容器中獲取全部類型是DataChangedListener.class的bean
@Override
public void afterPropertiesSet() {
Collection<DataChangedListener> listenerBeans = applicationContext.getBeansOfType(DataChangedListener.class).values();
this.listeners = Collections.unmodifiableList(new ArrayList<>(listenerBeans));
}
複製代碼
DataChangedEventDispatcher 監聽到變動事件後,會執行 onApplicationEvent,遍歷全部的監聽類對監聽事件進行處理,這裏是NacosDataChangedListener,以下圖的debug。
@Override
@SuppressWarnings("unchecked")
public void onApplicationEvent(final DataChangedEvent event) {
for (DataChangedListener listener : listeners) {
switch (event.getGroupKey()) {
......
case RULE:
listener.onRuleChanged((List<RuleData>) event.getSource(), event.getEventType());
break;
......
default:
throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
}
}
}
複製代碼
三、NacosDataChangedListener 會執行 onRuleChanged,updateRuleMap 先將網關數據同步至內存,在經過publishConfig同步至nacos。
org.dromara.soul.admin.listener.nacos.NacosDataChangedListener
// 執行監聽事件
@Override
public void onRuleChanged(final List<RuleData> changed, final DataEventTypeEnum eventType) {
updateRuleMap(getConfig(RULE_DATA_ID));
switch (eventType) {
......
default:
changed.forEach(rule -> {
List<RuleData> ls = RULE_MAP
.getOrDefault(rule.getSelectorId(), new ArrayList<>())
.stream()
.filter(s -> !s.getId().equals(rule.getSelectorId()))
.sorted(RULE_DATA_COMPARATOR)
.collect(Collectors.toList());
ls.add(rule);
RULE_MAP.put(rule.getSelectorId(), ls);
});
break;
}
publishConfig(RULE_DATA_ID, RULE_MAP);
}
// 同步至內存
private void updateRuleMap(final String configInfo) {
JsonObject jo = GsonUtils.getInstance().fromJson(configInfo, JsonObject.class);
Set<String> set = new HashSet<>(RULE_MAP.keySet());
......
RULE_MAP.keySet().removeAll(set);
}
// 同步至nacos
@SneakyThrows
private void publishConfig(final String dataId, final Object data) {
configService.publishConfig(dataId, GROUP, GsonUtils.getInstance().toJson(data));
}
複製代碼
四、DataChangedEventDispatcher 、NacosDataChangedListener 類繼承關係
五、小結
一、例如 soul-admin 更新網關數據,發佈一個DataChangedEvent事件,eventPublisher.publishEvent(new DataChangedEvent())
二、DataChangedEventDispatcher --> onApplicationEvent()方法監聽事件到事件,判斷監聽類是 NacosDataChangedListener
三、NacosDataChangedListener --> onRuleChanged()處理事件
四、同步至內存 updateRuleMap(getConfig(RULE_DATA_ID))
五、同步至nacos publishConfig(RULE_DATA_ID, RULE_MAP)
一、soul-bootstrap 添加了nacos依賴 soul-spring-boot-starter-sync-data-nacos,服務啓動後會自動注入NacosSyncDataConfiguration
org.dromara.soul.springboot.starter.sync.data.nacos.NacosSyncDataConfiguration
NacosSyncDataService 負責讀取和同步nacos網關數據
@Configuration
@ConditionalOnClass(NacosSyncDataService.class)
@ConditionalOnProperty(prefix = "soul.sync.nacos", name = "url")
@Slf4j
public class NacosSyncDataConfiguration {
// 注入nacos數據同步服務
@Bean
public SyncDataService nacosSyncDataService(final ObjectProvider<ConfigService> configService, final ObjectProvider<PluginDataSubscriber> pluginSubscriber, final ObjectProvider<List<MetaDataSubscriber>> metaSubscribers, final ObjectProvider<List<AuthDataSubscriber>> authSubscribers) {
log.info("you use nacos sync soul data.......");
return new NacosSyncDataService(configService.getIfAvailable(), pluginSubscriber.getIfAvailable(),
metaSubscribers.getIfAvailable(Collections::emptyList), authSubscribers.getIfAvailable(Collections::emptyList));
}
// 注入nacos客戶端配置服務
@Bean
public ConfigService nacosConfigService(final NacosConfig nacosConfig) throws Exception {
Properties properties = new Properties();
......
return NacosFactory.createConfigService(properties);
}
// 注入nacos配置服務
@Bean
@ConfigurationProperties(prefix = "soul.sync.nacos")
public NacosConfig nacosConfig() {
return new NacosConfig();
}
}
複製代碼
二、org.dromara.soul.sync.data.nacos.NacosSyncDataService
初始化會執行 start
watcherData 負責監聽nacos網關數據
updatePluginMap 同步網關數據到內存
public void start() {
......
watcherData(RULE_DATA_ID, this::updateRuleMap);
......
}
@SneakyThrows
private String getConfigAndSignListener(final String dataId, final Listener listener) {
return configService.getConfigAndSignListener(dataId, GROUP, 6000, listener);
}
protected void watcherData(final String dataId, final OnChange oc) {
Listener listener = new Listener() {
@Override
public void receiveConfigInfo(final String configInfo) {
oc.change(configInfo);
}
......
};
oc.change(getConfigAndSignListener(dataId, listener));
LISTENERS.getOrDefault(dataId, new ArrayList<>()).add(listener);
}
複製代碼
三、NacosSyncDataService 類關係圖
四、小結
一、soul-bootstrap 啓動向容器自動注入 NacosSyncDataConfiguration
二、NacosSyncDataConfiguration 類中會向容器注入 NacosSyncDataService
三、NacosSyncDataService --> start() --> watcherData() 監聽nacos,同步網關數據到內存
四、watcherData() --> updatePluginMap()