Skip to content

Commit

Permalink
feat: ConfigService cache record stats (#5247)
Browse files Browse the repository at this point in the history
* feat: ConfigService cache record stats

* fix:add CHANGES.md

* fix:using constructor injection for MeterRegistry

* fix:using constructor injection for MeterRegistry

* fix:Remove useless imports
  • Loading branch information
youngzil authored Oct 18, 2024
1 parent 4bdf66a commit ecbf20c
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 66 deletions.
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");
}

}

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 表确认。

0 comments on commit ecbf20c

Please sign in to comment.