近日業務同學反映在Apollo介面更改設定後, 服務中對應變數的值卻沒有改變
相關設定key定義如下:
@ApolloJsonValue("${apollo.config.map:{}}")
private Map<String, List<String>> apolloConfigMap;
通過遠端debug服務發現,更改apollo設定後,服務中變數的值確實沒有改變。 重啟也不行。
在本地編寫demo,按照如上變數設定方式設定, 多次修改apollo設定後,變數的值都能即時熱更新, 本地復現失敗
AutoUpdateConfigChangeListener#onChange
方法。public void onChange(ConfigChangeEvent changeEvent) {
Set<String> keys = changeEvent.changedKeys();
if (CollectionUtils.isEmpty(keys)) {
return;
}
for (String key : keys) {
// 1. check whether the changed key is relevant
Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
if (targetValues == null || targetValues.isEmpty()) {
continue;
}
// 2. check whether the value is really changed or not (since spring property sources have hierarchies)
if (!shouldTriggerAutoUpdate(changeEvent, key)) {
continue;
}
// 3. update the value
for (SpringValue val : targetValues) {
updateSpringValue(val);
}
}
}
這個方法比較簡單,迴圈變更的key, 第一步校驗變更的key確實是bean中的屬性,第二步校驗確實需要熱更新bean中屬性值,第三步是真正的熱更新。
3. 通過偵錯發現,在第二步時,shouldTriggerAutoUpdate
方法返回了false,導致不會進行熱更新。
4. 我們來看下shouldTriggerAutoUpdate
方法
private boolean shouldTriggerAutoUpdate(ConfigChangeEvent changeEvent, String changedKey) {
ConfigChange configChange = changeEvent.getChange(changedKey);
if (configChange.getChangeType() == PropertyChangeType.DELETED) {
return true;
}
return Objects.equals(environment.getProperty(changedKey), configChange.getNewValue());
}
邏輯比較簡單,返回false的是最後一句, environment中獲取到的屬性值與apollo中設定的新值不一樣。
5. 為什麼會不一樣?
經過偵錯發現 key:apollo.config.map
的值最終是從com.ulisesbocchio.jasyptspringboot.caching.CachingDelegateEncryptablePropertySource
中獲取,而此類中有一個cache, apollo設定變更時,此cache中存的仍是舊設定。此類是jasypt相關包中的類,此包是與加解密相關的。
關鍵程式碼如下:
public Object getProperty(String name) {
// Can be called recursively, so, we cannot use computeIfAbsent.
if (cache.containsKey(name)) {
return cache.get(name);
}
synchronized (name.intern()) {
if (!cache.containsKey(name)) {
Object resolved = getProperty(resolver, filter, delegate, name);
if (resolved != null) {
cache.put(name, resolved);
}
}
return cache.get(name);
}
}
因為Jasypt會封裝Apollo的PropertySource類,快取屬性值,導致設定不能熱更新
我們來看下com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter這個類,這是一個property converter。它的作用即是封裝服務中各種的PropertySource, 當服務查詢設定的值時,如果設定需要解密的話,可以實現解密。而Apollo也會建立一個PropertySource物件, 也會被jasypt包裝,導致設定變更時cache無法更新。
CachingDelegateEncryptablePropertySource類確實有一個refresh方法,可以清空快取,下次再查詢屬性值時,會從真正的PropertySource中獲取。而refresh方法是在com.ulisesbocchio.jasyptspringboot.caching.RefreshScopeRefreshedEventListener#onApplicationEvent方法中被呼叫。可以看出,如果apollo設定變更時傳送事件,jasypt的onApplicationEvent應該可以被觸發,並清空cache。
經過驗證確實可以通過編寫一個Apollo設定變更監聽器,在監聽器中傳送ApplicationEvent事件,達到清空Cache的目的。但是經過驗證,自己定義的監聽器,在AutoUpdateConfigChangeListener#onChange之後執行,還是無法熱更新。
Apollo將AutoUpdateConfigChangeListener監聽器是放在監聽器集合中的第一位的,第一個執行。所以必要要更改的話,需要更改AutoUpdateConfigChangeListener的邏輯,首先傳送事件,然後再執行onChange方法中的第二步。 但Apollo將AutoUpdateConfigChangeListener放一位也是有道理的,設定變更先更新設定,再執行其它監聽器,因為在其它監聽器中也許需要用到熱更新後的值。
解決方法有三種,需要根據使用的場景不同選擇不同的方法
Config apolloConfig = ConfigService.getConfig(<namespace>)
- apolloConfig.getProperty()