diff --git a/CHANGES.md b/CHANGES.md index 167134551db..f72dce3c0f6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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) diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java index 37fe1ed0c6f..0ab8beb38be 100644 --- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java +++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java @@ -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); } diff --git a/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/ConfigServiceAutoConfiguration.java b/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/ConfigServiceAutoConfiguration.java index a3229cafbd7..76e90709244 100644 --- a/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/ConfigServiceAutoConfiguration.java +++ b/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/ConfigServiceAutoConfiguration.java @@ -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; @@ -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 @@ -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()); } diff --git a/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCache.java b/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCache.java index 2ddbb7f175f..00ca866b0ba 100644 --- a/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCache.java +++ b/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCache.java @@ -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; @@ -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 configCache; @@ -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() { - @Override - public ConfigCacheEntry load(String key) throws Exception { - List 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>() { - @Override - public Optional 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 @@ -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() { + @Override + public ConfigCacheEntry load(String key) throws Exception { + List 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>() { + @Override + public Optional 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; diff --git a/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCacheAndCacheKeyIgnoreCaseTest.java b/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCacheAndCacheKeyIgnoreCaseTest.java index 63c5d5c822e..f7298186ee3 100644 --- a/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCacheAndCacheKeyIgnoreCaseTest.java +++ b/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCacheAndCacheKeyIgnoreCaseTest.java @@ -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; @@ -62,6 +63,8 @@ public class ConfigServiceWithCacheAndCacheKeyIgnoreCaseTest { @Mock private BizConfig bizConfig; @Mock + private MeterRegistry meterRegistry; + @Mock private GrayReleaseRulesHolder grayReleaseRulesHolder; private String someAppId; @@ -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); diff --git a/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCacheTest.java b/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCacheTest.java index 3a6f0be93bb..0909d47cd47 100644 --- a/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCacheTest.java +++ b/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCacheTest.java @@ -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; @@ -59,6 +60,8 @@ public class ConfigServiceWithCacheTest { @Mock private BizConfig bizConfig; @Mock + private MeterRegistry meterRegistry; + @Mock private GrayReleaseRulesHolder grayReleaseRulesHolder; private String someAppId; @@ -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(); diff --git a/docs/en/deployment/distributed-deployment-guide.md b/docs/en/deployment/distributed-deployment-guide.md index 4dbe8dd7586..ff820d62869 100644 --- a/docs/en/deployment/distributed-deployment-guide.md +++ b/docs/en/deployment/distributed-deployment-guide.md @@ -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. @@ -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. \ No newline at end of file +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. diff --git a/docs/zh/deployment/distributed-deployment-guide.md b/docs/zh/deployment/distributed-deployment-guide.md index f83d62ac692..85779395a0c 100644 --- a/docs/zh/deployment/distributed-deployment-guide.md +++ b/docs/zh/deployment/distributed-deployment-guide.md @@ -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。 @@ -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 表确认。 \ No newline at end of file +以上配置指定了 appId=kl、clusterName=bj、namespaceName=namespace1、branchName=bj 的发布历史保留数量为 10,appId=kl、clusterName=bj、namespaceName=namespace2、branchName=bj 的发布历史保留数量为 20,branchName 一般等于 clusterName,只有灰度发布时才会不同,灰度发布的 branchName 需要查询数据库 ReleaseHistory 表确认。