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 2 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 @@ -11,6 +11,7 @@ Apollo 2.4.0
* [Fix: Resolve issues with duplicate comments and blank lines in configuration management](https://github.com/apolloconfig/apollo/pull/5232)
* [Fix link namespace published items show missing some items](https://github.com/apolloconfig/apollo/pull/5240)
* [Feature: Add limit and whitelist for namespace count per appid+cluster](https://github.com/apolloconfig/apollo/pull/5228)
* [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 @@ -223,6 +223,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 @@ -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 All @@ -44,6 +46,7 @@
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
nobodyiam marked this conversation as resolved.
Show resolved Hide resolved
import org.springframework.util.CollectionUtils;

/**
Expand All @@ -64,6 +67,9 @@ public class ConfigServiceWithCache extends AbstractConfigService {
private final ReleaseMessageService releaseMessageService;
private final BizConfig bizConfig;

@Autowired
private MeterRegistry meterRegistry;
youngzil marked this conversation as resolved.
Show resolved Hide resolved

youngzil marked this conversation as resolved.
Show resolved Hide resolved
private LoadingCache<String, ConfigCacheEntry> configCache;

private LoadingCache<Long, Optional<Release>> configIdCache;
Expand All @@ -83,63 +89,8 @@ public ConfigServiceWithCache(final ReleaseService releaseService,

@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 +150,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
13 changes: 12 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,17 @@ 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, see the reference index[Monitoring related-5.2 Metrics](en/design/apollo-design#5.2-Metrics)



Copy link
Member

Choose a reason for hiding this comment

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

Too many empty lines?

### 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 +1627,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.
10 changes: 9 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,14 @@ 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)

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

默认配置是128。
Expand Down Expand Up @@ -1555,4 +1563,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