-
Notifications
You must be signed in to change notification settings - Fork 8.9k
feature: add metrics support for NamingServer #7882
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
base: 2.x
Are you sure you want to change the base?
feature: add metrics support for NamingServer #7882
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 2.x #7882 +/- ##
============================================
- Coverage 71.11% 71.10% -0.02%
Complexity 797 797
============================================
Files 1300 1300
Lines 49601 49601
Branches 5875 5875
============================================
- Hits 35274 35269 -5
- Misses 11406 11411 +5
Partials 2921 2921 🚀 New features to boost your workflow:
|
funky-eyes
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, I suggest adding monitoring metrics for the response time (avg, p50, p99, p999) and QPS of each interface. You can implement this in the filter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we should abstract an interface for MetricsManager and provide a no-op (empty) implementation. When the Prometheus metrics feature is disabled, we can use this empty implementation, thereby avoiding the need for null checks in other classes that depend on MetricsManager.
| if (mappingObj instanceof Map) { | ||
| Map<String, String> vGroups = (Map<String, String>) mappingObj; | ||
| vGroups.forEach((k, v) -> { | ||
| // In non-raft mode, a unit is one-to-one with a node, and the unitName is stored on the node. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove these commented-out content?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, it just a mistake, i will restore it.
…rver' into add-metrics-support-for-NamingServer
…pNamingMetricsManager and PrometheusNamingMetricsManager
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems we're missing some common tags, such as the host of the current Naming Server. For example, this would make it easier to query metrics from specific Naming Server instances.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds comprehensive Prometheus metrics support to the NamingServer module. The implementation enables monitoring of critical server operations through Micrometer and Spring Boot Actuator.
Key Changes:
- Introduces three core metrics: cluster node count (Gauge), watcher count (Gauge), and cluster change push notifications (Counter)
- Adds configurable metrics support with a no-op implementation for when metrics are disabled
- Integrates metrics collection into existing server operations for real-time monitoring
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
namingserver/pom.xml |
Adds Spring Boot Actuator and Micrometer Prometheus registry dependencies |
namingserver/src/main/resources/application.yml |
Configures metrics endpoint exposure and percentile distribution settings |
namingserver/src/test/resources/application.yml |
Configures test environment metrics settings |
namingserver/src/main/java/org/apache/seata/namingserver/metrics/NamingServerMetricsManager.java |
Defines the metrics manager interface with metric names and tag constants |
namingserver/src/main/java/org/apache/seata/namingserver/metrics/PrometheusNamingMetricsManager.java |
Implements Prometheus metrics collection using MultiGauge and Counter |
namingserver/src/main/java/org/apache/seata/namingserver/metrics/NoOpNamingMetricsManager.java |
Provides no-op implementation when metrics are disabled |
namingserver/src/main/java/org/apache/seata/namingserver/metrics/NamingServerTagsContributor.java |
Adds custom tags to HTTP request metrics for business dimensions |
namingserver/src/main/java/org/apache/seata/namingserver/manager/NamingManager.java |
Integrates cluster node count metrics refresh on instance registration/unregistration |
namingserver/src/main/java/org/apache/seata/namingserver/manager/ClusterWatcherManager.java |
Integrates watcher count metrics and cluster change push counter |
namingserver/src/test/java/org/apache/seata/namingserver/NamingServerMetricsManagerTest.java |
Adds comprehensive unit tests for metrics manager functionality |
namingserver/src/test/java/org/apache/seata/namingserver/ClusterWatcherManagerTest.java |
Updates tests to inject NoOpNamingMetricsManager |
changes/en-us/2.x.md |
Documents the feature addition in English changelog |
changes/zh-cn/2.x.md |
Documents the feature addition in Chinese changelog |
Comments suppressed due to low confidence (1)
namingserver/src/main/java/org/apache/seata/namingserver/manager/ClusterWatcherManager.java:78
- The scheduled task re-registers watchers within its execution which calls registryWatcher, and registryWatcher immediately triggers refreshWatcherCountMetrics. This creates a potential for frequent metrics refreshes every second as the scheduled task runs. This could cause unnecessary overhead when combined with the refresh on line 102.
scheduledThreadPoolExecutor.scheduleAtFixedRate(
() -> {
for (String group : WATCHERS.keySet()) {
Optional.ofNullable(WATCHERS.remove(group))
.ifPresent(watchers -> watchers.parallelStream().forEach(watcher -> {
if (System.currentTimeMillis() >= watcher.getTimeout()) {
notify(watcher, HttpStatus.NOT_MODIFIED.value());
}
if (!watcher.isDone()) {
// Re-register
registryWatcher(watcher);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Add namespace tag | ||
| String namespace = context.getCarrier().getParameter(TAG_NAMESPACE); | ||
| keyValues = keyValues.and(KeyValue.of(TAG_NAMESPACE, namespace != null ? namespace : UNKNOWN)); | ||
|
|
||
| // Add cluster tag | ||
| String cluster = context.getCarrier().getParameter(TAG_CLUSTER); | ||
| if (cluster == null) { | ||
| cluster = context.getCarrier().getParameter("clusterName"); | ||
| } | ||
| keyValues = keyValues.and(KeyValue.of(TAG_CLUSTER, cluster != null ? cluster : UNKNOWN)); | ||
|
|
||
| // Add vgroup tag | ||
| String vgroup = context.getCarrier().getParameter(TAG_VGROUP); | ||
| if (vgroup == null) { | ||
| vgroup = context.getCarrier().getParameter("group"); | ||
| } | ||
| keyValues = keyValues.and(KeyValue.of(TAG_VGROUP, vgroup != null ? vgroup : UNKNOWN)); |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding tags with value "unknown" for every HTTP request will create high cardinality metrics. When these parameters are not present in requests, it would be better to either not add the tag at all, or use a constant shared "unknown" value. This could lead to performance issues and excessive memory usage in the metrics registry as each unique combination of tag values creates a separate time series. Consider only adding tags when they have meaningful values.
| // Immediately refresh watcher count metrics | ||
| metricsManager.refreshWatcherCountMetrics(); |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The metrics are being refreshed on every watcher registration, which could happen very frequently in a high-traffic scenario. Calling refreshWatcherCountMetrics on every single registration could create unnecessary overhead. Consider batching these updates or using a scheduled refresh instead of immediate refresh on every operation.
| // Immediately refresh cluster node count metrics | ||
| metricsManager.refreshClusterNodeCountMetrics(); |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The metrics are being refreshed on every instance registration and unregistration. In high-traffic scenarios with frequent registrations/unregistrations, this could create significant overhead. Consider batching these updates or using a scheduled refresh strategy instead of immediately refreshing on every operation.
| public class NamingServerTagsContributor extends DefaultServerRequestObservationConvention { | ||
|
|
||
| private static final String TAG_NAMESPACE = "namespace"; | ||
| private static final String TAG_CLUSTER = "cluster"; | ||
| private static final String TAG_VGROUP = "vgroup"; | ||
| private static final String UNKNOWN = "unknown"; | ||
|
|
||
| @Override | ||
| public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) { | ||
| KeyValues keyValues = super.getLowCardinalityKeyValues(context); | ||
|
|
||
| // Add namespace tag | ||
| String namespace = context.getCarrier().getParameter(TAG_NAMESPACE); | ||
| keyValues = keyValues.and(KeyValue.of(TAG_NAMESPACE, namespace != null ? namespace : UNKNOWN)); | ||
|
|
||
| // Add cluster tag | ||
| String cluster = context.getCarrier().getParameter(TAG_CLUSTER); | ||
| if (cluster == null) { | ||
| cluster = context.getCarrier().getParameter("clusterName"); | ||
| } | ||
| keyValues = keyValues.and(KeyValue.of(TAG_CLUSTER, cluster != null ? cluster : UNKNOWN)); | ||
|
|
||
| // Add vgroup tag | ||
| String vgroup = context.getCarrier().getParameter(TAG_VGROUP); | ||
| if (vgroup == null) { | ||
| vgroup = context.getCarrier().getParameter("group"); | ||
| } | ||
| keyValues = keyValues.and(KeyValue.of(TAG_VGROUP, vgroup != null ? vgroup : UNKNOWN)); | ||
|
|
||
| return keyValues; | ||
| } | ||
| } No newline at end of file |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The NamingServerTagsContributor class lacks test coverage. This component extends DefaultServerRequestObservationConvention and adds custom tag logic that should be tested to ensure tags are correctly extracted from request parameters and handled when missing.
| include: "prometheus,health,info,metrics" | ||
| metrics: | ||
| tags: | ||
| application: { spring.application.name } |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The syntax used here is incorrect for YAML. The correct syntax to reference another property value in Spring Boot YAML should use ${} without curly braces around the reference, like this: application: ${spring.application.name}
| application: { spring.application.name } | |
| application: ${spring.application.name} |
| include: "prometheus,health,info,metrics" | ||
| metrics: | ||
| tags: | ||
| application: { spring.application.name } |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The syntax used here is incorrect for YAML. The correct syntax to reference another property value in Spring Boot YAML should use ${} without curly braces around the reference, like this: application: ${spring.application.name}
| application: { spring.application.name } | |
| application: ${spring.application.name} |
| // Immediately refresh cluster node count metrics | ||
| metricsManager.refreshClusterNodeCountMetrics(); |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The metrics are being refreshed on every instance registration and unregistration. In high-traffic scenarios with frequent registrations/unregistrations, this could create significant overhead. Consider batching these updates or using a scheduled refresh strategy instead of immediately refreshing on every operation.
| protected final ScheduledExecutorService heartBeatCheckService = | ||
| new ScheduledThreadPoolExecutor(1, new CustomizableThreadFactory("heartBeatCheckExcuter")); | ||
| protected final ScheduledExecutorService heartBeatCheckService = new ScheduledThreadPoolExecutor(1, | ||
| new CustomizableThreadFactory("heartBeatCheckExcuter")); |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thread factory name contains a typo: "heartBeatCheckExcuter" should be spelled "heartBeatCheckExecutor".
| new CustomizableThreadFactory("heartBeatCheckExcuter")); | |
| new CustomizableThreadFactory("heartBeatCheckExecutor")); |
Ⅰ. Describe what this PR did
Add metrics for NamingServer:
Ⅱ. Does this pull request fix one issue?
fixes #7852
Ⅲ. Why don't you add test cases (unit test/integration test)?
Ⅳ. Describe how to verify it
Ⅴ. Special notes for reviews