AlexStocks commented on code in PR #3371:
URL: https://github.com/apache/dubbo-go/pull/3371#discussion_r3367357889
##########
registry/servicediscovery/service_discovery_registry.go:
##########
@@ -298,12 +320,142 @@ func (s *serviceDiscoveryRegistry) IsAvailable() bool {
}
func (s *serviceDiscoveryRegistry) Destroy() {
+ s.stopMetadataTimers()
err := s.serviceDiscovery.Destroy()
if err != nil {
logger.Errorf("[Registry][ServiceDiscovery] destroy
serviceDiscovery catch error, err=%s", err.Error())
}
}
+func (s *serviceDiscoveryRegistry) stopMetadataTimers() {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+ if s.renewAppMetadataTimer != nil {
+ s.renewAppMetadataTimer.Stop()
+ s.renewAppMetadataTimer = nil
+ }
+}
+
+// ========== renewAppMetadata: daily app-level metadata re-publish ==========
+
+func (s *serviceDiscoveryRegistry) startRenewAppMetadataTimer() {
+ if !s.url.GetParamBool(constant.CycleReportKey, true) {
+ return
+ }
+
+ // Run immediately on start
+ if s.url.GetParamBool(constant.MetadataRenewOnStartupKey, true) {
+ go s.doRenewAppMetadata()
+ }
+
+ delay := s.calculateRenewAppMetadataDelay()
+ s.renewAppMetadataTimer = time.AfterFunc(delay, func() {
+ s.doRenewAppMetadata()
+ // Reschedule for next day
+ s.lock.Lock()
+ if s.renewAppMetadataTimer != nil {
+ s.renewAppMetadataTimer.Reset(24 * time.Hour)
+ }
+ s.lock.Unlock()
+ })
+}
+
+func (s *serviceDiscoveryRegistry) doRenewAppMetadata() {
+ registryID := s.url.GetParam(constant.RegistryIdKey, "")
+ metaInfo := metadata.GetMetadataInfo(registryID)
+ if metaInfo == nil || metaInfo.Revision == "0" {
+ return
+ }
+ metaInfo.LastUpdatedTime = time.Now().UnixMilli()
+ if err := s.metadataReport.PublishAppMetadata(metaInfo.App,
metaInfo.Revision, metaInfo); err != nil {
+ logger.Errorf("[Metadata][renewAppMetadata] failed to
re-publish metadata for app=%s revision=%s: %v", metaInfo.App,
metaInfo.Revision, err)
+ } else {
+ logger.Infof("[Metadata][renewAppMetadata] refreshed metadata
for app=%s revision=%s", metaInfo.App, metaInfo.Revision)
+ }
+
+ // Run garbage collection if enabled, after each renew cycle
+ if s.url.GetParamBool(constant.MetadataGCEnabledKey, true) {
+ s.doGarbageCollect()
+ }
+}
+
+func (s *serviceDiscoveryRegistry) calculateRenewAppMetadataDelay()
time.Duration {
+ now := time.Now()
+ // Next day 2:00 AM
+ nextDay2AM := time.Date(now.Year(), now.Month(), now.Day()+1, 2, 0, 0,
0, now.Location())
+ // Add random offset 0~4 hours to avoid thundering herd
+ randomOffset := time.Duration(rand.Int64N(int64(4 * time.Hour)))
+ return time.Until(nextDay2AM) + randomOffset
+}
+
+// ========== GC: stale revision cleanup ==========
+
+func (s *serviceDiscoveryRegistry) doGarbageCollect() {
+ registryID := s.url.GetParam(constant.RegistryIdKey, "")
+ metaInfo := metadata.GetMetadataInfo(registryID)
+ if metaInfo == nil {
+ return
+ }
+ app := metaInfo.App
+ if app == "" {
+ return
+ }
+
+ // Step 1: List all revisions for this app
+ revisions, err := s.metadataReport.ListAppRevisions(app)
+ if err != nil {
+ logger.Warnf("[Metadata][GC] failed to list app revisions: %v",
err)
+ return
+ }
+ if len(revisions) == 0 {
+ return
+ }
+
+ // Step 2: Filter stale candidates (exceed GC window in days)
+ gcWindowDays := s.url.GetParamByIntValue(constant.MetadataGCWindowKey,
5)
+ if gcWindowDays <= 0 || gcWindowDays > 365 {
+ gcWindowDays = 5
+ }
+ cutoff := time.Now().AddDate(0, 0, -gcWindowDays).UnixMilli()
+ candidates := make(map[string]bool)
+ for _, rev := range revisions {
+ // Skip special revisions
+ if rev.Revision == "0" || rev.Revision == "N/A" || rev.Revision
== "" || rev.Revision == metaInfo.Revision {
+ continue
+ }
+ // ModifyTime == 0 means old metadata produced by a version
that does not set
+ // lastUpdatedTime — never garbage-collect such entries. Only
delete when the
+ // revision is older than gcWindow and no alive instance
references it.
+ if rev.ModifyTime > 0 && rev.ModifyTime < cutoff {
+ candidates[rev.Revision] = true
+ }
+ }
+ if len(candidates) == 0 {
+ return
+ }
+
+ // Step 3: Get alive instances and their revisions
+ instances := s.serviceDiscovery.GetInstances(app)
+ aliveRevisions := make(map[string]bool)
+ for _, inst := range instances {
+ rev :=
inst.GetMetadata()[constant.ExportedServicesRevisionPropertyName]
Review Comment:
[P0] 这里直接对 `inst.GetMetadata()` 的返回值取下标,注册中心返回的实例如果没有 metadata map,会在定时 GC
goroutine 中触发 nil map 访问 panic。上面的 listener 路径已经显式处理过 `instance.GetMetadata()
== nil`,但 GC 新增路径没有同样的防护;一旦某个存活实例缺少 metadata,整个 renew/GC 任务会崩掉,后续旧 revision
清理也停止。这里应先缓存 `metadata := inst.GetMetadata()` 并判空,缺失 metadata 的实例跳过或按无 revision
处理。
--
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]