优化结构
This commit is contained in:
+60
-33
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user