1717
1818package com .tencent .polaris .configuration .client .internal ;
1919
20+ import com .tencent .polaris .annonation .JustForTest ;
2021import com .tencent .polaris .api .control .Destroyable ;
2122import com .tencent .polaris .api .exception .ServerCodes ;
2223import com .tencent .polaris .api .plugin .configuration .ConfigFile ;
2728import com .tencent .polaris .client .api .SDKContext ;
2829import com .tencent .polaris .client .util .NamedThreadFactory ;
2930import com .tencent .polaris .configuration .api .core .ConfigFileMetadata ;
31+ import com .tencent .polaris .configuration .client .util .ConfigFileUtils ;
3032
31- import java .util .concurrent .ExecutorService ;
32- import java .util .concurrent .Executors ;
33- import java .util .concurrent .ScheduledExecutorService ;
34- import java .util .concurrent .TimeUnit ;
33+ import java .util .HashSet ;
34+ import java .util .Set ;
35+ import java .util .concurrent .*;
3536import java .util .concurrent .atomic .AtomicLong ;
3637import java .util .concurrent .atomic .AtomicReference ;
3738
@@ -47,6 +48,8 @@ public class RemoteConfigFileRepo extends AbstractConfigFileRepo {
4748
4849 private static ScheduledExecutorService pullExecutorService ;
4950
51+ private static Set <String > configFileInitSet = new HashSet <>();
52+
5053 private final AtomicReference <ConfigFile > remoteConfigFile ;
5154 //服务端通知的版本号,此版本号有可能落后于服务端
5255 private final AtomicLong notifiedVersion ;
@@ -58,6 +61,17 @@ public class RemoteConfigFileRepo extends AbstractConfigFileRepo {
5861
5962 private String token ;
6063
64+ private final boolean emptyProtection ;
65+
66+ private final long emptyProtectionExpiredInterval ;
67+
68+ /**
69+ * 淘汰线程
70+ */
71+ private final ScheduledExecutorService emptyProtectionExpireExecutor ;
72+
73+ private ScheduledFuture <?> emptyProtectionExpireFuture ;
74+
6175 static {
6276 createPullExecutorService ();
6377 }
@@ -80,6 +94,9 @@ public RemoteConfigFileRepo(SDKContext sdkContext,
8094 //获取远程调用插件实现类
8195 this .configFileConnector = connector ;
8296 this .fallbackToLocalCache = sdkContext .getConfig ().getConfigFile ().getServerConnector ().getFallbackToLocalCache ();
97+ this .emptyProtection = sdkContext .getConfig ().getConfigFile ().getServerConnector ().isEmptyProtectionEnable ();
98+ this .emptyProtectionExpiredInterval = sdkContext .getConfig ().getConfigFile ().getServerConnector ().getEmptyProtectionExpiredInterval ();
99+ this .emptyProtectionExpireExecutor = Executors .newSingleThreadScheduledExecutor (new NamedThreadFactory ("polaris-config-empty-protection" ));
83100 //注册 destroy hook
84101 registerRepoDestroyHook (sdkContext );
85102 //同步从远程仓库拉取一次
@@ -163,14 +180,24 @@ protected void doPull() {
163180 } else {
164181 shouldUpdateLocalCache = remoteConfigFile .get () == null || pulledConfigFile .getVersion () != remoteConfigFile .get ().getVersion ();
165182 }
166- if (shouldUpdateLocalCache ) {
183+ // 构造更新回调动作
184+ Runnable runnable = () -> {
167185 ConfigFile copiedConfigFile = deepCloneConfigFile (pulledConfigFile );
168186 remoteConfigFile .set (copiedConfigFile );
169187 //配置有更新,触发回调
170188 fireChangeEvent (copiedConfigFile );
171189
172190 // update local file cache
173191 this .configFilePersistHandler .asyncSaveConfigFile (pulledConfigFile );
192+ };
193+ if (shouldUpdateLocalCache && checkEmptyProtect (response )) {
194+ shouldUpdateLocalCache = false ;
195+ fallbackIfNecessaryWhenStartingUp (pullConfigFileReq );
196+ submitEmptyProtectionExpireTask (runnable );
197+ }
198+ if (shouldUpdateLocalCache ) {
199+ runnable .run ();
200+ cancelEmptyProtectionExpireTask ();
174201 }
175202 return ;
176203 }
@@ -179,16 +206,26 @@ protected void doPull() {
179206 if (response .getCode () == ServerCodes .NOT_FOUND_RESOURCE ) {
180207 LOGGER .warn ("[Config] config file not found, please check whether config file released. {}" ,
181208 configFileMetadata );
182- //delete local file cache
183- this .configFilePersistHandler
184- .asyncDeleteConfigFile (new ConfigFile (configFileMetadata .getNamespace (),
185- configFileMetadata .getFileGroup (), configFileMetadata .getFileName ()));
186-
187- //删除配置文件
188- if (remoteConfigFile .get () != null ) {
189- remoteConfigFile .set (null );
190- //删除配置文件也需要触发通知
191- fireChangeEvent (null );
209+ // 构造更新回调动作
210+ Runnable runnable = () -> {
211+ //delete local file cache
212+ this .configFilePersistHandler
213+ .asyncDeleteConfigFile (new ConfigFile (configFileMetadata .getNamespace (),
214+ configFileMetadata .getFileGroup (), configFileMetadata .getFileName ()));
215+
216+ //删除配置文件
217+ if (remoteConfigFile .get () != null ) {
218+ remoteConfigFile .set (null );
219+ //删除配置文件也需要触发通知
220+ fireChangeEvent (null );
221+ }
222+ };
223+ if (checkEmptyProtect (response )) {
224+ fallbackIfNecessaryWhenStartingUp (pullConfigFileReq );
225+ submitEmptyProtectionExpireTask (runnable );
226+ } else {
227+ runnable .run ();
228+ cancelEmptyProtectionExpireTask ();
192229 }
193230 return ;
194231 }
@@ -213,16 +250,39 @@ protected void doPull() {
213250 }
214251
215252 private void fallbackIfNecessary (final int retryTimes , ConfigFile configFileReq ) {
216- if (retryTimes >= PULL_CONFIG_RETRY_TIMES && fallbackToLocalCache ) {
253+ if (retryTimes >= PULL_CONFIG_RETRY_TIMES ) {
254+ LOGGER .info ("[Config] failed to pull config file from remote." );
255+ //重试次数超过上限,从本地缓存拉取
256+ loadLocalCache (configFileReq );
257+ }
258+ }
259+
260+ private void fallbackIfNecessaryWhenStartingUp (ConfigFile configFileReq ) {
261+ String identifier = getIdentifier ();
262+ boolean initFlag = false ;
263+ if (configFileInitSet .contains (identifier )) {
264+ initFlag = true ;
265+ } else {
266+ configFileInitSet .add (identifier );
267+ }
268+ if (!initFlag ) {
269+ // 第一次启动的时候,如果拉取到空配置,则尝试从缓存中获取
270+ LOGGER .info ("[Config] load local cache because of empty config when starting up." );
271+ loadLocalCache (configFileReq );
272+ }
273+ }
274+
275+ private void loadLocalCache (ConfigFile configFileReq ) {
276+ if (fallbackToLocalCache ) {
217277 ConfigFile configFileRes = configFilePersistHandler .loadPersistedConfigFile (configFileReq );
218278 if (configFileRes != null ) {
219- LOGGER .info ("[Config] failed to pull config file from remote,fallback to local cache success.{}." , configFileRes );
279+ LOGGER .info ("[Config] load local cache success.{}." , configFileRes );
220280 remoteConfigFile .set (configFileRes );
221281 //配置有更新,触发回调
222282 fireChangeEvent (configFileRes );
223283 return ;
224284 }
225- LOGGER .info ("[Config] failed to pull config file from remote,fallback to local cache fail.{}." , configFileReq );
285+ LOGGER .info ("[Config] load local cache fail.{}." , configFileReq );
226286 }
227287 }
228288
@@ -293,4 +353,39 @@ protected void doDestroy() {
293353 static void destroyPullExecutor () {
294354 ThreadPoolUtils .waitAndStopThreadPools (new ExecutorService []{pullExecutorService });
295355 }
356+
357+ /**
358+ * 若配置为空,则推空保护开启,则不刷新配置
359+ *
360+ * @param configFileResponse
361+ * @return
362+ */
363+ private boolean checkEmptyProtect (ConfigFileResponse configFileResponse ) {
364+ if (emptyProtection && ConfigFileUtils .checkConfigContentEmpty (configFileResponse )) {
365+ LOGGER .warn ("Empty response from remote with {}, will not refresh config." , getIdentifier ());
366+ return true ;
367+ }
368+ return false ;
369+ }
370+
371+ private void submitEmptyProtectionExpireTask (Runnable runnable ) {
372+ if (emptyProtectionExpireFuture == null || emptyProtectionExpireFuture .isCancelled () || emptyProtectionExpireFuture .isDone ()) {
373+ LOGGER .info ("Empty protection expire task of {} submit." , getIdentifier ());
374+ emptyProtectionExpireFuture = emptyProtectionExpireExecutor .schedule (runnable , emptyProtectionExpiredInterval , TimeUnit .MILLISECONDS );
375+ }
376+ }
377+
378+ private void cancelEmptyProtectionExpireTask () {
379+ if (emptyProtectionExpireFuture != null && !emptyProtectionExpireFuture .isCancelled () && !emptyProtectionExpireFuture .isDone ()) {
380+ emptyProtectionExpireFuture .cancel (true );
381+ LOGGER .info ("Empty protection expire task of {} cancel." , getIdentifier ());
382+ }
383+ }
384+
385+ @ JustForTest
386+ String getIdentifier () {
387+ return configFileMetadata .getNamespace () + "."
388+ + configFileMetadata .getFileGroup () + "."
389+ + configFileMetadata .getFileName ();
390+ }
296391}
0 commit comments