Update internal/config/dynamic_config.go
This commit is contained in:
1 parent
ba4f7bfaa7
commit
79c1a0171e
1 file changed
+85
-62
@@ -9,7 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Файл: internal/config/dynamic_config.go
|
// Файл: internal/config/dynamic_config.go
|
||||||
// Назначение: Централизованное управление конфигурацией через Raft
|
// Назначение: Централизованное управление конфигурацией через Raft.
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ import (
|
|||||||
|
|
||||||
type atomicBool struct{ v int32 }
|
type atomicBool struct{ v int32 }
|
||||||
|
|
||||||
func (b *atomicBool) Load() bool { return atomic.LoadInt32(&b.v) != 0 }
|
func (b *atomicBool) Load() bool { return atomic.LoadInt32(&b.v) != 0 }
|
||||||
func (b *atomicBool) Store(v bool) { atomic.StoreInt32(&b.v, boolToInt32(v)) }
|
func (b *atomicBool) Store(v bool) { atomic.StoreInt32(&b.v, boolToInt32(v)) }
|
||||||
func (b *atomicBool) CAS(old, new bool) bool {
|
func (b *atomicBool) CAS(old, new bool) bool {
|
||||||
return atomic.CompareAndSwapInt32(&b.v, boolToInt32(old), boolToInt32(new))
|
return atomic.CompareAndSwapInt32(&b.v, boolToInt32(old), boolToInt32(new))
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ func (a *atomicUint64) Add(d uint64) uint64 { return atomic.AddUint64(&a.v, d) }
|
|||||||
// ТИПЫ ДЛЯ ДИНАМИЧЕСКОЙ КОНФИГУРАЦИИ
|
// ТИПЫ ДЛЯ ДИНАМИЧЕСКОЙ КОНФИГУРАЦИИ
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// DynamicConfigChange представляет изменение конфигурации
|
// DynamicConfigChange представляет изменение конфигурации.
|
||||||
type DynamicConfigChange struct {
|
type DynamicConfigChange struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Version uint64 `json:"version"`
|
Version uint64 `json:"version"`
|
||||||
@@ -65,22 +65,22 @@ type DynamicConfigChange struct {
|
|||||||
Changes map[string]interface{} `json:"changes"`
|
Changes map[string]interface{} `json:"changes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DynamicConfigSnapshot представляет снэпшот конфигурации
|
// DynamicConfigSnapshot представляет снэпшот конфигурации.
|
||||||
type DynamicConfigSnapshot struct {
|
type DynamicConfigSnapshot struct {
|
||||||
Version uint64 `json:"version"`
|
Version uint64 `json:"version"`
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
Config *Config `json:"config"`
|
Config *Config `json:"config"`
|
||||||
ChangeHistory []*DynamicConfigChange `json:"change_history"`
|
ChangeHistory []*DynamicConfigChange `json:"change_history"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigChangeListener представляет слушатель изменений конфигурации
|
// ConfigChangeListener представляет слушатель изменений конфигурации.
|
||||||
type ConfigChangeListener func(change *DynamicConfigChange)
|
type ConfigChangeListener func(change *DynamicConfigChange)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// DYNAMIC CONFIG MANAGER
|
// DYNAMIC CONFIG MANAGER
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// DynamicConfigManager управляет динамической конфигурацией через Raft
|
// DynamicConfigManager управляет динамической конфигурацией через Raft.
|
||||||
type DynamicConfigManager struct {
|
type DynamicConfigManager struct {
|
||||||
config *Config
|
config *Config
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
@@ -97,29 +97,28 @@ type DynamicConfigManager struct {
|
|||||||
isLeader atomicBool
|
isLeader atomicBool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigFSM реализует конечный автомат для конфигурации
|
// ConfigFSM реализует конечный автомат для конфигурации.
|
||||||
type ConfigFSM struct {
|
type ConfigFSM struct {
|
||||||
config *DynamicConfigSnapshot
|
config *DynamicConfigSnapshot
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
// onChange вызывается после успешного применения изменения.
|
// onChange вызывается после успешного применения изменения.
|
||||||
// Может быть установлен менеджером для уведомления слушателей.
|
// Может быть установлен менеджером для уведомления слушателей.
|
||||||
onChange func(change *DynamicConfigChange)
|
onChange func(change *DynamicConfigChange)
|
||||||
onChangeMu sync.RWMutex
|
onChangeMu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigSnapshot реализует raft.FSMSnapshot
|
// ConfigSnapshot реализует raft.FSMSnapshot.
|
||||||
type ConfigSnapshot struct {
|
type ConfigSnapshot struct {
|
||||||
config *DynamicConfigSnapshot
|
config *DynamicConfigSnapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDynamicConfigManager создаёт новый менеджер динамической конфигурации
|
// NewDynamicConfigManager создаёт новый менеджер динамической конфигурации.
|
||||||
func NewDynamicConfigManager(cfg *Config, logger *log.Logger) *DynamicConfigManager {
|
func NewDynamicConfigManager(cfg *Config, logger *log.Logger) *DynamicConfigManager {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
cfg = &Config{}
|
cfg = &Config{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаём начальный снэпшот
|
|
||||||
initialSnapshot := &DynamicConfigSnapshot{
|
initialSnapshot := &DynamicConfigSnapshot{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
UpdatedAt: time.Now().UnixMilli(),
|
UpdatedAt: time.Now().UnixMilli(),
|
||||||
@@ -142,13 +141,11 @@ func NewDynamicConfigManager(cfg *Config, logger *log.Logger) *DynamicConfigMana
|
|||||||
}
|
}
|
||||||
dm.version.Store(1)
|
dm.version.Store(1)
|
||||||
|
|
||||||
// Подключаем FSM к менеджеру для уведомления слушателей
|
|
||||||
fsm.onChangeMu.Lock()
|
fsm.onChangeMu.Lock()
|
||||||
fsm.onChange = func(change *DynamicConfigChange) {
|
fsm.onChange = func(change *DynamicConfigChange) {
|
||||||
dm.version.Store(change.Version)
|
dm.version.Store(change.Version)
|
||||||
dm.mu.Lock()
|
dm.mu.Lock()
|
||||||
dm.changeHistory = append(dm.changeHistory, change)
|
dm.changeHistory = append(dm.changeHistory, change)
|
||||||
// Ограничиваем историю
|
|
||||||
if len(dm.changeHistory) > 1000 {
|
if len(dm.changeHistory) > 1000 {
|
||||||
dm.changeHistory = dm.changeHistory[len(dm.changeHistory)-1000:]
|
dm.changeHistory = dm.changeHistory[len(dm.changeHistory)-1000:]
|
||||||
}
|
}
|
||||||
@@ -160,19 +157,19 @@ func NewDynamicConfigManager(cfg *Config, logger *log.Logger) *DynamicConfigMana
|
|||||||
return dm
|
return dm
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRaft устанавливает Raft для управления конфигурацией
|
// SetRaft устанавливает Raft для управления конфигурацией.
|
||||||
func (dm *DynamicConfigManager) SetRaft(r *raft.Raft) {
|
func (dm *DynamicConfigManager) SetRaft(r *raft.Raft) {
|
||||||
dm.mu.Lock()
|
dm.mu.Lock()
|
||||||
defer dm.mu.Unlock()
|
defer dm.mu.Unlock()
|
||||||
dm.raft = r
|
dm.raft = r
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFSM возвращает FSM для конфигурации
|
// GetFSM возвращает FSM для конфигурации.
|
||||||
func (dm *DynamicConfigManager) GetFSM() *ConfigFSM {
|
func (dm *DynamicConfigManager) GetFSM() *ConfigFSM {
|
||||||
return dm.fsm
|
return dm.fsm
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start запускает менеджер динамической конфигурации
|
// Start запускает менеджер динамической конфигурации.
|
||||||
func (dm *DynamicConfigManager) Start() {
|
func (dm *DynamicConfigManager) Start() {
|
||||||
if dm.logger != nil {
|
if dm.logger != nil {
|
||||||
dm.logger.Info("Dynamic config manager started")
|
dm.logger.Info("Dynamic config manager started")
|
||||||
@@ -203,9 +200,8 @@ func (dm *DynamicConfigManager) GetConfig() *Config {
|
|||||||
return deepCopyConfig(dm.fsm.config.Config)
|
return deepCopyConfig(dm.fsm.config.Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVersion возвращает текущую версию конфигурации
|
// GetVersion возвращает текущую версию конфигурации.
|
||||||
func (dm *DynamicConfigManager) GetVersion() uint64 {
|
func (dm *DynamicConfigManager) GetVersion() uint64 {
|
||||||
// Синхронизируем с FSM, чтобы значение было актуальным
|
|
||||||
dm.fsm.mu.RLock()
|
dm.fsm.mu.RLock()
|
||||||
defer dm.fsm.mu.RUnlock()
|
defer dm.fsm.mu.RUnlock()
|
||||||
if dm.fsm.config != nil {
|
if dm.fsm.config != nil {
|
||||||
@@ -214,7 +210,7 @@ func (dm *DynamicConfigManager) GetVersion() uint64 {
|
|||||||
return dm.version.Load()
|
return dm.version.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChangeHistory возвращает историю изменений
|
// GetChangeHistory возвращает историю изменений.
|
||||||
func (dm *DynamicConfigManager) GetChangeHistory() []*DynamicConfigChange {
|
func (dm *DynamicConfigManager) GetChangeHistory() []*DynamicConfigChange {
|
||||||
dm.mu.RLock()
|
dm.mu.RLock()
|
||||||
defer dm.mu.RUnlock()
|
defer dm.mu.RUnlock()
|
||||||
@@ -224,7 +220,7 @@ func (dm *DynamicConfigManager) GetChangeHistory() []*DynamicConfigChange {
|
|||||||
return history
|
return history
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyChange применяет изменение конфигурации через Raft
|
// ApplyChange применяет изменение конфигурации через Raft.
|
||||||
func (dm *DynamicConfigManager) ApplyChange(changes map[string]interface{}, description string, changedBy string) error {
|
func (dm *DynamicConfigManager) ApplyChange(changes map[string]interface{}, description string, changedBy string) error {
|
||||||
dm.mu.RLock()
|
dm.mu.RLock()
|
||||||
r := dm.raft
|
r := dm.raft
|
||||||
@@ -234,14 +230,12 @@ func (dm *DynamicConfigManager) ApplyChange(changes map[string]interface{}, desc
|
|||||||
return fmt.Errorf("raft not initialized")
|
return fmt.Errorf("raft not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что мы лидер — через сам Raft, а не через внешний флаг
|
|
||||||
if r.State() != raft.Leader {
|
if r.State() != raft.Leader {
|
||||||
return fmt.Errorf("not the leader (current state: %s)", r.State())
|
return fmt.Errorf("not the leader (current state: %s)", r.State())
|
||||||
}
|
}
|
||||||
|
|
||||||
currentVersion := dm.GetVersion()
|
currentVersion := dm.GetVersion()
|
||||||
|
|
||||||
// Создаём команду изменения
|
|
||||||
change := &DynamicConfigChange{
|
change := &DynamicConfigChange{
|
||||||
ID: fmt.Sprintf("config_%d_%d", currentVersion+1, time.Now().UnixNano()),
|
ID: fmt.Sprintf("config_%d_%d", currentVersion+1, time.Now().UnixNano()),
|
||||||
Version: currentVersion + 1,
|
Version: currentVersion + 1,
|
||||||
@@ -251,7 +245,6 @@ func (dm *DynamicConfigManager) ApplyChange(changes map[string]interface{}, desc
|
|||||||
Changes: changes,
|
Changes: changes,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сериализуем и применяем через Raft
|
|
||||||
data, err := json.Marshal(change)
|
data, err := json.Marshal(change)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal config change: %v", err)
|
return fmt.Errorf("failed to marshal config change: %v", err)
|
||||||
@@ -262,7 +255,6 @@ func (dm *DynamicConfigManager) ApplyChange(changes map[string]interface{}, desc
|
|||||||
return fmt.Errorf("raft apply failed: %v", err)
|
return fmt.Errorf("raft apply failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем ошибку из FSM.Apply
|
|
||||||
if resp := future.Response(); resp != nil {
|
if resp := future.Response(); resp != nil {
|
||||||
if err, ok := resp.(error); ok {
|
if err, ok := resp.(error); ok {
|
||||||
return fmt.Errorf("fsm apply failed: %v", err)
|
return fmt.Errorf("fsm apply failed: %v", err)
|
||||||
@@ -272,16 +264,15 @@ func (dm *DynamicConfigManager) ApplyChange(changes map[string]interface{}, desc
|
|||||||
if dm.logger != nil {
|
if dm.logger != nil {
|
||||||
dm.logger.Info(fmt.Sprintf("Applied config change: %s (version: %d)", description, change.Version))
|
dm.logger.Info(fmt.Sprintf("Applied config change: %s (version: %d)", description, change.Version))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateConfig обновляет конфигурацию
|
// UpdateConfig обновляет конфигурацию.
|
||||||
func (dm *DynamicConfigManager) UpdateConfig(updates map[string]interface{}, description string, changedBy string) error {
|
func (dm *DynamicConfigManager) UpdateConfig(updates map[string]interface{}, description string, changedBy string) error {
|
||||||
return dm.ApplyChange(updates, description, changedBy)
|
return dm.ApplyChange(updates, description, changedBy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyConfigChange применяет изменение к FSM (вызывается из Raft)
|
// ApplyConfigChange применяет изменение к FSM (вызывается из Raft).
|
||||||
func (fsm *ConfigFSM) ApplyConfigChange(change *DynamicConfigChange) error {
|
func (fsm *ConfigFSM) ApplyConfigChange(change *DynamicConfigChange) error {
|
||||||
if change == nil {
|
if change == nil {
|
||||||
return fmt.Errorf("nil config change")
|
return fmt.Errorf("nil config change")
|
||||||
@@ -303,7 +294,6 @@ func (fsm *ConfigFSM) ApplyConfigChange(change *DynamicConfigChange) error {
|
|||||||
|
|
||||||
config := fsm.config.Config
|
config := fsm.config.Config
|
||||||
|
|
||||||
// Применяем изменения к конфигурации
|
|
||||||
for path, value := range change.Changes {
|
for path, value := range change.Changes {
|
||||||
if err := setConfigField(config, path, value); err != nil {
|
if err := setConfigField(config, path, value); err != nil {
|
||||||
fsm.mu.Unlock()
|
fsm.mu.Unlock()
|
||||||
@@ -311,11 +301,9 @@ func (fsm *ConfigFSM) ApplyConfigChange(change *DynamicConfigChange) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем метаданные
|
|
||||||
fsm.config.Version = change.Version
|
fsm.config.Version = change.Version
|
||||||
fsm.config.UpdatedAt = time.Now().UnixMilli()
|
fsm.config.UpdatedAt = time.Now().UnixMilli()
|
||||||
fsm.config.ChangeHistory = append(fsm.config.ChangeHistory, change)
|
fsm.config.ChangeHistory = append(fsm.config.ChangeHistory, change)
|
||||||
// Ограничиваем историю
|
|
||||||
if len(fsm.config.ChangeHistory) > 1000 {
|
if len(fsm.config.ChangeHistory) > 1000 {
|
||||||
fsm.config.ChangeHistory = fsm.config.ChangeHistory[len(fsm.config.ChangeHistory)-1000:]
|
fsm.config.ChangeHistory = fsm.config.ChangeHistory[len(fsm.config.ChangeHistory)-1000:]
|
||||||
}
|
}
|
||||||
@@ -326,32 +314,28 @@ func (fsm *ConfigFSM) ApplyConfigChange(change *DynamicConfigChange) error {
|
|||||||
fsm.logger.Debug(fmt.Sprintf("Applied config change to FSM: %s", change.Description))
|
fsm.logger.Debug(fmt.Sprintf("Applied config change to FSM: %s", change.Description))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Уведомляем слушателей (через callback, установленный менеджером)
|
|
||||||
fsm.onChangeMu.RLock()
|
fsm.onChangeMu.RLock()
|
||||||
cb := fsm.onChange
|
cb := fsm.onChange
|
||||||
fsm.onChangeMu.RUnlock()
|
fsm.onChangeMu.RUnlock()
|
||||||
if cb != nil {
|
if cb != nil {
|
||||||
cb(change)
|
cb(change)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply применяет команду к FSM (реализация raft.FSM)
|
// Apply применяет команду к FSM (реализация raft.FSM).
|
||||||
func (fsm *ConfigFSM) Apply(log *raft.Log) interface{} {
|
func (fsm *ConfigFSM) Apply(log *raft.Log) interface{} {
|
||||||
var change DynamicConfigChange
|
var change DynamicConfigChange
|
||||||
if err := json.Unmarshal(log.Data, &change); err != nil {
|
if err := json.Unmarshal(log.Data, &change); err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal config change: %v", err)
|
return fmt.Errorf("failed to unmarshal config change: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fsm.ApplyConfigChange(&change); err != nil {
|
if err := fsm.ApplyConfigChange(&change); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot создаёт снэпшот состояния FSM
|
// Snapshot создаёт снэпшот состояния FSM.
|
||||||
func (fsm *ConfigFSM) Snapshot() (raft.FSMSnapshot, error) {
|
func (fsm *ConfigFSM) Snapshot() (raft.FSMSnapshot, error) {
|
||||||
fsm.mu.RLock()
|
fsm.mu.RLock()
|
||||||
defer fsm.mu.RUnlock()
|
defer fsm.mu.RUnlock()
|
||||||
@@ -365,7 +349,6 @@ func (fsm *ConfigFSM) Snapshot() (raft.FSMSnapshot, error) {
|
|||||||
}}, nil
|
}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Глубокая копия через JSON, чтобы снэпшот не зависел от мутаций FSM
|
|
||||||
data, err := json.Marshal(fsm.config)
|
data, err := json.Marshal(fsm.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal snapshot: %v", err)
|
return nil, fmt.Errorf("failed to marshal snapshot: %v", err)
|
||||||
@@ -384,7 +367,7 @@ func (fsm *ConfigFSM) Snapshot() (raft.FSMSnapshot, error) {
|
|||||||
return &ConfigSnapshot{config: &snapshotData}, nil
|
return &ConfigSnapshot{config: &snapshotData}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore восстанавливает состояние FSM из снэпшота
|
// Restore восстанавливает состояние FSM из снэпшота.
|
||||||
func (fsm *ConfigFSM) Restore(snapshot io.ReadCloser) error {
|
func (fsm *ConfigFSM) Restore(snapshot io.ReadCloser) error {
|
||||||
defer snapshot.Close()
|
defer snapshot.Close()
|
||||||
|
|
||||||
@@ -407,37 +390,34 @@ func (fsm *ConfigFSM) Restore(snapshot io.ReadCloser) error {
|
|||||||
fsm.mu.Lock()
|
fsm.mu.Lock()
|
||||||
fsm.config = &data
|
fsm.config = &data
|
||||||
fsm.mu.Unlock()
|
fsm.mu.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persist сохраняет снэпшот
|
// Persist сохраняет снэпшот.
|
||||||
func (s *ConfigSnapshot) Persist(sink raft.SnapshotSink) error {
|
func (s *ConfigSnapshot) Persist(sink raft.SnapshotSink) error {
|
||||||
data, err := json.Marshal(s.config)
|
data, err := json.Marshal(s.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sink.Cancel()
|
sink.Cancel()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := sink.Write(data); err != nil {
|
if _, err := sink.Write(data); err != nil {
|
||||||
sink.Cancel()
|
sink.Cancel()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return sink.Close()
|
return sink.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release освобождает ресурсы снэпшота
|
// Release освобождает ресурсы снэпшота.
|
||||||
func (s *ConfigSnapshot) Release() {}
|
func (s *ConfigSnapshot) Release() {}
|
||||||
|
|
||||||
// RegisterListener регистрирует слушатель изменений конфигурации
|
// RegisterListener регистрирует слушатель изменений конфигурации.
|
||||||
func (dm *DynamicConfigManager) RegisterListener(name string, listener ConfigChangeListener) {
|
func (dm *DynamicConfigManager) RegisterListener(name string, listener ConfigChangeListener) {
|
||||||
dm.listenerMu.Lock()
|
dm.listenerMu.Lock()
|
||||||
defer dm.listenerMu.Unlock()
|
defer dm.listenerMu.Unlock()
|
||||||
dm.listeners[name] = listener
|
dm.listeners[name] = listener
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnregisterListener удаляет слушатель
|
// UnregisterListener удаляет слушатель.
|
||||||
func (dm *DynamicConfigManager) UnregisterListener(name string) {
|
func (dm *DynamicConfigManager) UnregisterListener(name string) {
|
||||||
dm.listenerMu.Lock()
|
dm.listenerMu.Lock()
|
||||||
defer dm.listenerMu.Unlock()
|
defer dm.listenerMu.Unlock()
|
||||||
@@ -445,8 +425,6 @@ func (dm *DynamicConfigManager) UnregisterListener(name string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// notifyListeners уведомляет слушателей об изменении.
|
// notifyListeners уведомляет слушателей об изменении.
|
||||||
// Каждый слушатель вызывается в отдельной горутине; паника в слушателе
|
|
||||||
// не должна ронять процесс.
|
|
||||||
func (dm *DynamicConfigManager) notifyListeners(change *DynamicConfigChange) {
|
func (dm *DynamicConfigManager) notifyListeners(change *DynamicConfigChange) {
|
||||||
dm.listenerMu.RLock()
|
dm.listenerMu.RLock()
|
||||||
defer dm.listenerMu.RUnlock()
|
defer dm.listenerMu.RUnlock()
|
||||||
@@ -468,28 +446,22 @@ func (dm *DynamicConfigManager) notifyListeners(change *DynamicConfigChange) {
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// deepCopyConfig создаёт глубокую копию конфигурации через JSON.
|
// deepCopyConfig создаёт глубокую копию конфигурации через JSON.
|
||||||
// Если src == nil, возвращает &Config{} (не nil), чтобы избежать паник.
|
|
||||||
func deepCopyConfig(src *Config) *Config {
|
func deepCopyConfig(src *Config) *Config {
|
||||||
if src == nil {
|
if src == nil {
|
||||||
return &Config{}
|
return &Config{}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(src)
|
data, err := json.Marshal(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Возвращаем пустую копию, а не nil, чтобы не сломать вызывающий код
|
|
||||||
return &Config{}
|
return &Config{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var dst Config
|
var dst Config
|
||||||
if err := json.Unmarshal(data, &dst); err != nil {
|
if err := json.Unmarshal(data, &dst); err != nil {
|
||||||
return &Config{}
|
return &Config{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &dst
|
return &dst
|
||||||
}
|
}
|
||||||
|
|
||||||
// coerceInt пытается привести значение к int.
|
// coerceInt пытается привести значение к int.
|
||||||
// Поддерживает int, int32, int64, uint, uint32, uint64, float32, float64, json.Number.
|
|
||||||
func coerceInt(value interface{}) (int, bool) {
|
func coerceInt(value interface{}) (int, bool) {
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case int:
|
case int:
|
||||||
@@ -627,8 +599,7 @@ func coerceString(value interface{}) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setConfigField устанавливает значение поля конфигурации по пути.
|
// setConfigField устанавливает значение поля конфигурации по пути.
|
||||||
// Поддерживает основные секции: cluster, storage, replication, wal, mvcc,
|
// Используется FSM при применении изменений из Raft.
|
||||||
// saga, backpressure, api, log, plugins, autoscaling, monitoring, transactions.
|
|
||||||
func setConfigField(config *Config, path string, value interface{}) error {
|
func setConfigField(config *Config, path string, value interface{}) error {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return fmt.Errorf("empty config path")
|
return fmt.Errorf("empty config path")
|
||||||
@@ -1087,12 +1058,64 @@ func setConfigField(config *Config, path string, value interface{}) error {
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("transactions.max_savepoints_per_tx: expected int, got %T", value)
|
return fmt.Errorf("transactions.max_savepoints_per_tx: expected int, got %T", value)
|
||||||
|
|
||||||
|
// ===== compression =====
|
||||||
|
case "compression.enabled":
|
||||||
|
if v, ok := coerceBool(value); ok {
|
||||||
|
config.Compression.Enabled = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("compression.enabled: expected bool, got %T", value)
|
||||||
|
case "compression.algorithm":
|
||||||
|
if v, ok := coerceString(value); ok {
|
||||||
|
config.Compression.Algorithm = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("compression.algorithm: expected string, got %T", value)
|
||||||
|
case "compression.level":
|
||||||
|
if v, ok := coerceInt(value); ok {
|
||||||
|
config.Compression.Level = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("compression.level: expected int, got %T", value)
|
||||||
|
case "compression.min_size":
|
||||||
|
if v, ok := coerceInt(value); ok {
|
||||||
|
config.Compression.MinSize = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("compression.min_size: expected int, got %T", value)
|
||||||
|
|
||||||
|
// ===== performance =====
|
||||||
|
case "performance.enable_pipeline":
|
||||||
|
if v, ok := coerceBool(value); ok {
|
||||||
|
config.Performance.EnablePipeline = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("performance.enable_pipeline: expected bool, got %T", value)
|
||||||
|
case "performance.batch_size":
|
||||||
|
if v, ok := coerceInt(value); ok {
|
||||||
|
config.Performance.BatchSize = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("performance.batch_size: expected int, got %T", value)
|
||||||
|
case "performance.max_connections":
|
||||||
|
if v, ok := coerceInt(value); ok {
|
||||||
|
config.Performance.MaxConnections = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("performance.max_connections: expected int, got %T", value)
|
||||||
|
case "performance.read_from_follower":
|
||||||
|
if v, ok := coerceBool(value); ok {
|
||||||
|
config.Performance.ReadFromFollower = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("performance.read_from_follower: expected bool, got %T", value)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown config path: %s", path)
|
return fmt.Errorf("unknown config path: %s", path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLeaderStatus возвращает статус лидера
|
// GetLeaderStatus возвращает статус лидера.
|
||||||
func (dm *DynamicConfigManager) GetLeaderStatus() bool {
|
func (dm *DynamicConfigManager) GetLeaderStatus() bool {
|
||||||
dm.mu.RLock()
|
dm.mu.RLock()
|
||||||
r := dm.raft
|
r := dm.raft
|
||||||
@@ -1110,7 +1133,7 @@ func (dm *DynamicConfigManager) SetLeaderStatus(isLeader bool) {
|
|||||||
dm.isLeader.Store(isLeader)
|
dm.isLeader.Store(isLeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetListeners возвращает список зарегистрированных слушателей
|
// GetListeners возвращает список зарегистрированных слушателей.
|
||||||
func (dm *DynamicConfigManager) GetListeners() []string {
|
func (dm *DynamicConfigManager) GetListeners() []string {
|
||||||
dm.listenerMu.RLock()
|
dm.listenerMu.RLock()
|
||||||
defer dm.listenerMu.RUnlock()
|
defer dm.listenerMu.RUnlock()
|
||||||
|
|||||||
Reference in new issue
Block a user