From 2b4d1ab8d81ea1ce1767da526fec4144d40d4265 Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:31:08 +0800 Subject: [PATCH] perf: add caching for system configuration fetcher to enhance performance (#7100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement /area core /milestone 2.20.x #### What this PR does / why we need it: 为系统配置获取增加缓存以提高路由和主题模板渲染的速度 #### Special notes for your reviewer: 1. 系统能正确初始化 2. 测试修改系统配置后 http://localhost:8090/actuator/globalinfo 和主题端 `${site}` 是否都是新的 3. 更改了文章路由规则后能正确调整到新的规则 #### Does this PR introduce a user-facing change? ```release-note 为系统配置的获取增加缓存以提高路由和主题模板渲染的速度 ``` --- .../console/SystemConfigEndpoint.java | 2 +- .../reconciler/SystemSettingReconciler.java | 2 +- .../SystemConfigurableEnvironmentFetcher.java | 105 +++++++++++++----- .../security/preauth/SystemSetupEndpoint.java | 2 +- .../SystemSettingReconcilerTest.java | 9 +- ...temConfigurableEnvironmentFetcherTest.java | 4 +- 6 files changed, 89 insertions(+), 35 deletions(-) diff --git a/application/src/main/java/run/halo/app/core/endpoint/console/SystemConfigEndpoint.java b/application/src/main/java/run/halo/app/core/endpoint/console/SystemConfigEndpoint.java index de6bd4fb0..810c23411 100644 --- a/application/src/main/java/run/halo/app/core/endpoint/console/SystemConfigEndpoint.java +++ b/application/src/main/java/run/halo/app/core/endpoint/console/SystemConfigEndpoint.java @@ -76,7 +76,7 @@ public class SystemConfigEndpoint implements CustomEndpoint { private Mono updateConfigByGroup(ServerRequest request) { final var group = request.pathVariable("group"); return request.bodyToMono(ObjectNode.class) - .flatMap(objectNode -> configurableEnvironmentFetcher.getConfigMap() + .flatMap(objectNode -> configurableEnvironmentFetcher.loadConfigMap() .flatMap(configMap -> { var data = configMap.getData(); data.put(group, JsonUtils.objectToJson(objectNode)); diff --git a/application/src/main/java/run/halo/app/core/reconciler/SystemSettingReconciler.java b/application/src/main/java/run/halo/app/core/reconciler/SystemSettingReconciler.java index ea920d839..1e841a62d 100644 --- a/application/src/main/java/run/halo/app/core/reconciler/SystemSettingReconciler.java +++ b/application/src/main/java/run/halo/app/core/reconciler/SystemSettingReconciler.java @@ -295,6 +295,6 @@ public class SystemSettingReconciler implements Reconciler { } private Optional getConfigMap(String name) { - return environmentFetcher.getConfigMapBlocking(); + return environmentFetcher.loadConfigMapBlocking(); } } diff --git a/application/src/main/java/run/halo/app/infra/SystemConfigurableEnvironmentFetcher.java b/application/src/main/java/run/halo/app/infra/SystemConfigurableEnvironmentFetcher.java index 2f3f87ea9..f210ed8b5 100644 --- a/application/src/main/java/run/halo/app/infra/SystemConfigurableEnvironmentFetcher.java +++ b/application/src/main/java/run/halo/app/infra/SystemConfigurableEnvironmentFetcher.java @@ -1,19 +1,28 @@ package run.halo.app.infra; +import static run.halo.app.extension.index.query.QueryFactory.equal; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.github.fge.jsonpatch.JsonPatchException; import com.github.fge.jsonpatch.mergepatch.JsonMergePatch; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.StringUtils; import org.springframework.core.convert.ConversionService; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; import run.halo.app.extension.ConfigMap; +import run.halo.app.extension.ExtensionMatcher; +import run.halo.app.extension.ListOptions; import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.extension.controller.Controller; +import run.halo.app.extension.controller.ControllerBuilder; +import run.halo.app.extension.controller.Reconciler; import run.halo.app.infra.utils.JsonParseException; import run.halo.app.infra.utils.JsonUtils; @@ -27,9 +36,10 @@ import run.halo.app.infra.utils.JsonUtils; * @since 2.0.0 */ @Component -public class SystemConfigurableEnvironmentFetcher { +public class SystemConfigurableEnvironmentFetcher implements Reconciler { private final ReactiveExtensionClient extensionClient; private final ConversionService conversionService; + private final AtomicReference configMapCache = new AtomicReference<>(); public SystemConfigurableEnvironmentFetcher(ReactiveExtensionClient extensionClient, ConversionService conversionService) { @@ -71,31 +81,27 @@ public class SystemConfigurableEnvironmentFetcher { .defaultIfEmpty(Map.of()); } - /** - * Gets config map. - * - * @return a new {@link ConfigMap} named system by json merge patch. - */ public Mono getConfigMap() { - Mono mapMono = - extensionClient.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG_DEFAULT); - if (mapMono == null) { - return Mono.empty(); - } - return mapMono.flatMap(systemDefault -> - extensionClient.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG) - .map(system -> { - Map defaultData = systemDefault.getData(); - Map data = system.getData(); - Map mergedData = mergeData(defaultData, data); - system.setData(mergedData); - return system; - }) - .switchIfEmpty(Mono.just(systemDefault))); + return Mono.fromSupplier(configMapCache::get) + .switchIfEmpty(Mono.defer(this::loadConfigMapInternal)); } - public Optional getConfigMapBlocking() { - return getConfigMap().blockOptional(); + /** + * Load the system config map from the extension client. + * + * @return latest configMap from {@link ReactiveExtensionClient} without any cache. + */ + public Mono loadConfigMap() { + return loadConfigMapInternal(); + } + + /** + * Gets the system config map without any cache. + * + * @return load configMap from {@link ReactiveExtensionClient} + */ + public Optional loadConfigMapBlocking() { + return loadConfigMapInternal().blockOptional(); } private Map mergeData(Map defaultData, @@ -132,7 +138,7 @@ public class SystemConfigurableEnvironmentFetcher { return copiedDefault; } - String mergeRemappingFunction(String dataV, String defaultV) { + private String mergeRemappingFunction(String dataV, String defaultV) { JsonNode dataJsonValue = nullSafeToJsonNode(dataV); // original JsonNode defaultJsonValue = nullSafeToJsonNode(defaultV); @@ -147,8 +153,57 @@ public class SystemConfigurableEnvironmentFetcher { } } - JsonNode nullSafeToJsonNode(String json) { + private JsonNode nullSafeToJsonNode(String json) { return StringUtils.isBlank(json) ? JsonNodeFactory.instance.nullNode() : JsonUtils.jsonToObject(json, JsonNode.class); } + + @Override + public Result reconcile(Request request) { + loadConfigMapInternal() + // should never happen + .switchIfEmpty(Mono.error(new IllegalStateException("System configMap not found."))) + .doOnNext(configMapCache::set) + .block(); + return Result.doNotRetry(); + } + + @Override + public Controller setupWith(ControllerBuilder builder) { + ExtensionMatcher matcher = extension -> Objects.equals(extension.getMetadata().getName(), + SystemSetting.SYSTEM_CONFIG); + return builder + .extension(new ConfigMap()) + .syncAllOnStart(true) + .syncAllListOptions(ListOptions.builder() + .fieldQuery(equal("metadata.name", SystemSetting.SYSTEM_CONFIG)) + .build()) + .onAddMatcher(matcher) + .onUpdateMatcher(matcher) + .onDeleteMatcher(matcher) + .build(); + } + + /** + * Gets config map. + * + * @return a new {@link ConfigMap} named system by json merge patch. + */ + private Mono loadConfigMapInternal() { + Mono mapMono = + extensionClient.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG_DEFAULT); + if (mapMono == null) { + return Mono.empty(); + } + return mapMono.flatMap(systemDefault -> + extensionClient.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG) + .map(system -> { + Map defaultData = systemDefault.getData(); + Map data = system.getData(); + Map mergedData = mergeData(defaultData, data); + system.setData(mergedData); + return system; + }) + .switchIfEmpty(Mono.just(systemDefault))); + } } diff --git a/application/src/main/java/run/halo/app/security/preauth/SystemSetupEndpoint.java b/application/src/main/java/run/halo/app/security/preauth/SystemSetupEndpoint.java index ab4d7718f..62027aada 100644 --- a/application/src/main/java/run/halo/app/security/preauth/SystemSetupEndpoint.java +++ b/application/src/main/java/run/halo/app/security/preauth/SystemSetupEndpoint.java @@ -177,7 +177,7 @@ public class SystemSetupEndpoint { ) .subscribeOn(Schedulers.boundedElastic()); - var basicConfigMono = Mono.defer(() -> systemConfigFetcher.getConfigMap() + var basicConfigMono = Mono.defer(() -> systemConfigFetcher.loadConfigMap() .flatMap(configMap -> { mergeToBasicConfig(body, configMap); return client.update(configMap); diff --git a/application/src/test/java/run/halo/app/core/reconciler/SystemSettingReconcilerTest.java b/application/src/test/java/run/halo/app/core/reconciler/SystemSettingReconcilerTest.java index 24a1518bd..b6f1b6587 100644 --- a/application/src/test/java/run/halo/app/core/reconciler/SystemSettingReconcilerTest.java +++ b/application/src/test/java/run/halo/app/core/reconciler/SystemSettingReconcilerTest.java @@ -18,7 +18,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.context.ApplicationContext; -import run.halo.app.core.reconciler.SystemSettingReconciler; import run.halo.app.extension.ConfigMap; import run.halo.app.extension.ExtensionClient; import run.halo.app.extension.Metadata; @@ -59,7 +58,7 @@ class SystemSettingReconcilerTest { rules.setArchives("archives-new"); return rules; }); - when(environmentFetcher.getConfigMapBlocking()).thenReturn(Optional.of(configMap)); + when(environmentFetcher.loadConfigMapBlocking()).thenReturn(Optional.of(configMap)); when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG))) .thenReturn(Optional.of(configMap)); systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG)); @@ -83,7 +82,7 @@ class SystemSettingReconcilerTest { rules.setTags("tags-new"); return rules; }); - when(environmentFetcher.getConfigMapBlocking()).thenReturn(Optional.of(configMap)); + when(environmentFetcher.loadConfigMapBlocking()).thenReturn(Optional.of(configMap)); when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG))) .thenReturn(Optional.of(configMap)); systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG)); @@ -104,7 +103,7 @@ class SystemSettingReconcilerTest { rules.setCategories("categories-new"); return rules; }); - when(environmentFetcher.getConfigMapBlocking()).thenReturn(Optional.of(configMap)); + when(environmentFetcher.loadConfigMapBlocking()).thenReturn(Optional.of(configMap)); when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG))) .thenReturn(Optional.of(configMap)); systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG)); @@ -125,7 +124,7 @@ class SystemSettingReconcilerTest { rules.setPost("/post-new/{slug}"); return rules; }); - when(environmentFetcher.getConfigMapBlocking()).thenReturn(Optional.of(configMap)); + when(environmentFetcher.loadConfigMapBlocking()).thenReturn(Optional.of(configMap)); when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG))) .thenReturn(Optional.of(configMap)); systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG)); diff --git a/application/src/test/java/run/halo/app/infra/SystemConfigurableEnvironmentFetcherTest.java b/application/src/test/java/run/halo/app/infra/SystemConfigurableEnvironmentFetcherTest.java index c50784ace..11581108c 100644 --- a/application/src/test/java/run/halo/app/infra/SystemConfigurableEnvironmentFetcherTest.java +++ b/application/src/test/java/run/halo/app/infra/SystemConfigurableEnvironmentFetcherTest.java @@ -44,8 +44,8 @@ class SystemConfigurableEnvironmentFetcherTest { } @Test - void getConfigMap() { - environmentFetcher.getConfigMap() + void loadConfigMap() { + environmentFetcher.loadConfigMap() .as(StepVerifier::create) .consumeNextWith(configMap -> { assertThat(configMap.getMetadata().getName())