fix(reliability): 采集器并发去重 + 备份恢复即时重载 + context 贯穿请求链

- CollectorService 安装/卸载按采集器互斥去重(重复触发不再并发 docker rm/run 互踩);删除后卸载失败留日志
- ApplyStagedBackup 替换文件后立即 Reload 内存态,消除「重启前旧内存落盘覆盖恢复文件」竞态;暂存文件名升级 lokistack.json(兼容旧名)
- GetUsage/Validate 全链路 context 贯穿:请求取消即中止 SSH/k8s 查询(异步调用点显式 Background)
This commit is contained in:
opsx committed 2026-10-29 13:29:25 +08:00
1 parent 9daaf7f403
commit 572efc89a4
9 files changed
+180 -29

No files matched your search

+102 -5
View File
@@ -264,11 +264,6 @@ func (d *JsonDB) fixUnreachableLogSourceURLs() {
// LogSource : 查询页数据源下拉选不动(两个 option 同 value)
// AlertRule : 规则列表出现两条同 id,编辑/删除操作命中错的那条
// NotifyChannel: 通知渠道覆盖错对象
func (d *JsonDB) dedupeCollectionIDs() {
d.dedupeLogSourceIDs()
d.dedupeAlertRuleIDs()
d.dedupeNotifyChannelIDs()
}
// dedupeAlertRuleIDs 重新编号重复的告警规则 ID。
// 同名规则(seed 重复执行导致)只保留第一条,其余删除。
@@ -539,6 +534,108 @@ func (d *JsonDB) migrateSchema() bool {
return true
}
// Reload 从磁盘重新加载内存态(备份恢复后调用):
// 成功解析才整体替换集合并重建 ID 序列;失败时保持原内存态并返回错误。
// 必须在持有写锁的情况下调用。
func (d *JsonDB) Reload() error {
data, err := os.ReadFile(d.dataFile)
if err != nil {
return err
}
var fresh JsonDB
if err := json.Unmarshal(data, &fresh); err != nil {
return err
}
d.Users = fresh.Users
d.Clusters = fresh.Clusters
d.Hosts = fresh.Hosts
d.Storages = fresh.Storages
d.Deployments = fresh.Deployments
d.Collectors = fresh.Collectors
d.Logs = fresh.Logs
d.Settings = fresh.Settings
d.LogSources = fresh.LogSources
d.AlertRules = fresh.AlertRules
d.AlertDeliveries = fresh.AlertDeliveries
d.NotifyChannels = fresh.NotifyChannels
d.LogPolicyGroups = fresh.LogPolicyGroups
d.AuditLogs = fresh.AuditLogs
d.DailyVolumeSnapshots = fresh.DailyVolumeSnapshots
d.SchemaVersion = fresh.SchemaVersion
d.sequences = map[string]uint{}
for _, u := range d.Users {
if u.ID > d.sequences["users"] {
d.sequences["users"] = u.ID
}
}
for _, cl := range d.Clusters {
if cl.ID > d.sequences["clusters"] {
d.sequences["clusters"] = cl.ID
}
}
for _, h := range d.Hosts {
if h.ID > d.sequences["hosts"] {
d.sequences["hosts"] = h.ID
}
}
for _, s := range d.Storages {
if s.ID > d.sequences["storages"] {
d.sequences["storages"] = s.ID
}
}
for _, de := range d.Deployments {
if de.ID > d.sequences["deployments"] {
d.sequences["deployments"] = de.ID
}
}
for _, col := range d.Collectors {
if col.ID > d.sequences["collectors"] {
d.sequences["collectors"] = col.ID
}
}
for _, l := range d.Logs {
if l.ID > d.sequences["logs"] {
d.sequences["logs"] = l.ID
}
}
for _, g := range d.LogPolicyGroups {
if g.ID > d.sequences["logPolicyGroups"] {
d.sequences["logPolicyGroups"] = g.ID
}
}
for _, s := range d.LogSources {
if s.ID > d.sequences["logSources"] {
d.sequences["logSources"] = s.ID
}
}
for _, ch := range d.NotifyChannels {
if ch.ID > d.sequences["notifyChannels"] {
d.sequences["notifyChannels"] = ch.ID
}
}
for _, r := range d.AlertRules {
if r.ID > d.sequences["alertRules"] {
d.sequences["alertRules"] = r.ID
}
}
for _, de := range d.AlertDeliveries {
if de.ID > d.sequences["alertDeliveries"] {
d.sequences["alertDeliveries"] = de.ID
}
}
for _, a := range d.AuditLogs {
if a.ID > d.sequences["auditLogs"] {
d.sequences["auditLogs"] = a.ID
}
}
for _, v := range d.DailyVolumeSnapshots {
if v.ID > d.sequences["dailyVolumeSnapshots"] {
d.sequences["dailyVolumeSnapshots"] = v.ID
}
}
return nil
}
// migrateLegacyDataFile 首次从历史命名(opsxloki.json / lokideploy.json)加载后,
// 立即升级到 lokistack.json:数据原子写入新文件后删除旧文件。
// 不迁移的话回退链会让旧文件名(旧品牌残留)一直被沿用下去。