17
17
18
18
package com .tencent .polaris .configuration .client .internal ;
19
19
20
+ import com .tencent .polaris .annonation .JustForTest ;
20
21
import com .tencent .polaris .api .control .Destroyable ;
21
22
import com .tencent .polaris .api .exception .ServerCodes ;
22
23
import com .tencent .polaris .api .plugin .configuration .ConfigFile ;
27
28
import com .tencent .polaris .client .api .SDKContext ;
28
29
import com .tencent .polaris .client .util .NamedThreadFactory ;
29
30
import com .tencent .polaris .configuration .api .core .ConfigFileMetadata ;
31
+ import com .tencent .polaris .configuration .client .util .ConfigFileUtils ;
30
32
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 .*;
35
36
import java .util .concurrent .atomic .AtomicLong ;
36
37
import java .util .concurrent .atomic .AtomicReference ;
37
38
@@ -47,6 +48,8 @@ public class RemoteConfigFileRepo extends AbstractConfigFileRepo {
47
48
48
49
private static ScheduledExecutorService pullExecutorService ;
49
50
51
+ private static Set <String > configFileInitSet = new HashSet <>();
52
+
50
53
private final AtomicReference <ConfigFile > remoteConfigFile ;
51
54
//服务端通知的版本号,此版本号有可能落后于服务端
52
55
private final AtomicLong notifiedVersion ;
@@ -58,6 +61,17 @@ public class RemoteConfigFileRepo extends AbstractConfigFileRepo {
58
61
59
62
private String token ;
60
63
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
+
61
75
static {
62
76
createPullExecutorService ();
63
77
}
@@ -80,6 +94,9 @@ public RemoteConfigFileRepo(SDKContext sdkContext,
80
94
//获取远程调用插件实现类
81
95
this .configFileConnector = connector ;
82
96
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" ));
83
100
//注册 destroy hook
84
101
registerRepoDestroyHook (sdkContext );
85
102
//同步从远程仓库拉取一次
@@ -163,14 +180,24 @@ protected void doPull() {
163
180
} else {
164
181
shouldUpdateLocalCache = remoteConfigFile .get () == null || pulledConfigFile .getVersion () != remoteConfigFile .get ().getVersion ();
165
182
}
166
- if (shouldUpdateLocalCache ) {
183
+ // 构造更新回调动作
184
+ Runnable runnable = () -> {
167
185
ConfigFile copiedConfigFile = deepCloneConfigFile (pulledConfigFile );
168
186
remoteConfigFile .set (copiedConfigFile );
169
187
//配置有更新,触发回调
170
188
fireChangeEvent (copiedConfigFile );
171
189
172
190
// update local file cache
173
191
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 ();
174
201
}
175
202
return ;
176
203
}
@@ -179,16 +206,26 @@ protected void doPull() {
179
206
if (response .getCode () == ServerCodes .NOT_FOUND_RESOURCE ) {
180
207
LOGGER .warn ("[Config] config file not found, please check whether config file released. {}" ,
181
208
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 ();
192
229
}
193
230
return ;
194
231
}
@@ -213,16 +250,39 @@ protected void doPull() {
213
250
}
214
251
215
252
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 ) {
217
277
ConfigFile configFileRes = configFilePersistHandler .loadPersistedConfigFile (configFileReq );
218
278
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 );
220
280
remoteConfigFile .set (configFileRes );
221
281
//配置有更新,触发回调
222
282
fireChangeEvent (configFileRes );
223
283
return ;
224
284
}
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 );
226
286
}
227
287
}
228
288
@@ -293,4 +353,39 @@ protected void doDestroy() {
293
353
static void destroyPullExecutor () {
294
354
ThreadPoolUtils .waitAndStopThreadPools (new ExecutorService []{pullExecutorService });
295
355
}
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
+ }
296
391
}
0 commit comments