mirror of https://github.com/halo-dev/halo
perf: add caching for extension getter to enhance performance (#7102)
#### What type of PR is this? /kind improvement /area core /milestone 2.20.x #### What this PR does / why we need it: 为扩展获取增加缓存以提高网站整体性能 在此之前,每个请求都要经过很多过滤器,而一些过滤器会获取扩展因此导致频繁查询扩展和扩展点定义拖慢了速度 **测试情况** 初始化一个全新环境,安装并启用以下插件和主题 - 已激活主题: [Earth 1.11.0](https://github.com/halo-dev/theme-earth) - 已启动插件: - [SEO 工具集 1.0.1](https://github.com/f2ccloud/plugin-seo-tools) - [OAuth2 认证 1.5.0](https://github.com/halo-sigs/plugin-oauth2) - [Trailing Slash 1.0.0](https://github.com/halo-sigs/plugin-trailing-slash) - [评论组件 2.5.1](https://github.com/halo-dev/plugin-comment-widget) - [KaTeX 2.1.0](https://github.com/halo-sigs/plugin-katex) - [应用市场 1.9.0](https://www.halo.run/store/apps/app-VYJbF) 通过 Apache Benchmark (ab) 进行 1w 次请求并发 100 个,测试访问首页,得到以下测试结果: 核心指标对比 |指标|改进前|改进后|提升情况| |---|---|---|---| |**总耗时 (Time taken)**|27.030 秒|25.718 秒|减少约 **4.9%**| |**每秒请求数 (RPS)**|369.96 req/sec|388.83 req/sec|提升约 **5.1%**| |**单请求平均耗时**|270.298 ms|257.181 ms|减少约 **4.9%**| |**传输速率 (Transfer Rate)**|6346.44 KB/s|6670.12 KB/s|提升约 **5.1%**| 综合分析 - 性能提升主要体现在:请求处理时间(Processing)、等待时间(Waiting)以及每秒请求数(RPS)均有 约5% 左右的提升。 - 传输效率更高:通过更快的处理时间,传输速率提高了 5.1%。 - 长尾请求优化显著:最大响应时间减少了约 14.9%,意味着极端情况下的性能更优。 #### Does this PR introduce a user-facing change? ```release-note 为扩展获取增加缓存使网站整体性能提升 5% 以上 ```pull/7094/head^2
parent
2b4d1ab8d8
commit
eb969122ff
|
@ -0,0 +1,44 @@
|
|||
package run.halo.app.plugin.extensionpoint;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import run.halo.app.extension.Extension;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.controller.Controller;
|
||||
import run.halo.app.extension.controller.ControllerBuilder;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
abstract class AbstractDefinitionGetter<E extends Extension>
|
||||
implements Reconciler<Reconciler.Request>, DisposableBean {
|
||||
|
||||
protected final ConcurrentMap<String, E> cache = new ConcurrentHashMap<>();
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
private final E watchType;
|
||||
|
||||
abstract void putCache(E definition);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Result reconcile(Request request) {
|
||||
client.fetch((Class<E>) watchType.getClass(), request.name())
|
||||
.ifPresent(this::putCache);
|
||||
return Result.doNotRetry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Controller setupWith(ControllerBuilder builder) {
|
||||
return builder.extension(watchType)
|
||||
.syncAllOnStart(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package run.halo.app.plugin.extensionpoint;
|
||||
|
||||
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
@ -11,15 +9,9 @@ import org.pf4j.ExtensionPoint;
|
|||
import org.pf4j.PluginManager;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ListOptions;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.PageRequestImpl;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.router.selector.FieldSelector;
|
||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.SystemSetting.ExtensionPointEnabled;
|
||||
|
||||
|
@ -33,7 +25,9 @@ public class DefaultExtensionGetter implements ExtensionGetter {
|
|||
|
||||
private final BeanFactory beanFactory;
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
private final ExtensionDefinitionGetter extensionDefinitionGetter;
|
||||
|
||||
private final ExtensionPointDefinitionGetter extensionPointDefinitionGetter;
|
||||
|
||||
@Override
|
||||
public <T extends ExtensionPoint> Flux<T> getExtensions(Class<T> extensionPoint) {
|
||||
|
@ -86,9 +80,7 @@ public class DefaultExtensionGetter implements ExtensionGetter {
|
|||
}
|
||||
var extensions = getExtensions(extensionPoint).cache();
|
||||
return Flux.fromIterable(extensionDefNames)
|
||||
.flatMapSequential(extensionDefName ->
|
||||
client.fetch(ExtensionDefinition.class, extensionDefName)
|
||||
)
|
||||
.flatMapSequential(extensionDefinitionGetter::get)
|
||||
.flatMapSequential(extensionDef -> {
|
||||
var className = extensionDef.getSpec().getClassName();
|
||||
return extensions.filter(
|
||||
|
@ -101,15 +93,7 @@ public class DefaultExtensionGetter implements ExtensionGetter {
|
|||
|
||||
private Mono<ExtensionPointDefinition> fetchExtensionPointDefinition(
|
||||
Class<? extends ExtensionPoint> extensionPoint) {
|
||||
var listOptions = new ListOptions();
|
||||
listOptions.setFieldSelector(FieldSelector.of(
|
||||
equal("spec.className", extensionPoint.getName())
|
||||
));
|
||||
var sort = Sort.by("metadata.creationTimestamp", "metadata.name").ascending();
|
||||
return client.listBy(ExtensionPointDefinition.class, listOptions,
|
||||
PageRequestImpl.ofSize(1).withSort(sort)
|
||||
)
|
||||
.flatMap(list -> Mono.justOrEmpty(ListResult.first(list)));
|
||||
return extensionPointDefinitionGetter.getByClassName(extensionPoint.getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package run.halo.app.plugin.extensionpoint;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface ExtensionDefinitionGetter {
|
||||
|
||||
/**
|
||||
* Gets extension definition by extension definition name.
|
||||
*
|
||||
* @param name extension definition name
|
||||
*/
|
||||
Mono<ExtensionDefinition> get(String name);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package run.halo.app.plugin.extensionpoint;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
|
||||
@Component
|
||||
public class ExtensionDefinitionGetterImpl
|
||||
extends AbstractDefinitionGetter<ExtensionDefinition>
|
||||
implements ExtensionDefinitionGetter {
|
||||
|
||||
public ExtensionDefinitionGetterImpl(ExtensionClient client) {
|
||||
super(client, new ExtensionDefinition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ExtensionDefinition> get(String name) {
|
||||
return Mono.fromSupplier(() -> cache.get(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
void putCache(ExtensionDefinition definition) {
|
||||
cache.put(definition.getMetadata().getName(), definition);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package run.halo.app.plugin.extensionpoint;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface ExtensionPointDefinitionGetter {
|
||||
|
||||
/**
|
||||
* Gets extension point definition by extension point class.
|
||||
* <p>Retrieve by filedSelector: <code>spec.className</code></p>
|
||||
*
|
||||
* @param className extension point class name
|
||||
*/
|
||||
Mono<ExtensionPointDefinition> getByClassName(String className);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package run.halo.app.plugin.extensionpoint;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
|
||||
@Component
|
||||
public class ExtensionPointDefinitionGetterImpl
|
||||
extends AbstractDefinitionGetter<ExtensionPointDefinition>
|
||||
implements ExtensionPointDefinitionGetter {
|
||||
|
||||
public ExtensionPointDefinitionGetterImpl(ExtensionClient client) {
|
||||
super(client, new ExtensionPointDefinition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ExtensionPointDefinition> getByClassName(String className) {
|
||||
return Mono.fromSupplier(() -> cache.get(className));
|
||||
}
|
||||
|
||||
@Override
|
||||
void putCache(ExtensionPointDefinition definition) {
|
||||
var className = definition.getSpec().getClassName();
|
||||
cache.put(className, definition);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ package run.halo.app.plugin.extensionpoint;
|
|||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static run.halo.app.infra.SystemSetting.ExtensionPointEnabled.GROUP;
|
||||
|
@ -22,10 +22,7 @@ import org.springframework.beans.factory.ObjectProvider;
|
|||
import org.springframework.core.annotation.Order;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.extension.ListOptions;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.SystemSetting.ExtensionPointEnabled;
|
||||
import run.halo.app.plugin.extensionpoint.ExtensionPointDefinition.ExtensionPointType;
|
||||
|
@ -34,7 +31,10 @@ import run.halo.app.plugin.extensionpoint.ExtensionPointDefinition.ExtensionPoin
|
|||
class DefaultExtensionGetterTest {
|
||||
|
||||
@Mock
|
||||
ReactiveExtensionClient client;
|
||||
ExtensionPointDefinitionGetter extensionPointDefinitionGetter;
|
||||
|
||||
@Mock
|
||||
ExtensionDefinitionGetter extensionDefinitionGetter;
|
||||
|
||||
@Mock
|
||||
PluginManager pluginManager;
|
||||
|
@ -54,15 +54,14 @@ class DefaultExtensionGetterTest {
|
|||
@Test
|
||||
void shouldGetExtensionBySingletonDefinitionWhenExtensionPointEnabledSet() {
|
||||
// prepare extension point definition
|
||||
when(client.listBy(same(ExtensionPointDefinition.class), any(ListOptions.class), any()))
|
||||
.thenReturn(Mono.fromSupplier(() -> {
|
||||
var epd = createExtensionPointDefinition("fake-extension-point",
|
||||
when(extensionPointDefinitionGetter.getByClassName(any()))
|
||||
.thenReturn(Mono.fromSupplier(
|
||||
() -> createExtensionPointDefinition("fake-extension-point",
|
||||
FakeExtensionPoint.class,
|
||||
ExtensionPointType.SINGLETON);
|
||||
return new ListResult<>(List.of(epd));
|
||||
}));
|
||||
ExtensionPointType.SINGLETON))
|
||||
);
|
||||
|
||||
when(client.fetch(ExtensionDefinition.class, "fake-extension"))
|
||||
when(extensionDefinitionGetter.get(eq("fake-extension")))
|
||||
.thenReturn(Mono.fromSupplier(() -> createExtensionDefinition(
|
||||
"fake-extension",
|
||||
FakeExtensionPointImpl.class,
|
||||
|
@ -94,13 +93,12 @@ class DefaultExtensionGetterTest {
|
|||
|
||||
@Test
|
||||
void shouldGetDefaultSingletonDefinitionWhileExtensionPointEnabledNotSet() {
|
||||
when(client.listBy(same(ExtensionPointDefinition.class), any(ListOptions.class), any()))
|
||||
.thenReturn(Mono.fromSupplier(() -> {
|
||||
var epd = createExtensionPointDefinition("fake-extension-point",
|
||||
when(extensionPointDefinitionGetter.getByClassName(any()))
|
||||
.thenReturn(Mono.fromSupplier(
|
||||
() -> createExtensionPointDefinition("fake-extension-point",
|
||||
FakeExtensionPoint.class,
|
||||
ExtensionPointType.SINGLETON);
|
||||
return new ListResult<>(List.of(epd));
|
||||
}));
|
||||
ExtensionPointType.SINGLETON))
|
||||
);
|
||||
|
||||
when(configFetcher.fetch(GROUP, ExtensionPointEnabled.class))
|
||||
.thenReturn(Mono.empty());
|
||||
|
@ -124,21 +122,20 @@ class DefaultExtensionGetterTest {
|
|||
@Test
|
||||
void shouldGetMultiInstanceExtensionWhileExtensionPointEnabledSet() {
|
||||
// prepare extension point definition
|
||||
when(client.listBy(same(ExtensionPointDefinition.class), any(ListOptions.class), any()))
|
||||
.thenReturn(Mono.fromSupplier(() -> {
|
||||
var epd = createExtensionPointDefinition("fake-extension-point",
|
||||
when(extensionPointDefinitionGetter.getByClassName(any()))
|
||||
.thenReturn(Mono.fromSupplier(
|
||||
() -> createExtensionPointDefinition("fake-extension-point",
|
||||
FakeExtensionPoint.class,
|
||||
ExtensionPointType.MULTI_INSTANCE);
|
||||
return new ListResult<>(List.of(epd));
|
||||
}));
|
||||
ExtensionPointType.MULTI_INSTANCE))
|
||||
);
|
||||
|
||||
when(client.fetch(ExtensionDefinition.class, "fake-extension"))
|
||||
when(extensionDefinitionGetter.get(eq("fake-extension")))
|
||||
.thenReturn(Mono.fromSupplier(() -> createExtensionDefinition(
|
||||
"fake-extension",
|
||||
FakeExtensionPointImpl.class,
|
||||
"fake-extension-point")));
|
||||
|
||||
when(client.fetch(ExtensionDefinition.class, "default-fake-extension"))
|
||||
when(extensionDefinitionGetter.get(eq("default-fake-extension")))
|
||||
.thenReturn(Mono.fromSupplier(() -> createExtensionDefinition(
|
||||
"default-fake-extension",
|
||||
FakeExtensionPointDefaultImpl.class,
|
||||
|
@ -177,13 +174,12 @@ class DefaultExtensionGetterTest {
|
|||
@Test
|
||||
void shouldGetMultiInstanceExtensionWhileExtensionPointEnabledNotSet() {
|
||||
// prepare extension point definition
|
||||
when(client.listBy(same(ExtensionPointDefinition.class), any(ListOptions.class), any()))
|
||||
.thenReturn(Mono.fromSupplier(() -> {
|
||||
var epd = createExtensionPointDefinition("fake-extension-point",
|
||||
when(extensionPointDefinitionGetter.getByClassName(any()))
|
||||
.thenReturn(Mono.fromSupplier(
|
||||
() -> createExtensionPointDefinition("fake-extension-point",
|
||||
FakeExtensionPoint.class,
|
||||
ExtensionPointType.MULTI_INSTANCE);
|
||||
return new ListResult<>(List.of(epd));
|
||||
}));
|
||||
ExtensionPointType.MULTI_INSTANCE))
|
||||
);
|
||||
|
||||
when(configFetcher.fetch(GROUP, ExtensionPointEnabled.class))
|
||||
.thenReturn(Mono.empty());
|
||||
|
|
Loading…
Reference in New Issue