Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ConfigService cache record stats #5247

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ Apollo 2.4.0
* [Feature: Add limit and whitelist for namespace count per appid+cluster](https://github.com/apolloconfig/apollo/pull/5228)
* [Feature support the observe status access-key for pre-check and logging only](https://github.com/apolloconfig/apollo/pull/5236)
* [Feature add limit for items count per namespace](https://github.com/apolloconfig/apollo/pull/5227)
* [Feature: Add ConfigService cache record stats function](https://github.com/apolloconfig/apollo/pull/5247)
------------------
All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1)
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ public boolean isConfigServiceCacheEnabled() {
return getBooleanProperty("config-service.cache.enabled", false);
}

public boolean isConfigServiceCacheStatsEnabled() {
return getBooleanProperty("config-service.cache.stats.enabled", false);
}

public boolean isConfigServiceCacheKeyIgnoreCase() {
return getBooleanProperty("config-service.cache.key.ignore-case", false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.ctrip.framework.apollo.configservice.service.config.ConfigServiceWithCache;
import com.ctrip.framework.apollo.configservice.service.config.DefaultConfigService;
import com.ctrip.framework.apollo.configservice.util.AccessKeyUtil;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -47,15 +48,18 @@ public class ConfigServiceAutoConfiguration {
private final ReleaseService releaseService;
private final ReleaseMessageService releaseMessageService;
private final GrayReleaseRuleRepository grayReleaseRuleRepository;
private final MeterRegistry meterRegistry;

public ConfigServiceAutoConfiguration(final BizConfig bizConfig,
final ReleaseService releaseService,
final ReleaseMessageService releaseMessageService,
final GrayReleaseRuleRepository grayReleaseRuleRepository) {
final ReleaseService releaseService,
final ReleaseMessageService releaseMessageService,
final GrayReleaseRuleRepository grayReleaseRuleRepository,
final MeterRegistry meterRegistry) {
this.bizConfig = bizConfig;
this.releaseService = releaseService;
this.releaseMessageService = releaseMessageService;
this.grayReleaseRuleRepository = grayReleaseRuleRepository;
this.meterRegistry = meterRegistry;
}

@Bean
Expand All @@ -67,7 +71,7 @@ public GrayReleaseRulesHolder grayReleaseRulesHolder() {
public ConfigService configService() {
if (bizConfig.isConfigServiceCacheEnabled()) {
return new ConfigServiceWithCache(releaseService, releaseMessageService,
grayReleaseRulesHolder(), bizConfig);
grayReleaseRulesHolder(), bizConfig, meterRegistry);
}
return new DefaultConfigService(releaseService, grayReleaseRulesHolder());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.cache.GuavaCacheMetrics;
import java.util.Optional;

import org.slf4j.Logger;
Expand Down Expand Up @@ -63,6 +65,7 @@ public class ConfigServiceWithCache extends AbstractConfigService {
private final ReleaseService releaseService;
private final ReleaseMessageService releaseMessageService;
private final BizConfig bizConfig;
private final MeterRegistry meterRegistry;

private LoadingCache<String, ConfigCacheEntry> configCache;

Expand All @@ -73,73 +76,20 @@ public class ConfigServiceWithCache extends AbstractConfigService {
public ConfigServiceWithCache(final ReleaseService releaseService,
final ReleaseMessageService releaseMessageService,
final GrayReleaseRulesHolder grayReleaseRulesHolder,
final BizConfig bizConfig) {
final BizConfig bizConfig,
final MeterRegistry meterRegistry) {
super(grayReleaseRulesHolder);
this.releaseService = releaseService;
this.releaseMessageService = releaseMessageService;
this.bizConfig = bizConfig;
this.meterRegistry = meterRegistry;
nullConfigCacheEntry = new ConfigCacheEntry(ConfigConsts.NOTIFICATION_ID_PLACEHOLDER, null);
}

@PostConstruct
void initialize() {
configCache = CacheBuilder.newBuilder()
.expireAfterAccess(DEFAULT_EXPIRED_AFTER_ACCESS_IN_MINUTES, TimeUnit.MINUTES)
.build(new CacheLoader<String, ConfigCacheEntry>() {
@Override
public ConfigCacheEntry load(String key) throws Exception {
List<String> namespaceInfo = ReleaseMessageKeyGenerator.messageToList(key);
if (CollectionUtils.isEmpty(namespaceInfo)) {
Tracer.logError(
new IllegalArgumentException(String.format("Invalid cache load key %s", key)));
return nullConfigCacheEntry;
}

Transaction transaction = Tracer.newTransaction(TRACER_EVENT_CACHE_LOAD, key);
try {
ReleaseMessage latestReleaseMessage = releaseMessageService.findLatestReleaseMessageForMessages(Lists
.newArrayList(key));
Release latestRelease = releaseService.findLatestActiveRelease(namespaceInfo.get(0), namespaceInfo.get(1),
namespaceInfo.get(2));

transaction.setStatus(Transaction.SUCCESS);

long notificationId = latestReleaseMessage == null ? ConfigConsts.NOTIFICATION_ID_PLACEHOLDER : latestReleaseMessage
.getId();

if (notificationId == ConfigConsts.NOTIFICATION_ID_PLACEHOLDER && latestRelease == null) {
return nullConfigCacheEntry;
}

return new ConfigCacheEntry(notificationId, latestRelease);
} catch (Throwable ex) {
transaction.setStatus(ex);
throw ex;
} finally {
transaction.complete();
}
}
});
configIdCache = CacheBuilder.newBuilder()
.expireAfterAccess(DEFAULT_EXPIRED_AFTER_ACCESS_IN_MINUTES, TimeUnit.MINUTES)
.build(new CacheLoader<Long, Optional<Release>>() {
@Override
public Optional<Release> load(Long key) throws Exception {
Transaction transaction = Tracer.newTransaction(TRACER_EVENT_CACHE_LOAD_ID, String.valueOf(key));
try {
Release release = releaseService.findActiveOne(key);

transaction.setStatus(Transaction.SUCCESS);

return Optional.ofNullable(release);
} catch (Throwable ex) {
transaction.setStatus(ex);
throw ex;
} finally {
transaction.complete();
}
}
});
buildConfigCache();
buildConfigIdCache();
}

@Override
Expand Down Expand Up @@ -199,6 +149,86 @@ public void handleMessage(ReleaseMessage message, String channel) {
}
}

private void buildConfigCache() {
CacheBuilder configCacheBuilder = CacheBuilder.newBuilder()
.expireAfterAccess(DEFAULT_EXPIRED_AFTER_ACCESS_IN_MINUTES, TimeUnit.MINUTES);
if (bizConfig.isConfigServiceCacheStatsEnabled()) {
configCacheBuilder.recordStats();
}

configCache = configCacheBuilder.build(new CacheLoader<String, ConfigCacheEntry>() {
@Override
public ConfigCacheEntry load(String key) throws Exception {
List<String> namespaceInfo = ReleaseMessageKeyGenerator.messageToList(key);
if (CollectionUtils.isEmpty(namespaceInfo)) {
Tracer.logError(
new IllegalArgumentException(String.format("Invalid cache load key %s", key)));
return nullConfigCacheEntry;
}

Transaction transaction = Tracer.newTransaction(TRACER_EVENT_CACHE_LOAD, key);
try {
ReleaseMessage latestReleaseMessage = releaseMessageService.findLatestReleaseMessageForMessages(Lists
.newArrayList(key));
Release latestRelease = releaseService.findLatestActiveRelease(namespaceInfo.get(0), namespaceInfo.get(1),
namespaceInfo.get(2));

transaction.setStatus(Transaction.SUCCESS);

long notificationId = latestReleaseMessage == null ? ConfigConsts.NOTIFICATION_ID_PLACEHOLDER : latestReleaseMessage
.getId();

if (notificationId == ConfigConsts.NOTIFICATION_ID_PLACEHOLDER && latestRelease == null) {
return nullConfigCacheEntry;
}

return new ConfigCacheEntry(notificationId, latestRelease);
} catch (Throwable ex) {
transaction.setStatus(ex);
throw ex;
} finally {
transaction.complete();
}
}
});

if (bizConfig.isConfigServiceCacheStatsEnabled()) {
GuavaCacheMetrics.monitor(meterRegistry, configCache, "config_cache");
}

}
Comment on lines +152 to +199
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor to eliminate code duplication between buildConfigCache() and buildConfigIdCache().

Both methods share similar logic for cache construction, statistics recording, and metrics monitoring. Extracting the common code into a generic helper method can improve maintainability and reduce redundancy.

Also applies to: 202-231


private void buildConfigIdCache() {
CacheBuilder configIdCacheBuilder = CacheBuilder.newBuilder()
.expireAfterAccess(DEFAULT_EXPIRED_AFTER_ACCESS_IN_MINUTES, TimeUnit.MINUTES);
if (bizConfig.isConfigServiceCacheStatsEnabled()) {
configIdCacheBuilder.recordStats();
}
configIdCache = configIdCacheBuilder.build(new CacheLoader<Long, Optional<Release>>() {
@Override
public Optional<Release> load(Long key) throws Exception {
Transaction transaction = Tracer.newTransaction(TRACER_EVENT_CACHE_LOAD_ID, String.valueOf(key));
try {
Release release = releaseService.findActiveOne(key);

transaction.setStatus(Transaction.SUCCESS);

return Optional.ofNullable(release);
} catch (Throwable ex) {
transaction.setStatus(ex);
throw ex;
} finally {
transaction.complete();
}
}
});

if (bizConfig.isConfigServiceCacheStatsEnabled()) {
GuavaCacheMetrics.monitor(meterRegistry, configIdCache, "config_id_cache");
}

}

private static class ConfigCacheEntry {
private final long notificationId;
private final Release release;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.ctrip.framework.apollo.biz.utils.ReleaseMessageKeyGenerator;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.google.common.collect.Lists;
import io.micrometer.core.instrument.MeterRegistry;
import java.util.regex.Pattern;
import org.junit.Before;
import org.junit.Test;
Expand Down Expand Up @@ -62,6 +63,8 @@ public class ConfigServiceWithCacheAndCacheKeyIgnoreCaseTest {
@Mock
private BizConfig bizConfig;
@Mock
private MeterRegistry meterRegistry;
@Mock
private GrayReleaseRulesHolder grayReleaseRulesHolder;

private String someAppId;
Expand All @@ -75,7 +78,7 @@ public class ConfigServiceWithCacheAndCacheKeyIgnoreCaseTest {
@Before
public void setUp() throws Exception {
configServiceWithCache = new ConfigServiceWithCache(releaseService, releaseMessageService,
grayReleaseRulesHolder, bizConfig);
grayReleaseRulesHolder, bizConfig, meterRegistry);

when(bizConfig.isConfigServiceCacheKeyIgnoreCase()).thenReturn(true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.biz.utils.ReleaseMessageKeyGenerator;

import io.micrometer.core.instrument.MeterRegistry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -59,6 +60,8 @@ public class ConfigServiceWithCacheTest {
@Mock
private BizConfig bizConfig;
@Mock
private MeterRegistry meterRegistry;
@Mock
private GrayReleaseRulesHolder grayReleaseRulesHolder;

private String someAppId;
Expand All @@ -71,7 +74,7 @@ public class ConfigServiceWithCacheTest {
@Before
public void setUp() throws Exception {
configServiceWithCache = new ConfigServiceWithCache(releaseService, releaseMessageService,
grayReleaseRulesHolder, bizConfig);
grayReleaseRulesHolder, bizConfig, meterRegistry);

configServiceWithCache.initialize();

Expand Down
12 changes: 11 additions & 1 deletion docs/en/deployment/distributed-deployment-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,16 @@ This configuration takes effect when config-service.cache.enabled is set to true

> This configuration is used to be compatible with the configuration acquisition logic when the cache is not enabled, because MySQL database queries are case-insensitive by default. If the cache is enabled and MySQL is used, it is recommended to configure it as true. If the database used by your Apollo is case-sensitive, you must keep the default configuration as false, otherwise the configuration cannot be obtained.


#### 3.2.3.2 config-service.cache.stats.enabled - Whether to enable caching metric statistics function
> For versions 2.4.0 and above

> `config-service.cache.stats.enabled` The adjustment configuration must be restarted config service to take effect.

This configuration works when `config-service.cache.stats.enabled` is true, it is used to control the opening of the cache statistics function.
The default is false, that is, it will not enable the cache statistics function, when it is set to true, it will enable the cache metric statistics function.
View metric reference index[Monitoring related-5.2 Metrics](en/design/apollo-design#5.2-Metrics),such as `http://${someIp:somePort}/prometheus`

### 3.2.4 `item.key.length.limit`- Maximum length limit for configuration item key

The default configuration is 128.
Expand Down Expand Up @@ -1616,4 +1626,4 @@ json
"kl+bj+namespace2+bj": 20
}
```
The above configuration specifies that the retention size for release history of appId=kl, clusterName=bj, namespaceName=namespace1, and branchName=bj is 10, and the retention size for release history of appId=kl, clusterName=bj, namespaceName=namespace2, and branchName=bj is 20. In general, branchName equals clusterName. It is only different during gray release, where the branchName needs to be confirmed by querying the ReleaseHistory table in the database.
The above configuration specifies that the retention size for release history of appId=kl, clusterName=bj, namespaceName=namespace1, and branchName=bj is 10, and the retention size for release history of appId=kl, clusterName=bj, namespaceName=namespace2, and branchName=bj is 20. In general, branchName equals clusterName. It is only different during gray release, where the branchName needs to be confirmed by querying the ReleaseHistory table in the database.
11 changes: 10 additions & 1 deletion docs/zh/deployment/distributed-deployment-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1464,6 +1464,15 @@ http://5.5.5.5:8080/eureka/,http://6.6.6.6:8080/eureka/

> 这个配置用于兼容未开启缓存时的配置获取逻辑,因为 MySQL 数据库查询默认字符串匹配大小写不敏感。如果开启了缓存,且用了 MySQL,建议配置 true。如果你 Apollo 使用的数据库字符串匹配大小写敏感,那么必须保持默认配置 false,否则将获取不到配置。

#### 3.2.3.2 config-service.cache.stats.enabled - 是否开启缓存metric统计功能
> 适用于2.4.0及以上版本

> `config-service.cache.stats.enabled` 配置调整必须重启 config service 才能生效

该配置作用于`config-service.cache.stats.enabled`为 true 时,用于控制开启缓存统计功能。
默认为 false,即不会开启缓存统计功能,当配置为 true 时,开启缓存metric统计功能
指标查看参考[监控相关-5.2 Metrics](zh/design/apollo-design#5.2-Metrics),如`http://${someIp:somePort}/prometheus`

### 3.2.4 item.key.length.limit - 配置项 key 最大长度限制

默认配置是128。
Expand Down Expand Up @@ -1555,4 +1564,4 @@ json
"kl+bj+namespace2+bj": 20
}
```
以上配置指定了 appId=kl、clusterName=bj、namespaceName=namespace1、branchName=bj 的发布历史保留数量为 10,appId=kl、clusterName=bj、namespaceName=namespace2、branchName=bj 的发布历史保留数量为 20,branchName 一般等于 clusterName,只有灰度发布时才会不同,灰度发布的 branchName 需要查询数据库 ReleaseHistory 表确认。
以上配置指定了 appId=kl、clusterName=bj、namespaceName=namespace1、branchName=bj 的发布历史保留数量为 10,appId=kl、clusterName=bj、namespaceName=namespace2、branchName=bj 的发布历史保留数量为 20,branchName 一般等于 clusterName,只有灰度发布时才会不同,灰度发布的 branchName 需要查询数据库 ReleaseHistory 表确认。
Loading