Skip to content

Commit 030728d

Browse files
feat:support config empty protection. (#601)
1 parent a442512 commit 030728d

File tree

7 files changed

+377
-23
lines changed

7 files changed

+377
-23
lines changed

polaris-common/polaris-config/src/main/java/com/tencent/polaris/api/config/configuration/ConnectorConfig.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,18 @@ public interface ConnectorConfig extends ServerConnectorConfig {
3333
* @return 连接器类型
3434
*/
3535
String getConnectorType();
36+
37+
/**
38+
* 是否开启推空保护
39+
*
40+
* @return 是否开启推空保护
41+
*/
42+
Boolean isEmptyProtectionEnable();
43+
44+
/**
45+
* 推空保护过期时间,单位毫秒
46+
*
47+
* @return 推空保护过期时间
48+
*/
49+
Long getEmptyProtectionExpiredInterval();
3650
}

polaris-common/polaris-config/src/main/java/com/tencent/polaris/factory/config/configuration/ConnectorConfigImpl.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ public class ConnectorConfigImpl extends ServerConnectorConfigImpl implements Co
6161
@JsonProperty
6262
private Integer configFileGroupThreadNum = 10;
6363

64+
@JsonProperty
65+
private Boolean emptyProtectionEnable = true;
66+
67+
@JsonProperty
68+
@JsonDeserialize(using = TimeStrJsonDeserializer.class)
69+
private Long emptyProtectionExpiredInterval = 7 * 24 * 3600 * 1000L;
70+
6471
@Override
6572
public void verify() {
6673
ConfigUtils.validateString(connectorType, "configConnectorType");
@@ -91,6 +98,12 @@ public void setDefault(Object defaultObject) {
9198
if (connectorType == null) {
9299
this.connectorType = connectorConfig.getConnectorType();
93100
}
101+
if (emptyProtectionEnable == null) {
102+
this.emptyProtectionEnable = connectorConfig.isEmptyProtectionEnable();
103+
}
104+
if (emptyProtectionExpiredInterval == null) {
105+
this.emptyProtectionExpiredInterval = connectorConfig.getEmptyProtectionExpiredInterval();
106+
}
94107
}
95108
}
96109

@@ -159,4 +172,21 @@ public void setConfigFileGroupThreadNum(Integer configFileGroupThreadNum) {
159172
this.configFileGroupThreadNum = configFileGroupThreadNum;
160173
}
161174

175+
@Override
176+
public Boolean isEmptyProtectionEnable() {
177+
return emptyProtectionEnable;
178+
}
179+
180+
public void setEmptyProtectionEnable(Boolean emptyProtectionEnable) {
181+
this.emptyProtectionEnable = emptyProtectionEnable;
182+
}
183+
184+
@Override
185+
public Long getEmptyProtectionExpiredInterval() {
186+
return emptyProtectionExpiredInterval;
187+
}
188+
189+
public void setEmptyProtectionExpiredInterval(Long emptyProtectionExpiredInterval) {
190+
this.emptyProtectionExpiredInterval = emptyProtectionExpiredInterval;
191+
}
162192
}

polaris-configuration/polaris-configuration-client/src/main/java/com/tencent/polaris/configuration/client/internal/RemoteConfigFileRepo.java

Lines changed: 113 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package com.tencent.polaris.configuration.client.internal;
1919

20+
import com.tencent.polaris.annonation.JustForTest;
2021
import com.tencent.polaris.api.control.Destroyable;
2122
import com.tencent.polaris.api.exception.ServerCodes;
2223
import com.tencent.polaris.api.plugin.configuration.ConfigFile;
@@ -27,11 +28,11 @@
2728
import com.tencent.polaris.client.api.SDKContext;
2829
import com.tencent.polaris.client.util.NamedThreadFactory;
2930
import 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.*;
3536
import java.util.concurrent.atomic.AtomicLong;
3637
import 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
}

polaris-configuration/polaris-configuration-client/src/main/java/com/tencent/polaris/configuration/client/util/ConfigFileUtils.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@
1818
package com.tencent.polaris.configuration.client.util;
1919

2020
import com.google.common.collect.Maps;
21+
import com.tencent.polaris.api.exception.ServerCodes;
22+
import com.tencent.polaris.api.plugin.configuration.ConfigFile;
23+
import com.tencent.polaris.api.plugin.configuration.ConfigFileResponse;
2124
import com.tencent.polaris.api.utils.StringUtils;
2225
import com.tencent.polaris.configuration.api.core.ConfigFileMetadata;
26+
import com.tencent.polaris.logging.LoggerFactory;
27+
import org.slf4j.Logger;
2328

2429
import java.util.Collections;
2530
import java.util.Map;
@@ -31,6 +36,8 @@
3136
*/
3237
public class ConfigFileUtils {
3338

39+
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigFileUtils.class);
40+
3441
public static void checkConfigFileMetadata(ConfigFileMetadata configFileMetadata) {
3542
if (StringUtils.isBlank(configFileMetadata.getNamespace())) {
3643
throw new IllegalArgumentException("namespace cannot be empty.");
@@ -57,4 +64,25 @@ public static Set<String> stringPropertyNames(Properties properties) {
5764
}
5865
return map.keySet();
5966
}
67+
68+
public static boolean checkConfigContentEmpty(ConfigFileResponse configFileResponse) {
69+
if (configFileResponse == null) {
70+
LOGGER.debug("config file response is null.");
71+
return true;
72+
}
73+
if (configFileResponse.getCode() == ServerCodes.NOT_FOUND_RESOURCE) {
74+
LOGGER.debug("config file not found. maybe not exist or deleted.");
75+
return true;
76+
}
77+
ConfigFile configFile = configFileResponse.getConfigFile();
78+
if (configFile == null) {
79+
LOGGER.debug("config file is null.");
80+
return true;
81+
}
82+
if (StringUtils.isBlank(configFile.getContent())) {
83+
LOGGER.debug("config file content is empty.");
84+
return true;
85+
}
86+
return false;
87+
}
6088
}

0 commit comments

Comments
 (0)