优化结构

This commit is contained in:
Yvan
2026-08-24 21:50:33 +08:00
parent d3c20c578f
commit 969afee284
20 changed files with 497 additions and 244 deletions
+60 -33
View File
@@ -58,10 +58,14 @@ type Data struct {
storage *storage.Reloadable
dbListMu sync.RWMutex
dbList map[string]*gorm.DB
appLogger *slog.Logger
auditLog *dataScopeAuditWriter
catalog module.Catalog
resourceHook func()
// Handles replaced by a hot reload of the named lists. They follow the same
// grace period as the primary pool so in-flight queries are not cut off.
retiredDBList retiredSet[*gorm.DB]
retiredRedisList retiredSet[redis.UniversalClient]
appLogger *slog.Logger
auditLog *dataScopeAuditWriter
catalog module.Catalog
resourceHook func()
}
// DB exposes the active primary database to narrowly scoped data submodules.
@@ -100,7 +104,16 @@ func (d *Data) IntegrationRuntime() *runtimeconfig.Store {
// Database resolves the primary or a named database for repositories such as
// the system export module.
func (d *Data) Database(name string) (*gorm.DB, error) {
return d.database(name)
if name == "" {
return d.gormDB.DB(), nil
}
d.dbListMu.RLock()
db := d.dbList[name]
d.dbListMu.RUnlock()
if db == nil {
return nil, fmt.Errorf("database %q not found", name)
}
return db, nil
}
func (d *Data) RedisClient() redis.UniversalClient {
@@ -190,13 +203,13 @@ func (d *Data) logger() *slog.Logger {
return slog.Default()
}
func openDatabaseList(configs []*config.Database, appLogger ...*slog.Logger) (map[string]*gorm.DB, error) {
func openDatabaseList(configs []*config.Database, appLogger *slog.Logger) (map[string]*gorm.DB, error) {
items := make(map[string]*gorm.DB)
for _, config := range configs {
if config == nil || config.Disable || config.AliasName == "" {
continue
}
db, err := openDatabase(config, false, "", appLogger...)
db, err := openDatabase(config, false, "", appLogger)
if err != nil {
for _, opened := range items {
if sqlDB, dbErr := opened.DB(); dbErr == nil {
@@ -212,31 +225,26 @@ func openDatabaseList(configs []*config.Database, appLogger ...*slog.Logger) (ma
func closeDatabaseList(items map[string]*gorm.DB) {
for _, db := range items {
if sqlDB, err := db.DB(); err == nil {
_ = sqlDB.Close()
if db != nil {
closeGormDB(db)
}
}
}
// replaceDatabaseList swaps the named database handles. Superseded handles are
// retired instead of closed on the spot: a request that already resolved a
// handle would otherwise fail with "sql: database is closed" mid-flight.
func (d *Data) replaceDatabaseList(items map[string]*gorm.DB) {
d.dbListMu.Lock()
old := d.dbList
d.dbList = items
d.dbListMu.Unlock()
closeDatabaseList(old)
}
func (d *Data) database(name string) (*gorm.DB, error) {
if name == "" {
return d.gormDB.DB(), nil
for name, db := range old {
if db == nil || db == items[name] {
continue
}
d.retiredDBList.retire(db, closeGormDB)
}
d.dbListMu.RLock()
db := d.dbList[name]
d.dbListMu.RUnlock()
if db == nil {
return nil, fmt.Errorf("database %q not found", name)
}
return db, nil
}
func NewData(runtime *config.Store, appLogger *slog.Logger, storageManager *storage.Reloadable, catalog module.Catalog) (*Data, func(), error) {
@@ -264,10 +272,16 @@ func NewData(runtime *config.Store, appLogger *slog.Logger, storageManager *stor
d.gormDB.close()
}
closeDatabaseList(d.dbList)
for _, db := range d.retiredDBList.drain() {
closeGormDB(db)
}
if d.redis != nil {
d.redis.close()
}
closeRedisList(d.redisList)
for _, client := range d.retiredRedisList.drain() {
closeRedisClient(client)
}
if d.mongo != nil {
d.mongo.close()
}
@@ -363,10 +377,20 @@ func NewData(runtime *config.Store, appLogger *slog.Logger, storageManager *stor
return d, cleanup, nil
}
func openRedis(config *config.Redis, enabled bool, appLogger ...*slog.Logger) redis.UniversalClient {
if !enabled || config == nil || (config.Addr == "" && len(config.ClusterAddrs) == 0) {
// redisConnectionConfigured reports whether a Redis block names an endpoint.
// The reload path shares it with openRedis so "no endpoint configured" is never
// confused with "configured endpoint failed to answer".
func redisConnectionConfigured(config *config.Redis) bool {
return config != nil && (config.Addr != "" || len(config.ClusterAddrs) > 0)
}
func openRedis(config *config.Redis, enabled bool, appLogger *slog.Logger) redis.UniversalClient {
if !enabled || !redisConnectionConfigured(config) {
return nil
}
if appLogger == nil {
appLogger = slog.Default()
}
var candidate redis.UniversalClient
if config.UseCluster {
addresses := config.ClusterAddrs
@@ -387,18 +411,14 @@ func openRedis(config *config.Redis, enabled bool, appLogger ...*slog.Logger) re
pingCtx, cancel := context.WithTimeout(context.Background(), 800*time.Millisecond)
defer cancel()
if err := candidate.Ping(pingCtx).Err(); err != nil {
log := slog.Default()
if len(appLogger) > 0 && appLogger[0] != nil {
log = appLogger[0]
}
log.Warn("redis unavailable, using in-memory cache", "mod", "redis", "error", err)
appLogger.Warn("redis unavailable, using in-memory cache", "mod", "redis", "error", err)
_ = candidate.Close()
return nil
}
return candidate
}
func openRedisList(configs []*config.Redis, enabled bool, appLogger ...*slog.Logger) map[string]redis.UniversalClient {
func openRedisList(configs []*config.Redis, enabled bool, appLogger *slog.Logger) map[string]redis.UniversalClient {
if !enabled || len(configs) == 0 {
return nil
}
@@ -407,7 +427,7 @@ func openRedisList(configs []*config.Redis, enabled bool, appLogger ...*slog.Log
if item == nil || item.Name == "" {
continue
}
if client := openRedis(item, true, appLogger...); client != nil {
if client := openRedis(item, true, appLogger); client != nil {
clients[item.Name] = client
}
}
@@ -420,11 +440,13 @@ func openRedisList(configs []*config.Redis, enabled bool, appLogger ...*slog.Log
func closeRedisList(clients map[string]redis.UniversalClient) {
for _, client := range clients {
if client != nil {
_ = client.Close()
closeRedisClient(client)
}
}
}
// replaceRedisList mirrors replaceDatabaseList: superseded clients stay open for
// the retire grace period so in-flight commands are not aborted.
func (d *Data) replaceRedisList(clients map[string]redis.UniversalClient) {
if d == nil {
return
@@ -433,7 +455,12 @@ func (d *Data) replaceRedisList(clients map[string]redis.UniversalClient) {
old := d.redisList
d.redisList = clients
d.redisListMu.Unlock()
closeRedisList(old)
for name, client := range old {
if client == nil || client == clients[name] {
continue
}
d.retiredRedisList.retire(client, closeRedisClient)
}
}
func (d *Data) activateDatabase(db *gorm.DB, config *config.Database) {