[ISSUE #8354] Remove Prometheus dependency from nacos-client via Micrometer - #15793
[ISSUE #8354] Remove Prometheus dependency from nacos-client via Micrometer#15793neoLsH wants to merge 7 commits into
Conversation
…ia pluggable metrics SPI nacos-client no longer depends on io.prometheus:simpleclient. Metrics recording is delegated to a NacosClientMetricsProvider loaded via SPI, with a no-op default when no adapter is on the classpath: - add NacosClientMetricsProvider SPI and NoopClientMetricsProvider default - MetricsMonitor becomes a thin static facade with semantic methods, no prometheus types leak into the client API anymore - MetricsHttpAgent records the actual request method label (POST/DELETE were previously recorded as GET) - metrics adapter artifacts (e.g. a prometheus exporter) can implement the SPI via META-INF/services to restore metrics collection Fixes alibaba#8354 Assisted-by: Qoder
|
Thanks for your this PR. 🙏 感谢您提交的PR。 🙏 |
KomachiSion
left a comment
There was a problem hiding this comment.
Thanks for working on making nacos-client lighter. However, I don't think the current SPI-based implementation is complete enough to merge yet.
The main blocker is that this change removes the built-in Prometheus implementation and falls back to NoopClientMetricsProvider, but does not provide a production metrics adapter. Adding io.prometheus:simpleclient back to the application classpath will not restore metrics, because simpleclient does not implement or register NacosClientMetricsProvider. Existing client metrics would therefore silently disappear after upgrading.
I suggest using Micrometer instead of introducing another metrics SPI:
nacos-clientalready has a non-optional dependency onmicrometer-core.FailoverReactoralready records client metrics throughMetrics.globalRegistry.- Therefore, migrating these metrics to Micrometer adds no new base dependency or footprint.
- Without a concrete registry, the global composite registry safely behaves as a no-op.
- Once the application registers a
PrometheusMeterRegistry, subsequent Nacos client metrics are collected automatically.
For Prometheus export, users should add and register either:
micrometer-registry-prometheusfor the new Prometheus client; ormicrometer-registry-prometheus-simpleclientfor the legacy client.
Adding only the underlying simpleclient artifact is not sufficient. If compatibility with plain simpleclient is required, an official bridge/adapter must be delivered together with this change rather than left as a follow-up.
Please also ensure that metrics failures can never affect client requests. In the current implementation, provider loading and recording exceptions may escape, and calls from MetricsHttpAgent are executed in finally, so a provider exception can replace a successful result or mask the original HTTP exception.
The revised implementation should include tests for:
- No registry configured: client behavior remains unaffected.
- A registry added after meters are created: subsequent values are collected.
PrometheusMeterRegistry.scrape()contains the expected metric names, tags, units, and buckets.- Provider/registry failures do not affect business requests.
- GET, POST, and DELETE request method labels are recorded correctly.
Special attention is needed when replacing the existing Prometheus Histogram with a Micrometer Timer or DistributionSummary, because metric names, time units, and bucket definitions may change and break existing dashboards.
|
@neoLsH Would you mind provide your dingtalk number, we would like invite you into nacos contributors group. |
好的,我在钉钉私聊您~ |
… of a custom SPI Follows the review direction on alibaba#15793: drop the NacosClientMetricsProvider SPI and record everything on Metrics.globalRegistry, which nacos-client already depends on (micrometer-core) and already uses in FailoverReactor. - MetricsMonitor now registers meters on the global composite registry, so the metrics are collected as soon as the application adds a concrete registry (for example a PrometheusMeterRegistry) and are a no-op until then - metric names and tags are unchanged: gauge nacos_monitor, counter nacos_client_naming_request_failed_total; the request histogram becomes a Timer, so it is exported as nacos_client_request_seconds_* with the Prometheus default buckets kept as service level objectives - every recording path swallows Throwable, so a broken registry can never affect a client request, including the calls made from the finally block of MetricsHttpAgent - fix the request method label of MetricsHttpAgent: httpPost and httpDelete were both recorded as GET Note on units: the previous implementation passed a millisecond value to a histogram whose buckets are in seconds, so every observation fell into +Inf and the bucket data was unusable. The Timer records the elapsed time in seconds, so the buckets are now meaningful, while _sum changes from milliseconds to seconds. Assisted-by: Qoder
Adapt to the AI watch metrics added by alibaba#15795: MetricsMonitor now exposes semantic increment/decrement/read methods for the agentWatch* gauges and the nacos_client_ai_watch_events_total counter, keeping the in process values readable without a registry so AgentWatchClientMetrics and its tests keep working unchanged.
|
Thanks for the detailed review — you were right on all three points, and I have reworked the PR to use Micrometer instead of the SPI. The branch is also merged with the latest Micrometer instead of a new SPI
Metrics come back automatically once the application registers a concrete registry ( Failures can never affect client requestsEvery recording path in Compatibility, measured on a real scrapeI dumped
So only the request timer is renamed. The Prometheus default bucket boundaries are kept as service level objectives, and There is one thing worth pointing out about the units. The previous code called - record: nacos_client_request_count
expr: nacos_client_request_seconds_count
- record: nacos_client_request_sum
expr: nacos_client_request_seconds_sum * 1000I am happy to switch the timer to a Adapting to #15795
TestsCovering the five cases you asked for:
The method label fix is included as well: Verification: |
…ethod per operation The previous revision added three methods (increment, decrement, read) for every AI watch gauge, which is duplicated code and forces MetricsMonitor to grow by three methods for each new gauge. Introduce ClientGauge, a small handle that owns the in process value, and let MetricsMonitor hand it out. This restores the shape of the original API (`getAgentWatchIntentCountMonitor().inc()`) without leaking any metrics library type, removes nine one line methods, and drops the ConcurrentHashMap and its double checked lookup, since the set of gauges is fixed at compile time. Assisted-by: Qoder
|
Thanks for the update. The code changes look good, and the documentation follow-up does not need to block this PR. After this PR is merged, please submit a documentation PR to the
Please link the documentation PR back here once it is created. |
…of prior tests The static in-process event counts are shared across the whole JVM, so AgentWatchManagerTest recording listener_callback/failed in an earlier test run leaked into the assertion expecting 0.0 and failed CI sporadically. Capture the failed-result baseline the same way the success baseline is already captured.
|
The documentation PR is up: nacos-group/nacos-group.github.io#1147. It adds a "Java SDK Metrics" page to
The page also notes the failure-isolation guarantee you asked about in the first review (recording failures can never affect client requests). Separately: the latest |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…ests The codecov report on this PR flagged the catch branch of recordAgentWatchEvent as uncovered: ThrowingMeterRegistry only probed the timer and the naming failure counter. Extend the counter probe to the result tag and record one ai watch event while the broken registry is installed, asserting that the call is swallowed and the in-process count still increases - that read-back channel is the reason the in-process AtomicLong map exists. The gauge registration catch (registerGauge) stays uncovered on purpose: gauges are registered once during class initialization, as already documented in the test.
|
The last Integration Test run (attempt 2 on 2993630) failed during Derby schema initialization on the self-hosted runner ( I have pushed b20913b, which adds the missing coverage for the The workflows for the new commit are awaiting approval — could you approve them when you have a moment? Thanks. |
nacos-community
left a comment
There was a problem hiding this comment.
This PR removes the mandatory io.prometheus:simpleclient dependency from nacos-client by re-implementing MetricsMonitor as a thin Micrometer facade, so metrics recording is a no-op when no MeterRegistry is on the classpath. The refactor is correct, thread-safe, well-tested (no-registry / late-registry / Prometheus-scrape / failure cases) and preserves Java 8 compatibility for the client module. Two compatibility notes (anchored inline) are worth capturing in the migration guide / release notes before merge; neither blocks the code change.
Suggestions
- Record the Prometheus metric-name change
nacos_client_request->nacos_client_request_seconds_*(Micrometer appends the_secondsunit suffix to timers) in the release notes, since existing dashboards/alerts scraping the old name will break. - Call out the
MetricsMonitorpublic API break (removed Prometheus-typed accessors, private constructor) in the migration guide for anyone extending the client.
Automated review by github-manager-bot
|
|
||
| private static final String NACOS_MONITOR = "nacos_monitor"; | ||
|
|
||
| private static final String NACOS_CLIENT_REQUEST = "nacos_client_request"; |
There was a problem hiding this comment.
The nacos_client_request metric is now recorded as a Micrometer Timer, which the Prometheus registry exports as nacos_client_request_seconds_* (e.g., nacos_client_request_seconds_bucket). The previous Prometheus histogram exported nacos_client_request_* without the _seconds unit suffix, so existing dashboards and alerts that scrape this metric will break after upgrading. Document this as a breaking change in the release notes and consider whether to provide a meter filter or naming workaround to preserve the legacy name.
There was a problem hiding this comment.
This one is intentional — the rename comes together with the unit fix. The old series was comparing millisecond observations against second-based bucket boundaries, so keeping the legacy name would preserve a broken metric. Migration is covered in the doc PR opened alongside this change (nacos-group/nacos-group.github.io#1147), with PromQL and recording-rule examples. A release-notes entry definitely makes sense at merge time.
| * | ||
| * @author Nacos | ||
| */ | ||
| public class MetricsMonitor { |
There was a problem hiding this comment.
MetricsMonitor removes the public Prometheus-typed accessor methods (getServiceInfoMapSizeMonitor, getConfigRequestMonitor, getNamingRequestFailedMonitor, etc.) and makes the constructor private. This is a source and binary compatibility break for any external code that imported this client class. The change is necessary to drop the Prometheus dependency, but it should be clearly called out in the migration guide.
There was a problem hiding this comment.
Intentional as well — the handle-based ClientGauge accessors are the direction requested during the review of this PR; the old one-method-per-operation API was exactly what this refactor removed. The doc PR targets metric users (scrape / queries) rather than Java API migration, but I'm happy to add a short developer-facing note if maintainers want one.
| .tag(TAG_URL, url).tag(TAG_CODE, code) | ||
| .serviceLevelObjectives(REQUEST_DURATION_BUCKETS).register(Metrics.globalRegistry) | ||
| .record(elapsedMillis, TimeUnit.MILLISECONDS); | ||
| } catch (Throwable t) { |
There was a problem hiding this comment.
Recording paths catch and swallow Throwable, including JVM Errors such as OutOfMemoryError. While this guarantees metrics can never affect client requests, it can also mask serious runtime problems. Consider catching Exception instead and letting Errors propagate, or document this deliberate resilience choice explicitly.
There was a problem hiding this comment.
This is the documented resilience choice — see the class javadoc: any metrics failure is swallowed so it can never affect client requests. It's not fully silent though: the first failure logs a warn with the full stack trace, and repeats are suppressed to avoid log storms. Inside this catch there's nothing meaningful we could do for an Error either, and letting it propagate would bring back exactly the coupling this design removes. Narrowing to Exception is fine with me if maintainers prefer that.
| private static void observeRequest(String module, String method, String url, String code, | ||
| long elapsedMillis) { | ||
| try { | ||
| Timer.builder(NACOS_CLIENT_REQUEST).tag(TAG_MODULE, module).tag(TAG_METHOD, method) |
There was a problem hiding this comment.
Every call to observeRequest allocates a new Timer.Builder and performs a MeterRegistry lookup. Because this is on the request hot path, consider caching frequently-used timers (and similarly counters) by their tag tuple if profiling shows allocation pressure.
There was a problem hiding this comment.
Deferred until profiling shows a need — the registry already caches meters by id, so a repeated register returns the existing instance and the per-call cost is just the builder allocation plus the lookup. Client request volume is far below where that becomes measurable. If it ever shows up in a profile, caching timers by tag tuple would be a small follow-up.
The ai watch counter assertions added in b20913b exceeded the line length limit, which failed the spotless check in CI. Reformat them with 'mvn spotless:apply'; no logic change.
|
The I have pushed 14d2d07, which only reformats those lines via The new run is awaiting workflow approval again — thanks in advance. |
nacos-community
left a comment
There was a problem hiding this comment.
Follow-up on the new commit: I compared (previous head) with 14d2d079c — the incremental change is limited to client/src/test/java/com/alibaba/nacos/client/monitor/MetricsMonitorTest.java (+6/−3) and consists solely of line wrapping applied by mvn spotless:apply (no logic change), matching the description above. The CI failure on the previous head was a spotless formatting violation, not a test failure.
My earlier review comments on this PR remain applicable; no new findings from this formatting commit.
Automated review by github-manager-bot
nacos-community
left a comment
There was a problem hiding this comment.
Thanks for the detailed responses, @neoLsH — I verified against 14d2d079c and accept all four explanations:
- Metric rename (
MetricsMonitor.java:61) — Understood: the rename is tied to the unit fix (the old series compared millisecond observations against second-based buckets), and migration is covered by the doc PR nacos-group/nacos-group.github.io#1147. Agreed that a release-notes entry at merge time makes sense. - Timer lookup cost (
MetricsMonitor.java:199) — Deferral accepted. The registry returns the cached meter instance on repeated registration, so the per-call cost is bounded; revisiting via a tag-tuple cache can wait until profiling shows pressure. catch (Throwable)(MetricsMonitor.java:203) — Confirmed this is the documented resilience choice (class javadoc, line 45), with the first failure logged as a warn including the stack trace and repeats suppressed via theMETRICS_FAILURE_LOGGEDguard. Whether to narrow toExceptionis left to the maintainers.- Removed Prometheus-typed accessors (
MetricsMonitor.java:49) — Acknowledged: the handle-basedClientGaugeaccessors are the agreed direction of this refactor. A short developer-facing note is at the maintainers' discretion.
No further objections from my side on these points. Remaining decision items (narrowing the catch to Exception, developer-facing migration note, release-notes entry) are deferred to the maintainers.
Automated review by github-manager-bot
What is the purpose of the change
Fixes #8354: make
nacos-clientlightweight by removing the mandatoryio.prometheus:simpleclientdependency, per the plugin direction discussed in the issue. Supersedes #15212, which has been stalled since 2026-05-28 with failing CI and now conflicts withdevelop(maintainer offered reassignment on 2026-06-26).Brief changelog
io.prometheus:simpleclientfromclient/pom.xml.NacosClientMetricsProviderSPI (gauge / request-observation / failed-request-counter) and aNoopClientMetricsProviderdefault. Implementations are discovered viaNacosServiceLoader(META-INF/services); without an adapter artifact on the classpath, recording is a no-op.MetricsMonitorinto a thin static facade with semantic methods (recordServiceInfoMapSize,recordListenConfigCount,observeConfigRequest,observeNamingRequest,recordNamingRequestFailed); no prometheus types remain in the client.MetricsHttpAgentpreviously recordedhttpPost/httpDeleteunder theGETmethod label; it now records the actual method.enableClientMetricsproperty continues to gate all recording sites.Users who want Prometheus metrics can supply an adapter artifact implementing the SPI (the adapter itself can live in a separate module/repository to keep the SDK lean — happy to contribute it as a follow-up).
Verifying this change
MetricsMonitorTest(8 cases): SPI loading, delegation of all five semantic methods, no-op provider safetyClientWorkerTest,ServiceInfoHolderTestto the new static API;MetricsHttpAgentTestunaffectedclientmodule tests: 1216/1216 pass;apiandclient-basicunaffectedmvn -pl client -am apache-rat:check checkstyle:check spotless:check spotbugs:checkpassesFixes #8354
[ISSUE #123] ...mvn spotless:applyand basic checks (apache-rat:check checkstyle:check spotbugs:check spotless:check) pass