This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 6c24d2ed3 fix(server): declare the cache manager so the data source
API works (#4213)
6c24d2ed3 is described below
commit 6c24d2ed32b15fe7222dc7d0fb74ea837bbdb70a
Author: lizhimins <[email protected]>
AuthorDate: Thu Sep 10 17:21:34 2026 +0800
fix(server): declare the cache manager so the data source API works (#4213)
`@EnableCaching` on the application class and `@Cacheable("data-sources")`
on
`SettingsService.listDataSources` arrived together, with no `CacheManager`
declared — the manager
was left to Spring Boot's cache auto-configuration. Whatever that resolved
to in this application
could not serve the `data-sources` cache, so the caching interceptor threw
`IllegalArgumentException: Cannot find cache named 'data-sources'` on every
call and
`GET /api/settings/datasources` answered 500. That broke the settings
data-source tab and put an
`Internal Server Error` alert on the dashboard, whose metrics panel loads
the data source list.
The manager is now declared explicitly, which removes the dependency on
what the
auto-configuration happens to pick. It carries no fixed cache-name list, so
`ConcurrentMapCacheManager` creates caches on first use and a future
`@Cacheable` with a new name
cannot fail the same way.
`SettingsServiceCachingTest` supplies its own manager, which is why it
passed throughout while
production failed on every request. `CacheConfigTest` mirrors the
production wiring instead, and
fails without this configuration.
The rest of the change gives the metrics dashboard something to query,
which the local stack did
not previously have:
- Both brokers export Prometheus metrics on 5557. `metricsPromExporterHost`
must be set to
0.0.0.0 explicitly; it defaults to `brokerIP1`, which leaves the endpoint
unreachable from
outside the container.
- A `prometheus` service scrapes them by container name, so a container
rebuild changing the IP
does not need a config change. Port 9090 binds to the loopback interface
only.
- `.env.example` points `STUDIO_METRICS_PROMETHEUS_BASE_URL` at that
container. It cannot be a
loopback address: `UrlHostGuard` rejects loopback for metrics data
sources, and the query then
fails with "base URL is invalid".
---
deploy/.env.example | 6 +-
deploy/rocketmq/conf/broker-0.conf | 5 ++
deploy/rocketmq/conf/broker-1.conf | 5 ++
deploy/rocketmq/conf/prometheus.yml | 25 ++++++++
deploy/rocketmq/docker-compose.yml | 18 ++++++
.../rocketmq/studio/common/config/CacheConfig.java | 47 +++++++++++++++
.../studio/common/config/CacheConfigTest.java | 67 ++++++++++++++++++++++
7 files changed, 172 insertions(+), 1 deletion(-)
diff --git a/deploy/.env.example b/deploy/.env.example
index 3d7f82d1d..e7085087d 100644
--- a/deploy/.env.example
+++ b/deploy/.env.example
@@ -22,7 +22,11 @@ STUDIO_AUTH_ADMIN_USERNAME=admin
STUDIO_AUTH_ADMIN_PASSWORD=change-me
# Optional Prometheus-compatible data source for the built-in metrics proxy.
-STUDIO_METRICS_PROMETHEUS_BASE_URL=
+# `deploy/rocketmq/docker-compose.yml` ships a `rocketmq-prometheus` container
on the
+# shared `rocketmq_net` network that scrapes both brokers, so the container
name below
+# works as-is once that stack is up. Do not use localhost/127.0.0.1:
UrlHostGuard blocks
+# loopback for metrics data sources and the query fails with "base URL is
invalid".
+STUDIO_METRICS_PROMETHEUS_BASE_URL=http://rocketmq-prometheus:9090
STUDIO_METRICS_PROMETHEUS_USERNAME=
STUDIO_METRICS_PROMETHEUS_PASSWORD=
STUDIO_METRICS_PROMETHEUS_BEARER_TOKEN=
diff --git a/deploy/rocketmq/conf/broker-0.conf
b/deploy/rocketmq/conf/broker-0.conf
index 218ae6b5d..87ef67fbc 100644
--- a/deploy/rocketmq/conf/broker-0.conf
+++ b/deploy/rocketmq/conf/broker-0.conf
@@ -13,3 +13,8 @@ autoCreateSubscriptionGroup=true
# 消息轨迹:开启并自动创建轨迹 topic RMQ_SYS_TRACE_TOPIC
traceOn=true
traceTopicEnable=true
+# Prometheus 指标导出(Studio 监控面板数据源)
+# Host 必须显式设为 0.0.0.0:默认取 brokerIP1,会导致容器外抓取不到 5557
+metricsExporterType=PROM
+metricsPromExporterPort=5557
+metricsPromExporterHost=0.0.0.0
diff --git a/deploy/rocketmq/conf/broker-1.conf
b/deploy/rocketmq/conf/broker-1.conf
index b26d6b72a..293572fa1 100644
--- a/deploy/rocketmq/conf/broker-1.conf
+++ b/deploy/rocketmq/conf/broker-1.conf
@@ -15,3 +15,8 @@ autoCreateSubscriptionGroup=true
# 消息轨迹:开启并自动创建轨迹 topic RMQ_SYS_TRACE_TOPIC
traceOn=true
traceTopicEnable=true
+# Prometheus 指标导出(Studio 监控面板数据源)
+# Host 必须显式设为 0.0.0.0:默认取 brokerIP1,会导致容器外抓取不到 5557
+metricsExporterType=PROM
+metricsPromExporterPort=5557
+metricsPromExporterHost=0.0.0.0
diff --git a/deploy/rocketmq/conf/prometheus.yml
b/deploy/rocketmq/conf/prometheus.yml
new file mode 100644
index 000000000..b4b87372a
--- /dev/null
+++ b/deploy/rocketmq/conf/prometheus.yml
@@ -0,0 +1,25 @@
+# Prometheus 抓取配置 —— RocketMQ Studio 监控面板数据源
+#
+# broker 侧需在 broker-{0,1}.conf 打开 PROM 导出(metricsExporterType=PROM,
+# metricsPromExporterPort=5557,metricsPromExporterHost=0.0.0.0)。
+#
+# 按容器名抓取而非 IP:Prometheus 与 broker 同在 rocketmq_net,容器重建导致 IP
+# 漂移时无需改配置。
+
+global:
+ scrape_interval: 15s
+ evaluation_interval: 15s
+
+scrape_configs:
+ - job_name: rocketmq-broker
+ static_configs:
+ - targets:
+ - rmq-broker-0:5557
+ - rmq-broker-1:5557
+ labels:
+ cluster: rocketmq-studio
+
+ - job_name: prometheus
+ static_configs:
+ - targets:
+ - localhost:9090
diff --git a/deploy/rocketmq/docker-compose.yml
b/deploy/rocketmq/docker-compose.yml
index 8b5e3c32b..3aa48e967 100644
--- a/deploy/rocketmq/docker-compose.yml
+++ b/deploy/rocketmq/docker-compose.yml
@@ -108,6 +108,23 @@ services:
- broker-1
restart: on-failure
+ prometheus:
+ image: prom/prometheus:v2.53.0
+ container_name: rocketmq-prometheus
+ command:
+ - --config.file=/etc/prometheus/prometheus.yml
+ - --storage.tsdb.retention.time=15d
+ volumes:
+ - ./conf/prometheus.yml:/etc/prometheus/prometheus.yml:ro
+ - prometheus-data:/prometheus
+ # 仅绑定回环,避免公网暴露;Studio 走容器名 rocketmq-prometheus:9090 访问
+ ports:
+ - "127.0.0.1:9090:9090"
+ depends_on:
+ - broker-0
+ - broker-1
+ restart: unless-stopped
+
networks:
default:
name: rocketmq_net
@@ -118,3 +135,4 @@ volumes:
broker-0-logs:
broker-1-store:
broker-1-logs:
+ prometheus-data:
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/common/config/CacheConfig.java
b/server/src/main/java/org/apache/rocketmq/studio/common/config/CacheConfig.java
new file mode 100644
index 000000000..3fa534a07
--- /dev/null
+++
b/server/src/main/java/org/apache/rocketmq/studio/common/config/CacheConfig.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.studio.common.config;
+
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Declares the {@link CacheManager} backing the {@code @Cacheable} methods
enabled by
+ * {@code @EnableCaching} on the application class.
+ * <p>
+ * {@code @EnableCaching} on its own leaves the manager to Spring Boot's cache
+ * auto-configuration. In this application that did not yield a manager able
to serve the
+ * {@code data-sources} cache, so every call to {@code
SettingsService.listDataSources} failed with
+ * {@code IllegalArgumentException: Cannot find cache named 'data-sources'}
and surfaced as a 500 on
+ * {@code GET /api/settings/datasources} — breaking both the settings
data-source tab and the
+ * metrics data-source picker on the dashboard. Declaring the manager here
removes the dependency
+ * on whatever the auto-configuration happens to resolve to.
+ * <p>
+ * The manager is deliberately left without a fixed cache-name list: {@code
ConcurrentMapCacheManager}
+ * then creates caches on first use, so adding a {@code @Cacheable} with a new
name cannot
+ * reintroduce the same failure. Caches in use today: {@code data-sources}.
+ */
+@Configuration
+public class CacheConfig {
+
+ @Bean
+ public CacheManager cacheManager() {
+ return new ConcurrentMapCacheManager();
+ }
+}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/common/config/CacheConfigTest.java
b/server/src/test/java/org/apache/rocketmq/studio/common/config/CacheConfigTest.java
new file mode 100644
index 000000000..73902b129
--- /dev/null
+++
b/server/src/test/java/org/apache/rocketmq/studio/common/config/CacheConfigTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.studio.common.config;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.interceptor.CacheInterceptor;
+import
org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Guards the wiring that broke {@code GET /api/settings/datasources} with a
500: caching was
+ * enabled but the resolved {@link CacheManager} could not hand out the {@code
data-sources} cache,
+ * so the {@code @Cacheable} interceptor threw {@code Cannot find cache named
'data-sources'}.
+ * <p>
+ * The pre-existing {@code SettingsServiceCachingTest} supplies its own
manager, which is why it
+ * kept passing while production failed on every request.
+ */
+class CacheConfigTest {
+
+ /** Mirrors the production wiring: {@code @EnableCaching} plus {@link
CacheConfig}. */
+ @EnableCaching
+ static class CachingContext {
+ }
+
+ @Test
+ void resolvesEveryCacheNameUsedByCacheableMethodsTest() {
+ try (AnnotationConfigApplicationContext context = new
AnnotationConfigApplicationContext()) {
+ context.register(CachingContext.class, CacheConfig.class);
+ context.refresh();
+
+ CacheManager cacheManager = context.getBean(CacheManager.class);
+
+ // The name SettingsService.listDataSources caches under.
+ assertThat(cacheManager.getCache("data-sources")).isNotNull();
+ // Names are created on demand, so a newly added @Cacheable cannot
break the same way.
+
assertThat(cacheManager.getCache("any-future-cache-name")).isNotNull();
+ }
+ }
+
+ @Test
+ void enablesTheCachingInterceptorAgainstThatManagerTest() {
+ try (AnnotationConfigApplicationContext context = new
AnnotationConfigApplicationContext()) {
+ context.register(CachingContext.class, CacheConfig.class);
+ context.refresh();
+
+ // Without an interceptor @Cacheable would silently no-op instead
of caching.
+
assertThat(context.getBeansOfType(CacheInterceptor.class)).isNotEmpty();
+ }
+ }
+}