AlexStocks commented on code in PR #3711:
URL: https://github.com/apache/dubbo-go/pull/3711#discussion_r3885988326
##########
metadata/options.go:
##########
@@ -309,3 +325,12 @@ func WithRegistryId(id string) ReportOption {
opts.registryId = id
}
}
+
+// WithReportDefinition toggles publishing of interface-level service
+// definitions to this report. Definitions are published by default; pass false
+// to opt out. Mirrors Java's MetadataReportConfig.reportDefinition.
+func WithReportDefinition(report bool) ReportOption {
+ return func(opts *ReportOptions) {
+ opts.ReportDefinition = &report
Review Comment:
[P1] report-definition=false 没有进入真实实例初始化路径
这个 option 只把值写进当前 ReportOptions,但公开入口 dubbo.WithMetadataReport(...) 随后只保存内嵌的
MetadataReportConfig,启动时 initMetadataReport -> reportConfigToReportOptions
又重新构造一份 ReportOptions,却没有复制 ReportDefinition。因此无论 YAML 中配置
metadata-report.report-definition: false,还是调用
dubbo.WithMetadataReport(metadata.WithReportDefinition(false)),这里的值最终都会变回
nil,toUrl 不写开关,而发布端按默认 true 继续上报。我用真实转换函数做了负向探针,返回的 opts.ReportDefinition 确认为
nil。请在 reportConfigToReportOptions 中透传这个三态字段,并补一条从 dubbo.NewInstance/公开
Instance option 到最终 report URL 的回归测试。
##########
metadata/service_definition.go:
##########
@@ -0,0 +1,219 @@
+/*
+ * 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 metadata
+
+import (
+ "encoding/json"
+ "sort"
+)
+
+import (
+ "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/metadata/definition"
+ "dubbo.apache.org/dubbo-go/v3/metadata/report"
+)
+
+// PublishServiceDefinitions builds and publishes an interface-level service
+// definition for each exported URL that carries a describable contract, and
+// returns the URLs whose publish failed for a reason worth retrying.
+//
+// No error is returned and nothing here blocks. A provider whose definition
did
+// not reach the metadata center is still a working provider — it is only
+// invisible to Admin's console — so a metadata-center outage must not keep
+// instances out of traffic. Java reaches the same outcome by a different
route:
+// AbstractMetadataReport defaults sync-report to false and hands the write to
an
+// executor, so export never observes the result at all.
+//
+// Only write failures come back. A service that has no describable contract —
+// unregistered, unbuildable, or with no publishable methods — is reported in
the
+// log and dropped, because retrying cannot change any of those.
+//
+// Publishing is idempotent and keyed only by service identity, so calling this
+// on every start, on each cycle-report pass, and on every retry simply
+// overwrites the previous document.
+func PublishServiceDefinitions(urls []*common.URL) []*common.URL {
+ publishers := serviceDefinitionPublishers()
+ if len(publishers) == 0 {
+ return nil
+ }
+
+ var failed []*common.URL
+ for _, u := range dedupeByService(urls) {
+ if !publishServiceDefinition(u, publishers) {
+ failed = append(failed, u)
+ }
+ }
+ return failed
+}
+
+// publishServiceDefinition reports whether the caller should retry.
+//
+// A partial failure across several reports counts as a failure: the retry
+// republishes to all of them, which is harmless because each write overwrites.
+func publishServiceDefinition(u *common.URL, publishers
[]report.ServiceDefinitionPublisher) bool {
+ svc := common.ServiceMap.GetServiceByServiceKey(u.Protocol,
u.ServiceKey())
+ if svc == nil {
+ logger.Warnf("[Metadata][Definition] no registered service for
%s/%s, skipping definition",
+ u.Protocol, u.ServiceKey())
+ return true
+ }
+
+ def, skips, err := definition.BuildFromURL(u, svc.ServiceType())
+ if err != nil {
+ logger.Errorf("[Metadata][Definition] could not build
definition for %s: %v", u.ServiceKey(), err)
+ return true
+ }
+ for _, skip := range skips {
+ logger.Warnf("[Metadata][Definition] method %s.%s is not
published: %s",
+ def.CanonicalName, skip.Name, skip.Reason)
+ }
+ if len(def.Methods) == 0 {
+ logger.Warnf("[Metadata][Definition] %s has no publishable
methods, skipping definition",
+ def.CanonicalName)
+ return true
+ }
+
+ payload, err := json.Marshal(def)
+ if err != nil {
+ logger.Errorf("[Metadata][Definition] could not serialize
definition for %s: %v",
+ def.CanonicalName, err)
+ return true
+ }
+
+ application := u.GetParam(constant.ApplicationKey, "")
+ published := true
+ for _, publisher := range publishers {
+ if err := publisher.PublishServiceDefinition(
+ def.CanonicalName, u.Version(), u.Group(), application,
string(payload),
+ ); err != nil {
+ logger.Errorf("[Metadata][Definition] could not publish
definition for %s: %v",
+ def.CanonicalName, err)
+ published = false
+ continue
+ }
+ logger.Infof("[Metadata][Definition] published definition for
%s, methods=%d types=%d",
+ def.CanonicalName, len(def.Methods), len(def.Types))
+ }
+ return published
+}
+
+// ServiceDefinitionsEnabled reports whether any configured metadata report
will
+// accept interface-level service definitions.
+//
+// Callers use this to decide whether work that only exists to serve
definitions
+// — the daily re-publish, for one — is worth scheduling at all.
+func ServiceDefinitionsEnabled() bool {
+ return len(serviceDefinitionPublishers()) > 0
+}
+
+// serviceDefinitionPublishers returns the configured reports that both support
+// the capability and have it switched on.
+//
+// The instance table stores *DelegateMetadataReport, so the capability has to
be
+// queried through the wrapper rather than type-asserted off the interface
value.
+func serviceDefinitionPublishers() []report.ServiceDefinitionPublisher {
+ var publishers []report.ServiceDefinitionPublisher
+ for _, r := range GetMetadataReports() {
Review Comment:
[P1] 服务定义不能绕过 registry 绑定广播到全部 metadata report
serviceDiscoveryRegistry 已经通过 GetMetadataReportByRegistry(registryId)
把应用元数据限定到当前 registry 对应的 report,但这里改用全局 GetMetadataReports()
收集所有支持该能力的后端。一个进程配置两个 registry/Nacos namespace 时,注册在 A 的服务会同时把接口定义写进
B,破坏租户和环境隔离;B 的故障还会让 A 为无关后端持续安排重试。双 report 负向探针中,只传入 A 的服务 URL 后 A、B
都各收到了一次发布。请让 PublishServiceDefinitions、daily refresh 和 retry 显式携带当前 registry
已绑定的 delegate/publisher(或 registry id),并补 A/B 两个 report 下只写 A 的生产入口测试。
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]