-
Notifications
You must be signed in to change notification settings - Fork 1k
feat(metadata): add concurrency safety for application-level metadata state (#3353) #3367
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: develop
Are you sure you want to change the base?
Changes from 14 commits
ef8cee4
605d0b2
abf50a7
8bc0aaa
ea83f92
0048c16
e65f694
a7bad03
3cf2c5a
9bf63a0
21cedcc
1bd679e
c77d53a
f8d3248
d3f571d
3bf11e4
11cc932
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,14 +18,19 @@ | |
| // Package metadata collects and exposes information of all services for service discovery purpose. | ||
| package metadata | ||
|
|
||
| import ( | ||
| "sync" | ||
| ) | ||
|
|
||
| import ( | ||
| "dubbo.apache.org/dubbo-go/v3/common" | ||
| "dubbo.apache.org/dubbo-go/v3/common/constant" | ||
| "dubbo.apache.org/dubbo-go/v3/metadata/info" | ||
| ) | ||
|
|
||
| var ( | ||
| registryMetadataInfo = make(map[string]*info.MetadataInfo) | ||
| registryMetadataInfo = make(map[string]*info.MetadataInfo) | ||
| registryMetadataLock sync.RWMutex | ||
| metadataService MetadataService = &DefaultMetadataService{metadataMap: registryMetadataInfo} | ||
| ) | ||
|
|
||
|
|
@@ -34,27 +39,37 @@ func GetMetadataService() MetadataService { | |
| } | ||
|
|
||
| func GetMetadataInfo(registryId string) *info.MetadataInfo { | ||
| registryMetadataLock.RLock() | ||
| defer registryMetadataLock.RUnlock() | ||
| return registryMetadataInfo[registryId] | ||
| } | ||
|
|
||
| func AddService(registryId string, url *common.URL) { | ||
| registryMetadataLock.Lock() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] 这里仍然裸读 registryMetadataInfo。这个 PR 让 AddService/AddSubscribeURL/GetMetadataInfo 通过 registryMetadataLock 保护同一个全局 map,但 RemoveService/RemoveSubscribeURL 还在没有 RLock 的情况下做 map lookup;当服务导出/订阅与注销并发发生时,Add 分支可能正在写入 registryMetadataInfo,Remove 分支同时读会继续触发 concurrent map read/write。这里需要先在 registryMetadataLock 下取出 metadataInfo,再释放全局锁后调用 metadataInfo.RemoveService,和 AddService 的锁粒度保持一致。
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 我已经更新了 全局锁粒度最小化,并与 |
||
| if _, exist := registryMetadataInfo[registryId]; !exist { | ||
| registryMetadataInfo[registryId] = info.NewMetadataInfo( | ||
| url.GetParam(constant.ApplicationKey, ""), | ||
| url.GetParam(constant.ApplicationTagKey, ""), | ||
| ) | ||
| } | ||
| registryMetadataInfo[registryId].AddService(url) | ||
| metaInfo := registryMetadataInfo[registryId] | ||
| registryMetadataLock.Unlock() | ||
|
|
||
| metaInfo.AddService(url) | ||
| } | ||
|
|
||
| func AddSubscribeURL(registryId string, url *common.URL) { | ||
| registryMetadataLock.Lock() | ||
| if _, exist := registryMetadataInfo[registryId]; !exist { | ||
| registryMetadataInfo[registryId] = info.NewMetadataInfo( | ||
| url.GetParam(constant.ApplicationKey, ""), | ||
| url.GetParam(constant.ApplicationTagKey, ""), | ||
| ) | ||
| } | ||
| registryMetadataInfo[registryId].AddSubscribeURL(url) | ||
| metaInfo := registryMetadataInfo[registryId] | ||
| registryMetadataLock.Unlock() | ||
|
|
||
| metaInfo.AddSubscribeURL(url) | ||
| } | ||
|
|
||
| func RemoveService(registryId string, url *common.URL) { | ||
|
|
||
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.
[P1] 这里仍然绕过
MetadataInfo.mu直接重置Services和exportedServiceURLs。本 PR 已经让AddService、RemoveService、GetServices、GetExportedServiceURLs通过同一个 mutex 保护这些字段,但ReplaceExportedServices由service_discovery_registry.go调用时会无锁写 map;如果同时有 metadata 读取或实例变更处理在调用GetServices/GetExportedServiceURLs,仍然可能触发 data race 或读到半重建状态。这里需要在函数入口持有写锁,并避免在持锁后直接调用会再次加锁的AddService,可以拆出一个内部 no-lock helper 来复用写入逻辑。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.
好的,我现在使用了内部无锁辅助模式重构逻辑,并提取
addServiceWithoutLock来处理核心映射更新。现在,ReplaceExportedServices在入口获取info.mu.Lock()一次,并在循环中安全地重用辅助函数。