Files
waf-platform/EdgeDNS/internal/nodes/manager_record.go
2026-03-22 17:37:40 +08:00

267 lines
6.7 KiB
Go

// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeDNS/internal/dbs"
"github.com/TeaOSLab/EdgeDNS/internal/models"
"github.com/TeaOSLab/EdgeDNS/internal/remotelogs"
"github.com/TeaOSLab/EdgeDNS/internal/rpc"
"github.com/iwind/TeaGo/types"
"sync"
"sync/atomic"
"time"
)
// RecordManager 记录管理器
type RecordManager struct {
recordsMap map[int64]*models.DomainRecords // domainId => RecordsMap
db *dbs.DB
locker sync.RWMutex
version int64
dbWriteFailures atomic.Int64 // DB 写入累计失败次数
notifier chan bool
readyCh chan struct{} // 初始加载完成后关闭
}
// NewRecordManager 获取新记录管理器对象
func NewRecordManager(db *dbs.DB) *RecordManager {
return &RecordManager{
db: db,
recordsMap: map[int64]*models.DomainRecords{},
notifier: make(chan bool, 8),
readyCh: make(chan struct{}),
}
}
// Start 启动自动任务
func (this *RecordManager) Start() {
remotelogs.Println("RECORD_MANAGER", "starting ...")
// 从本地数据库中加载数据
err := this.Load()
if err != nil {
if rpc.IsConnError(err) {
remotelogs.Debug("RECORD_MANAGER", "load failed: "+err.Error())
} else {
remotelogs.Error("RECORD_MANAGER", "load failed: "+err.Error())
}
}
// 初始化运行
err = this.LoopAll()
if err != nil {
if rpc.IsConnError(err) {
remotelogs.Debug("RECORD_MANAGER", "loop failed: "+err.Error())
} else {
remotelogs.Error("RECORD_MANAGER", "loop failed: "+err.Error())
}
}
// 通知初始加载完成
close(this.readyCh)
// 更新
var ticker = time.NewTicker(30 * time.Second)
for {
select {
case <-ticker.C:
case <-this.notifier:
}
err := this.LoopAll()
if err != nil {
if rpc.IsConnError(err) {
remotelogs.Debug("RECORD_MANAGER", "loop failed: "+err.Error())
} else {
remotelogs.Error("RECORD_MANAGER", "loop failed: "+err.Error())
}
}
}
}
// Load 从数据库中加载数据
func (this *RecordManager) Load() error {
var offset = 0
var size = 10000
for {
records, err := this.db.ListRecords(offset, size)
if err != nil {
return err
}
if len(records) == 0 {
break
}
this.locker.Lock()
for _, record := range records {
domainRecords, ok := this.recordsMap[record.DomainId]
if !ok {
domainRecords = models.NewDomainRecords()
this.recordsMap[record.DomainId] = domainRecords
}
domainRecords.Add(record)
if record.Version > this.version {
this.version = record.Version
}
}
this.locker.Unlock()
offset += size
}
if this.version > 0 {
this.version++
}
return nil
}
func (this *RecordManager) LoopAll() error {
for {
hasNext, err := this.Loop()
if err != nil {
return err
}
if !hasNext {
break
}
}
return nil
}
// Loop 单次循环任务
func (this *RecordManager) Loop() (hasNext bool, err error) {
client, err := rpc.SharedRPC()
if err != nil {
return false, err
}
resp, err := client.NSRecordRPC.ListNSRecordsAfterVersion(client.Context(), &pb.ListNSRecordsAfterVersionRequest{
Version: this.version,
Size: 20000,
})
if err != nil {
return false, err
}
var records = resp.NsRecords
if len(records) == 0 {
return false, nil
}
for _, record := range records {
this.processRecord(record)
if record.Version > this.version {
this.version = record.Version
}
}
this.version++
return true, nil
}
func (this *RecordManager) FindRecords(domainId int64, routeCodes []string, recordName string, recordType dnsconfigs.RecordType, strictMode bool) (records []*models.NSRecord, routeCode string) {
this.locker.RLock()
domainRecords, ok := this.recordsMap[domainId]
if ok {
records, routeCode = domainRecords.Find(routeCodes, recordName, recordType, dnsNodeConfig().Answer, strictMode)
}
this.locker.RUnlock()
return
}
// NotifyUpdate 通知更新
func (this *RecordManager) NotifyUpdate() {
select {
case this.notifier <- true:
default:
}
}
// 处理单条记录
func (this *RecordManager) processRecord(record *pb.NSRecord) {
if record.NsDomain == nil {
return
}
if !record.IsOn || record.IsDeleted {
this.locker.Lock()
domainRecords, ok := this.recordsMap[record.NsDomain.Id]
if ok {
domainRecords.Remove(record.Id)
}
this.locker.Unlock()
// 从数据库中删除
if this.db != nil {
err := this.db.DeleteRecord(record.Id)
if err != nil {
count := this.dbWriteFailures.Add(1)
remotelogs.Error("RECORD_MANAGER", "delete record from db failed (total failures: "+types.String(count)+"): "+err.Error())
}
}
return
}
// 存入数据库
if this.db != nil {
exists, err := this.db.ExistsRecord(record.Id)
if err != nil {
count := this.dbWriteFailures.Add(1)
remotelogs.Error("RECORD_MANAGER", "query failed (total failures: "+types.String(count)+"): "+err.Error())
} else {
var routeIds = []string{}
for _, route := range record.NsRoutes {
routeIds = append(routeIds, route.Code)
}
if exists {
err = this.db.UpdateRecord(record.Id, record.NsDomain.Id, record.Name, record.Type, record.Value, record.MxPriority, record.SrvPriority, record.SrvWeight, record.SrvPort, record.CaaFlag, record.CaaTag, record.Ttl, record.Weight, routeIds, record.Version)
if err != nil {
count := this.dbWriteFailures.Add(1)
remotelogs.Error("RECORD_MANAGER", "update failed (total failures: "+types.String(count)+"): "+err.Error())
}
} else {
err = this.db.InsertRecord(record.Id, record.NsDomain.Id, record.Name, record.Type, record.Value, record.MxPriority, record.SrvPriority, record.SrvWeight, record.SrvPort, record.CaaFlag, record.CaaTag, record.Ttl, record.Weight, routeIds, record.Version)
if err != nil {
count := this.dbWriteFailures.Add(1)
remotelogs.Error("RECORD_MANAGER", "insert failed (total failures: "+types.String(count)+"): "+err.Error())
}
}
}
}
// 加入缓存Map
this.locker.Lock()
domainRecords, ok := this.recordsMap[record.NsDomain.Id]
if !ok {
domainRecords = models.NewDomainRecords()
this.recordsMap[record.NsDomain.Id] = domainRecords
}
var routeIds = []string{}
for _, r := range record.NsRoutes {
routeIds = append(routeIds, r.Code)
}
domainRecords.Add(&models.NSRecord{
Id: record.Id,
Name: record.Name,
Type: record.Type,
Value: record.Value,
MXPriority: record.MxPriority,
SRVPriority: record.SrvPriority,
SRVWeight: record.SrvWeight,
SRVPort: record.SrvPort,
CAAFlag: record.CaaFlag,
CAATag: record.CaaTag,
Ttl: record.Ttl,
Weight: record.Weight,
Version: record.Version,
RouteIds: routeIds,
DomainId: record.NsDomain.Id,
})
this.locker.Unlock()
}