Files
futriix/internal/cluster/node.go

1606 lines
68 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright 2026 Safronov Grigorii
*
* Licensed under the CDDL, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
* https://opensource.org/licenses/CDDL-1.0
*/
// Файл: internal/cluster/node.go
// Назначение: Реализация узла кластера (node) для распределённой СУБД с поддержкой временных меток.
//
// ОСНОВНЫЕ ФУНКЦИИ:
// 1. Управление жизненным циклом узла (создание, запуск, остановка)
// 2. TCP-сервер для приёма входящих соединений от других узлов
// 3. Обработка различных типов запросов (репликация, запросы, синхронизация, heartbeat)
// 4. Управление состоянием узла (active, syncing, offline, failed)
// 5. Интеграция с Raft-координатором для управления кластером
// 6. Пул воркеров для асинхронной обработки запросов
// 7. Механизм восстановления после паники (panic recovery)
// 8. Репликация документов между узлами кластера
// 9. Сбор статистики и мониторинг состояния
package cluster
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net"
"runtime/debug"
"sync"
"sync/atomic"
"time"
"futriis/internal/log"
"futriis/internal/storage"
"github.com/google/uuid"
"futriis/internal/config"
)
// =============================================================================
// NETWORK REPLICATOR - ЗАГЛУШКА
// =============================================================================
// NetworkReplicator - заглушка для сетевой репликации.
// В реальной реализации этот компонент отвечал бы за отправку данных на другие узлы, с поддержкой повторных попыток, backoff и джиттера.
// Полная реализация с поддержкой протокола gRPC или HTTP/2
type NetworkReplicator struct{}
// NewNetworkReplicator создаёт новый экземпляр NetworkReplicator.
// Принимает конфигурацию повторных попыток, пул воркеров и логгер.
// Возвращает заглушку, которая не выполняет реальной репликации.
func NewNetworkReplicator(config *ReplicationRetryConfig, workerPool *WorkerPool, logger *log.Logger) *NetworkReplicator {
return &NetworkReplicator{}
}
// ReplicationRetryConfig определяет конфигурацию повторных попыток для репликации.
// Содержит параметры экспоненциальной задержки и джиттера.
type ReplicationRetryConfig struct {
MaxRetries int // Максимальное количество попыток
InitialBackoff time.Duration // Начальная задержка между попытками
MaxBackoff time.Duration // Максимальная задержка
BackoffFactor float64 // Множитель для экспоненциального увеличения задержки
JitterEnabled bool // Включение случайного джиттера для предотвращения "thundering herd"
}
// DefaultReplicationRetryConfig возвращает конфигурацию по умолчанию.
// Настройки подобраны для баланса между надёжностью и производительностью.
func DefaultReplicationRetryConfig() *ReplicationRetryConfig {
return &ReplicationRetryConfig{
MaxRetries: 5,
InitialBackoff: 100 * time.Millisecond,
MaxBackoff: 10 * time.Second,
BackoffFactor: 2.0,
JitterEnabled: true,
}
}
// Close закрывает репликатор и освобождает ресурсы.
// В заглушке ничего не делает.
func (nr *NetworkReplicator) Close() error {
return nil
}
// GetStats возвращает статистику работы репликатора.
// В заглушке возвращает информацию о том, что репликация отключена.
func (nr *NetworkReplicator) GetStats() map[string]interface{} {
return map[string]interface{}{
"enabled": false,
"message": "NetworkReplicator is a stub - full implementation pending",
}
}
// Replicate выполняет репликацию данных на целевой узел.
// В заглушке всегда возвращает nil (успех).
func (nr *NetworkReplicator) Replicate(targetNodeID, targetAddress string, data []byte) error {
return nil
}
// =============================================================================
// ОСНОВНЫЕ ТИПЫ
// =============================================================================
// LoggerInterface определяет интерфейс для логирования.
// Используется для абстракции от конкретной реализации логгера.
type LoggerInterface interface {
Debug(msg string)
Info(msg string)
Warn(msg string)
Error(msg string)
Debugf(format string, args ...interface{})
Infof(format string, args ...interface{})
Warnf(format string, args ...interface{})
Errorf(format string, args ...interface{})
}
// NodeStatus представляет состояние узла кластера.
// Использует атомарные операции для потокобезопасного доступа.
type NodeStatus int32
const (
StatusOffline NodeStatus = iota // Узел отключён или недоступен
StatusActive // Узел активен и готов к работе
StatusSyncing // Узел синхронизируется с кластером
StatusFailed // Узел в состоянии ошибки
)
// NodeRequest представляет запрос между узлами кластера.
// Используется для передачи данных в формате JSON через TCP.
type NodeRequest struct {
Type string `json:"type"` // Тип запроса: replicate, query, sync, heartbeat, status_sync
FromNode string `json:"from_node"` // ID узла-отправителя
Data json.RawMessage `json:"data"` // Данные запроса в формате JSON
Timestamp int64 `json:"timestamp"` // Временная метка запроса (Unix millis)
}
// NodeInfo представляет информацию об узле в кластере.
// Используется для обмена метаданными между узлами.
type NodeInfo struct {
ID string `json:"id"` // Уникальный идентификатор узла
IP string `json:"ip"` // IP-адрес узла
Port int `json:"port"` // Порт для TCP-соединений
Status string `json:"status"` // Текущий статус узла
LastSeen int64 `json:"last_seen"` // Время последнего контакта (Unix millis)
JoinedAt int64 `json:"joined_at"` // Время присоединения к кластеру
UpdatedAt int64 `json:"updated_at"` // Время последнего обновления информации
Version int `json:"version"` // Версия узла для обнаружения изменений
}
// ShardInfo представляет информацию о шарде.
// Используется для управления распределением данных между узлами.
type ShardInfo struct {
ID string `json:"id"` // Уникальный идентификатор шарда
Name string `json:"name"` // Имя шарда
Nodes []string `json:"nodes"` // Список узлов, хранящих шард
LeaderNode string `json:"leader_node"` // Лидер шарда
Status string `json:"status"` // Статус шарда
CreatedAt int64 `json:"created_at"` // Время создания
UpdatedAt int64 `json:"updated_at"` // Время последнего обновления
LastRebalanced int64 `json:"last_rebalanced"` // Время последней перебалансировки
DocumentCount int64 `json:"document_count"` // Количество документов в шарде
SizeBytes int64 `json:"size_bytes"` // Размер шарда в байтах
}
// =============================================================================
// ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
// =============================================================================
// SafeGoWithLogger запускает горутину с защитой от паники.
// При панике горутина перезапускается с задержкой 5 секунд.
// Это обеспечивает самовосстановление критических компонентов.
//
// Параметры:
// - fn: функция для выполнения в горутине
// - logger: логгер для записи ошибок
// - name: имя горутины для идентификации в логах
func SafeGoWithLogger(fn func(), logger *log.Logger, name string) {
go func() {
defer func() {
if r := recover(); r != nil {
if logger != nil {
logger.Error(fmt.Sprintf("Goroutine %s panicked: %v\n%s", name, r, debug.Stack()))
}
time.Sleep(5 * time.Second)
SafeGoWithLogger(fn, logger, name)
}
}()
fn()
}()
}
// =============================================================================
// NODE - ОСНОВНАЯ СТРУКТУРА УЗЛА
// =============================================================================
// Node представляет узел в распределённом кластере.
// Каждый узел имеет свой TCP-сервер, хранилище данных и интеграцию с Raft-координатором.
//
// ПОТОКОВАЯ БЕЗОПАСНОСТЬ:
// - Status, lastSeen, joinedAt используют atomic для потокобезопасного доступа
// - mu защищает операции, изменяющие состояние узла
// - connPool использует sync.Map для потокобезопасного хранения соединений
// - routinesMu защищает доступ к recoverableRoutines
type Node struct {
// Основная информация об узле
ID string // Уникальный идентификатор узла (UUID)
IP string // IP-адрес для приёма соединений
Port int // Порт для TCP-сервера
// Состояние узла (атомарное для потокобезопасности)
Status atomic.Int32 // Текущий статус (NodeStatus)
// Хранилище и компоненты
Storage *storage.Storage // Хранилище данных
logger *log.Logger // Логгер для записи событий
coordinator *RaftCoordinator // Координатор для управления кластером (Raft)
// Временные метки (атомарные)
lastSeen atomic.Int64 // Время последнего полученного heartbeat
joinedAt atomic.Int64 // Время присоединения к кластеру
// Время жизненного цикла
createdAt int64 // Время создания узла
startedAt int64 // Время запуска TCP-сервера
stoppedAt int64 // Время остановки узла
// Сетевые компоненты
incomingConn chan net.Conn // Канал для входящих TCP-соединений (буфер 10000)
stopChan chan struct{} // Канал для остановки всех горутин
// Статистика (атомарная)
requestCount atomic.Uint64 // Счётчик обработанных запросов
bytesRx atomic.Uint64 // Количество полученных байт
bytesTx atomic.Uint64 // Количество отправленных байт
// Защита состояния
mu sync.RWMutex // Блокировка для операций изменения состояния
// Компоненты обработки
workerPool *WorkerPool // Пул воркеров для асинхронной обработки
replicator *NetworkReplicator // Репликатор для отправки данных
connPool sync.Map // Пул активных TCP-соединений
// Механизм восстановления после паники
panicRecoveryMgr *PanicRecoveryManager // Менеджер восстановления
recoverableRoutines map[string]*RecoverableRoutine // Зарегистрированные восстанавливаемые горутины
routinesMu sync.RWMutex // Защита доступа к recoverableRoutines
}
// NodeConfig представляет конфигурацию для создания узла.
// Используется для передачи зависимостей при создании.
type NodeConfig struct {
IP string // IP-адрес узла
Port int // Порт для TCP-сервера
Storage *storage.Storage // Хранилище данных
Logger *log.Logger // Логгер
Coordinator *RaftCoordinator // Raft-координатор
PanicRecoveryMgr *PanicRecoveryManager // Менеджер восстановления
}
// =============================================================================
// СОЗДАНИЕ УЗЛА
// =============================================================================
// NewNode создаёт новый узел с механизмом восстановления по умолчанию.
// Это упрощённый конструктор для случаев, когда менеджер восстановления не требуется.
//
// Параметры:
// - ip: IP-адрес для приёма соединений
// - port: порт для TCP-сервера
// - store: хранилище данных
// - logger: логгер
//
// Возвращает: указатель на созданный узел
func NewNode(ip string, port int, store *storage.Storage, logger *log.Logger) *Node {
return NewNodeWithRecovery(ip, port, store, logger, nil)
}
// NewNodeWithRecovery создаёт новый узел с поддержкой восстановления после паники.
// Это основной конструктор, который инициализирует все компоненты узла.
//
// ПОРЯДОК ИНИЦИАЛИЗАЦИИ:
// 1. Создание пула воркеров и репликатора
// 2. Инициализация структуры Node
// 3. Запуск восстанавливаемых горутин (если есть менеджер)
// 4. Запуск горутин с защитой от паники (если нет менеджера)
//
// Параметры:
// - ip: IP-адрес для приёма соединений
// - port: порт для TCP-сервера
// - store: хранилище данных
// - logger: логгер
// - panicRecoveryMgr: менеджер восстановления (может быть nil)
//
// Возвращает: указатель на созданный узел
func NewNodeWithRecovery(ip string, port int, store *storage.Storage, logger *log.Logger, panicRecoveryMgr *PanicRecoveryManager) *Node {
now := time.Now().UnixMilli()
// Создаём пул воркеров с максимальным количеством 500 задач
workerPool := NewWorkerPool(500, logger)
// Создаём репликатор с настройками по умолчанию
replicator := NewNetworkReplicator(DefaultReplicationRetryConfig(), workerPool, logger)
// Инициализируем структуру узла
node := &Node{
ID: uuid.New().String(), // Генерируем уникальный ID
IP: ip,
Port: port,
Storage: store,
logger: logger,
incomingConn: make(chan net.Conn, 10000), // Буфер на 10000 соединений
stopChan: make(chan struct{}),
createdAt: now,
startedAt: now,
workerPool: workerPool,
replicator: replicator,
panicRecoveryMgr: panicRecoveryMgr,
recoverableRoutines: make(map[string]*RecoverableRoutine),
}
// Устанавливаем начальный статус и временные метки
node.Status.Store(int32(StatusActive))
node.lastSeen.Store(now)
node.joinedAt.Store(0) // 0 означает "не присоединён"
// Запускаем горутины с механизмом восстановления или без него
if panicRecoveryMgr != nil {
// Используем восстанавливаемые горутины для критических компонентов
node.startRecoverableRoutine("TCPServer", node.startTCPServer)
node.startRecoverableRoutine("IncomingConnections", node.handleIncomingConnections)
node.startRecoverableRoutine("HeartbeatLoop", node.heartbeatLoop)
node.startRecoverableRoutine("ConnectionHealthMonitor", node.connectionHealthMonitor)
logger.Info(fmt.Sprintf("Node %s created with panic recovery (max workers: 500)", node.ID))
} else {
// Используем базовую защиту от паники с перезапуском
SafeGoWithLogger(node.startTCPServer, logger, "TCPServer")
SafeGoWithLogger(node.handleIncomingConnections, logger, "IncomingConnections")
SafeGoWithLogger(node.heartbeatLoop, logger, "HeartbeatLoop")
SafeGoWithLogger(node.connectionHealthMonitor, logger, "ConnectionHealthMonitor")
logger.Info(fmt.Sprintf("Node %s created at %s with lock-free worker pool (max: 500)", node.ID, node.GetCreatedAtStr()))
}
return node
}
// =============================================================================
// ВОССТАНАВЛИВАЕМЫЕ ГОРУТИНЫ
// =============================================================================
// startRecoverableRoutine запускает горутину с поддержкой восстановления.
// Если panicRecoveryMgr доступен, горутина будет автоматически перезапущена при панике.
// В противном случае используется базовый механизм SafeGoWithLogger.
//
// Параметры:
// - name: имя горутины для идентификации
// - fn: функция для выполнения
func (n *Node) startRecoverableRoutine(name string, fn func()) {
// Если менеджер восстановления недоступен, используем базовый механизм
if n.panicRecoveryMgr == nil {
SafeGoWithLogger(fn, n.logger, name)
return
}
// Блокируем доступ к карте восстанавливаемых горутин
n.routinesMu.Lock()
defer n.routinesMu.Unlock()
// Создаём новую восстанавливаемую горутину
routine := NewRecoverableRoutine(name, func() error {
fn()
return nil
}, n.panicRecoveryMgr, 10) // Максимум 10 перезапусков
// Сохраняем и запускаем
n.recoverableRoutines[name] = routine
routine.Start()
if n.logger != nil {
n.logger.Debug(fmt.Sprintf("Started recoverable routine: %s", name))
}
}
// stopRecoverableRoutine останавливает восстанавливаемую горутину.
// Используется при остановке узла для корректного завершения.
//
// Параметры:
// - name: имя горутины для остановки
func (n *Node) stopRecoverableRoutine(name string) {
n.routinesMu.RLock()
routine, ok := n.recoverableRoutines[name]
n.routinesMu.RUnlock()
if ok && routine != nil {
routine.Stop()
if n.logger != nil {
n.logger.Debug(fmt.Sprintf("Stopped recoverable routine: %s", name))
}
}
}
// =============================================================================
// TCP СЕРВЕР
// =============================================================================
// startTCPServer запускает TCP-сервер для приёма входящих соединений.
// Это основная точка входа для межузлового взаимодействия.
//
// ОСОБЕННОСТИ:
// - Использует deadline для предотвращения блокировки accept
// - Автоматически перезапускается при панике
// - Обрабатывает ошибки таймаута без остановки сервера
// - Передаёт соединения в канал incomingConn для асинхронной обработки
func (n *Node) startTCPServer() {
// Защита от паники с автоматическим перезапуском
defer func() {
if r := recover(); r != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("TCP server panicked: %v\n%s", r, debug.Stack()))
}
time.Sleep(5 * time.Second)
if n.panicRecoveryMgr != nil {
n.startRecoverableRoutine("TCPServer", n.startTCPServer)
} else {
SafeGoWithLogger(n.startTCPServer, n.logger, "TCPServer")
}
}
}()
// Формируем адрес и начинаем прослушивание
addr := fmt.Sprintf("%s:%d", n.IP, n.Port)
listener, err := net.Listen("tcp", addr)
if err != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Node %s failed to start TCP server: %v", n.ID, err))
}
n.Status.Store(int32(StatusFailed))
return
}
defer listener.Close()
if n.logger != nil {
n.logger.Info(fmt.Sprintf("Node %s listening on %s (started at %s)", n.ID, addr, n.GetStartedAtStr()))
}
// Основной цикл приёма соединений
for {
select {
case <-n.stopChan:
// Сигнал остановки получен
if n.logger != nil {
n.logger.Info(fmt.Sprintf("Node %s TCP server stopped", n.ID))
}
return
default:
// Устанавливаем deadline для таймаута accept (5 секунд)
// Это позволяет периодически проверять stopChan
if err := listener.(*net.TCPListener).SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
continue
}
// Принимаем новое соединение
conn, err := listener.Accept()
if err != nil {
// Проверяем, не является ли ошибка таймаутом (это нормально)
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Node %s accept error: %v", n.ID, err))
}
continue
}
// Устанавливаем таймауты для соединения
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
conn.SetWriteDeadline(time.Now().Add(30 * time.Second))
// Передаём соединение в канал для асинхронной обработки
select {
case n.incomingConn <- conn:
n.bytesRx.Add(1)
default:
// Канал переполнен - закрываем соединение
if n.logger != nil {
n.logger.Warn(fmt.Sprintf("Node %s incoming connection queue full, dropping connection", n.ID))
}
conn.Close()
}
}
}
}
// =============================================================================
// ОБРАБОТКА ВХОДЯЩИХ СОЕДИНЕНИЙ
// =============================================================================
// handleIncomingConnections обрабатывает входящие соединения из канала.
// Каждое соединение обрабатывается асинхронно через пул воркеров.
//
// РАБОТА С ПУЛОМ ВОРКЕРОВ:
// 1. Получение соединения из канала
// 2. Создание задачи с уникальным ID
// 3. Отправка задачи в пул воркеров
// 4. При ошибке отправки - закрытие соединения
func (n *Node) handleIncomingConnections() {
// Защита от паники с автоматическим перезапуском
defer func() {
if r := recover(); r != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Incoming connections handler panicked: %v\n%s", r, debug.Stack()))
}
time.Sleep(5 * time.Second)
if n.panicRecoveryMgr != nil {
n.startRecoverableRoutine("IncomingConnections", n.handleIncomingConnections)
} else {
SafeGoWithLogger(n.handleIncomingConnections, n.logger, "IncomingConnections")
}
}
}()
for {
select {
case <-n.stopChan:
return
case conn := <-n.incomingConn:
// Увеличиваем счётчик запросов
n.requestCount.Add(1)
// Создаём задачу для обработки соединения
taskID := fmt.Sprintf("handle_conn_%d_%s", time.Now().UnixNano(), conn.RemoteAddr().String())
err := n.workerPool.SubmitFunc(taskID, func() error {
n.handleNodeRequest(conn)
return nil
})
if err != nil {
if n.logger != nil {
n.logger.Warn(fmt.Sprintf("Failed to submit connection task: %v", err))
}
conn.Close()
}
}
}
}
// =============================================================================
// ОБРАБОТКА ЗАПРОСОВ
// =============================================================================
// handleNodeRequest обрабатывает один запрос от узла.
// Декодирует JSON-запрос и направляет его соответствующему обработчику.
//
// ТИПЫ ЗАПРОСОВ:
// - replicate: репликация документа
// - query: запрос документа
// - sync: синхронизация коллекции
// - heartbeat: проверка доступности узла
// - status_sync: синхронизация статуса кластера
func (n *Node) handleNodeRequest(conn net.Conn) {
// Защита от паники - всегда закрываем соединение
defer func() {
if r := recover(); r != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Request handler panicked: %v\n%s", r, debug.Stack()))
}
}
conn.Close()
}()
// Устанавливаем таймаут на чтение
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
// Декодируем запрос из JSON
decoder := json.NewDecoder(conn)
var req NodeRequest
if err := decoder.Decode(&req); err != nil {
if err != io.EOF && n.logger != nil {
n.logger.Error(fmt.Sprintf("Node %s failed to decode request: %v", n.ID, err))
}
return
}
// Обновляем время последнего контакта
n.lastSeen.Store(time.Now().UnixMilli())
if n.logger != nil {
n.logger.Debug(fmt.Sprintf("Node %s received request type %s from %s at %s",
n.ID, req.Type, req.FromNode, time.UnixMilli(req.Timestamp).Format("15:04:05.000")))
}
// Маршрутизация по типу запроса
switch req.Type {
case "replicate":
n.handleReplicateRequest(req.Data)
case "query":
n.handleQueryRequest(req.Data, conn)
case "sync":
n.handleSyncRequest(req.Data, conn)
case "heartbeat":
n.handleHeartbeatRequest(req, conn)
case "status_sync":
n.handleStatusSyncRequest(req, conn)
default:
if n.logger != nil {
n.logger.Warn(fmt.Sprintf("Node %s unknown request type: %s", n.ID, req.Type))
}
}
}
// handleReplicateRequest обрабатывает запрос на репликацию документа.
// Сохраняет полученный документ в локальном хранилище.
//
// ПОРЯДОК ОБРАБОТКИ:
// 1. Декодирование данных репликации
// 2. Получение базы данных и коллекции
// 3. Создание документа с полями
// 4. Вставка документа в коллекцию
// 5. Логирование результата
func (n *Node) handleReplicateRequest(data []byte) {
// Защита от паники
defer func() {
if r := recover(); r != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Replicate request handler panicked: %v\n%s", r, debug.Stack()))
}
}
}()
startTime := time.Now().UnixMilli()
// Структура данных репликации
var repData struct {
Database string `json:"database"` // Имя базы данных
Collection string `json:"collection"` // Имя коллекции
Document map[string]interface{} `json:"document"` // Данные документа
SourceNode string `json:"source_node"` // Узел-источник
ReplicaID string `json:"replica_id"` // Уникальный ID репликации
}
if err := json.Unmarshal(data, &repData); err != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Node %s failed to unmarshal replicate data: %v", n.ID, err))
}
return
}
// Получаем базу данных
db, err := n.Storage.GetDatabase(repData.Database)
if err != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Node %s database not found for replication: %s", n.ID, repData.Database))
}
return
}
// Получаем коллекцию
coll, err := db.GetCollection(repData.Collection)
if err != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Node %s collection not found for replication: %s", n.ID, repData.Collection))
}
return
}
// Извлекаем ID документа
docID, ok := repData.Document["_id"].(string)
if !ok {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Node %s document missing _id field", n.ID))
}
return
}
// Создаём документ с полученными полями
doc := storage.NewDocumentWithID(docID)
for k, v := range repData.Document {
doc.SetField(k, v)
}
// Вставляем документ в коллекцию
if err := coll.Insert(doc); err != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Node %s failed to replicate document: %v", n.ID, err))
}
} else {
// Логируем успешную репликацию
duration := time.Now().UnixMilli() - startTime
if n.logger != nil {
n.logger.Debug(fmt.Sprintf("Node %s replicated document %s from %s (took %d ms)",
n.ID, doc.ID, repData.SourceNode, duration))
}
}
}
// handleQueryRequest обрабатывает запрос на получение документа.
// Находит документ по ID и возвращает его в ответе.
//
// ПОРЯДОК ОБРАБОТКИ:
// 1. Декодирование данных запроса
// 2. Получение базы данных и коллекции
// 3. Поиск документа по ID
// 4. Формирование успешного или ошибочного ответа
func (n *Node) handleQueryRequest(data []byte, conn net.Conn) {
// Защита от паники с отправкой ошибки
defer func() {
if r := recover(); r != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Query request handler panicked: %v\n%s", r, debug.Stack()))
}
n.sendErrorResponse(conn, "Internal server error")
}
}()
startTime := time.Now().UnixMilli()
// Структура данных запроса
var queryData struct {
Database string `json:"database"` // Имя базы данных
Collection string `json:"collection"` // Имя коллекции
DocumentID string `json:"document_id"` // ID документа
RequestID string `json:"request_id"` // ID запроса для трекинга
}
if err := json.Unmarshal(data, &queryData); err != nil {
n.sendErrorResponse(conn, err.Error())
return
}
// Получаем базу данных
db, err := n.Storage.GetDatabase(queryData.Database)
if err != nil {
n.sendErrorResponse(conn, err.Error())
return
}
// Получаем коллекцию
coll, err := db.GetCollection(queryData.Collection)
if err != nil {
n.sendErrorResponse(conn, err.Error())
return
}
// Ищем документ
doc, err := coll.Find(queryData.DocumentID)
if err != nil {
n.sendErrorResponse(conn, err.Error())
return
}
duration := time.Now().UnixMilli() - startTime
// Формируем успешный ответ
response := map[string]interface{}{
"status": "success",
"data": doc,
"node_id": n.ID,
"request_id": queryData.RequestID,
"duration_ms": duration,
"timestamp": time.Now().UnixMilli(),
}
// Отправляем ответ
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
encoder := json.NewEncoder(conn)
if err := encoder.Encode(response); err == nil {
responseData, _ := json.Marshal(response)
n.bytesTx.Add(uint64(len(responseData)))
}
if n.logger != nil {
n.logger.Debug(fmt.Sprintf("Node %s handled query for %s.%s:%s (took %d ms)",
n.ID, queryData.Database, queryData.Collection, queryData.DocumentID, duration))
}
}
// handleSyncRequest обрабатывает запрос на синхронизацию коллекции.
// Возвращает все документы коллекции (или только изменённые после указанного времени).
//
// ПОРЯДОК ОБРАБОТКИ:
// 1. Декодирование данных запроса
// 2. Получение базы данных и коллекции
// 3. Фильтрация документов по времени (если указано)
// 4. Формирование ответа со всеми документами
func (n *Node) handleSyncRequest(data []byte, conn net.Conn) {
// Защита от паники с отправкой ошибки
defer func() {
if r := recover(); r != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Sync request handler panicked: %v\n%s", r, debug.Stack()))
}
n.sendErrorResponse(conn, "Internal server error")
}
}()
startTime := time.Now().UnixMilli()
// Структура данных запроса синхронизации
var syncData struct {
Database string `json:"database"` // Имя базы данных
Collection string `json:"collection"` // Имя коллекции
RequestID string `json:"request_id"` // ID запроса
Since int64 `json:"since"` // Временная метка для инкрементальной синхронизации
}
if err := json.Unmarshal(data, &syncData); err != nil {
n.sendErrorResponse(conn, err.Error())
return
}
// Получаем базу данных
db, err := n.Storage.GetDatabase(syncData.Database)
if err != nil {
n.sendErrorResponse(conn, err.Error())
return
}
// Получаем коллекцию
coll, err := db.GetCollection(syncData.Collection)
if err != nil {
n.sendErrorResponse(conn, err.Error())
return
}
// Получаем все документы
docs := coll.GetAllDocuments()
// Фильтруем по времени, если указано
if syncData.Since > 0 {
filtered := make([]*storage.Document, 0)
for _, doc := range docs {
if doc.UpdatedAt > syncData.Since {
filtered = append(filtered, doc)
}
}
docs = filtered
}
duration := time.Now().UnixMilli() - startTime
// Формируем ответ
response := map[string]interface{}{
"status": "success",
"docs": docs,
"count": len(docs),
"node_id": n.ID,
"request_id": syncData.RequestID,
"duration_ms": duration,
"timestamp": time.Now().UnixMilli(),
"sync_duration_ms": duration,
}
// Отправляем ответ
conn.SetWriteDeadline(time.Now().Add(30 * time.Second))
encoder := json.NewEncoder(conn)
if err := encoder.Encode(response); err != nil && n.logger != nil {
n.logger.Error(fmt.Sprintf("Failed to send sync response: %v", err))
}
if n.logger != nil {
n.logger.Info(fmt.Sprintf("Node %s synced %d documents from %s.%s (took %d ms)",
n.ID, len(docs), syncData.Database, syncData.Collection, duration))
}
}
// handleHeartbeatRequest обрабатывает heartbeat-запрос.
// Подтверждает, что узел жив, и возвращает информацию о статусе.
//
// ОТВЕТ СОДЕРЖИТ:
// - status: "alive" - подтверждение жизни
// - node_id: ID текущего узла
// - timestamp: время обработки
// - uptime_ms: время работы узла в миллисекундах
func (n *Node) handleHeartbeatRequest(req NodeRequest, conn net.Conn) {
// Защита от паники
defer func() {
if r := recover(); r != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Heartbeat handler panicked: %v\n%s", r, debug.Stack()))
}
}
}()
// Обновляем время последнего контакта
n.lastSeen.Store(time.Now().UnixMilli())
// Формируем ответ
response := map[string]interface{}{
"status": "alive",
"node_id": n.ID,
"timestamp": time.Now().UnixMilli(),
"uptime_ms": time.Now().UnixMilli() - n.startedAt,
}
// Отправляем ответ
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
encoder := json.NewEncoder(conn)
encoder.Encode(response)
}
// handleStatusSyncRequest обрабатывает запрос на синхронизацию статуса кластера.
// Обновляет информацию о лидере и термине через координатор.
//
// ПОРЯДОК ОБРАБОТКИ:
// 1. Декодирование данных статуса
// 2. Передача данных координатору
// 3. Формирование ответа с текущим статусом
func (n *Node) handleStatusSyncRequest(req NodeRequest, conn net.Conn) {
// Защита от паники с отправкой ошибки
defer func() {
if r := recover(); r != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Status sync handler panicked: %v\n%s", r, debug.Stack()))
}
n.sendErrorResponse(conn, "Internal server error")
}
}()
// Структура данных статуса
var syncStatus struct {
LeaderID string `json:"leader_id"` // ID лидера
Term uint64 `json:"term"` // Текущий термин Raft
ClusterSize int `json:"cluster_size"` // Размер кластера
}
if err := json.Unmarshal(req.Data, &syncStatus); err != nil {
n.sendErrorResponse(conn, err.Error())
return
}
// Передаём статус координатору
if n.coordinator != nil {
n.coordinator.HandleStatusSync(syncStatus.LeaderID, syncStatus.Term, syncStatus.ClusterSize)
}
// Формируем ответ
response := map[string]interface{}{
"status": "synced",
"node_id": n.ID,
"term": n.coordinator.GetCurrentTerm(),
"is_leader": n.coordinator.IsLeader(),
"timestamp": time.Now().UnixMilli(),
}
// Отправляем ответ
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
encoder := json.NewEncoder(conn)
encoder.Encode(response)
}
// sendErrorResponse отправляет ответ об ошибке клиенту.
// Используется для единообразного форматирования ошибок.
//
// Параметры:
// - conn: TCP-соединение
// - errMsg: текст ошибки
func (n *Node) sendErrorResponse(conn net.Conn, errMsg string) {
response := map[string]interface{}{
"status": "error",
"error": errMsg,
"node_id": n.ID,
"timestamp": time.Now().UnixMilli(),
}
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
encoder := json.NewEncoder(conn)
encoder.Encode(response)
}
// =============================================================================
// HEARTBEAT И МОНИТОРИНГ
// =============================================================================
// heartbeatLoop периодически отправляет heartbeat в кластер.
// Использует координатор для отправки heartbeat-сообщений.
//
// ИНТЕРВАЛ: 5 секунд
// УСЛОВИЕ: запускается только если координатор доступен
func (n *Node) heartbeatLoop() {
// Защита от паники с автоматическим перезапуском
defer func() {
if r := recover(); r != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Heartbeat loop panicked: %v\n%s", r, debug.Stack()))
}
time.Sleep(5 * time.Second)
if n.panicRecoveryMgr != nil {
n.startRecoverableRoutine("HeartbeatLoop", n.heartbeatLoop)
} else {
SafeGoWithLogger(n.heartbeatLoop, n.logger, "HeartbeatLoop")
}
}
}()
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-n.stopChan:
return
case <-ticker.C:
// Отправляем heartbeat через координатор
if n.coordinator != nil {
n.coordinator.SendHeartbeat(n.ID)
n.lastSeen.Store(time.Now().UnixMilli())
if n.logger != nil {
n.logger.Debug(fmt.Sprintf("Node %s sent heartbeat at %s", n.ID, n.GetLastSeenStr()))
}
}
}
}
}
// connectionHealthMonitor периодически проверяет здоровье активных соединений.
// Закрывает "мёртвые" соединения и удаляет их из пула.
//
// ИНТЕРВАЛ: 30 секунд
// МЕТОД ПРОВЕРКИ: чтение 1 байта с таймаутом 1 секунда
func (n *Node) connectionHealthMonitor() {
// Защита от паники с перезапуском
defer func() {
if r := recover(); r != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Connection health monitor panicked: %v\n%s", r, debug.Stack()))
}
SafeGoWithLogger(n.connectionHealthMonitor, n.logger, "ConnectionHealthMonitor")
}
}()
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-n.stopChan:
return
case <-ticker.C:
n.cleanupStaleConnections()
}
}
}
// cleanupStaleConnections закрывает "зависшие" соединения.
// Проверяет каждое соединение в пуле и удаляет недоступные.
func (n *Node) cleanupStaleConnections() {
// Защита от паники
defer func() {
if r := recover(); r != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Cleanup connections panicked: %v", r))
}
}
}()
n.connPool.Range(func(key, value interface{}) bool {
if conn, ok := value.(net.Conn); ok {
// Проверяем соединение с таймаутом 1 секунда
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
buf := make([]byte, 1)
_, err := conn.Read(buf)
if err != nil {
// Соединение недоступно - закрываем
conn.Close()
n.connPool.Delete(key)
if n.logger != nil {
n.logger.Debug(fmt.Sprintf("Cleaned up stale connection for %v", key))
}
}
}
return true
})
}
// =============================================================================
// УПРАВЛЕНИЕ СТАТУСОМ УЗЛА
// =============================================================================
// GetNodeStatus возвращает текущий статус узла.
// Потокобезопасный доступ через атомарную операцию.
func (n *Node) GetNodeStatus() NodeStatus {
return NodeStatus(n.Status.Load())
}
// IsActive проверяет, активен ли узел.
// Возвращает true только для статуса StatusActive.
func (n *Node) IsActive() bool {
return NodeStatus(n.Status.Load()) == StatusActive
}
// SetStatus устанавливает новый статус узла.
// При изменении статуса уведомляет координатор (если узел является лидером).
//
// ПОТОКОВАЯ БЕЗОПАСНОСТЬ:
// - Использует мьютекс для предотвращения гонок
// - Атомарно обновляет статус через Store
//
// Параметры:
// - status: новый статус узла
//
// Возвращает: ошибку, если не удалось обновить статус через координатор
func (n *Node) SetStatus(status NodeStatus) error {
n.mu.Lock()
defer n.mu.Unlock()
oldStatus := n.Status.Load()
if oldStatus == int32(status) {
return nil // Статус не изменился
}
// Если узел является лидером, обновляем статус через координатор
if n.coordinator != nil && n.coordinator.IsLeader() {
if err := n.coordinator.UpdateNodeStatus(n.ID, status); err != nil {
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Failed to update node status via Raft: %v", err))
}
return err
}
}
// Обновляем статус
n.Status.Store(int32(status))
if n.logger != nil {
n.logger.Info(fmt.Sprintf("Node %s status changed from %d to %d at %s",
n.ID, oldStatus, status, time.Now().Format("2006-01-02 15:04:05.000")))
}
return nil
}
// =============================================================================
// УПРАВЛЕНИЕ КЛАСТЕРОМ
// =============================================================================
// SetCoordinator устанавливает координатор для узла.
// Также обновляет время присоединения к кластеру.
//
// Параметры:
// - coord: Raft-координатор
func (n *Node) SetCoordinator(coord *RaftCoordinator) {
n.coordinator = coord
now := time.Now().UnixMilli()
n.joinedAt.Store(now)
if n.logger != nil {
n.logger.Info(fmt.Sprintf("Node %s joined cluster at %s", n.ID, n.GetJoinedAtStr()))
}
}
// JoinCluster присоединяет узел к кластеру.
// Регистрирует узел в координаторе и устанавливает активный статус.
//
// ПОРЯДОК ДЕЙСТВИЙ:
// 1. Проверка, что узел ещё не присоединён
// 2. Установка координатора
// 3. Регистрация узла в координаторе
// 4. Установка статуса Active
//
// Параметры:
// - coord: Raft-координатор
//
// Возвращает: ошибку, если присоединение не удалось
func (n *Node) JoinCluster(coord *RaftCoordinator) error {
if n.coordinator != nil {
return fmt.Errorf("node already joined to cluster")
}
n.SetCoordinator(coord)
if err := coord.RegisterNode(n); err != nil {
return fmt.Errorf("failed to register node: %v", err)
}
if err := n.SetStatus(StatusActive); err != nil {
return fmt.Errorf("failed to set active status: %v", err)
}
if n.logger != nil {
n.logger.Info(fmt.Sprintf("Node %s successfully joined cluster at %s", n.ID, n.GetJoinedAtStr()))
}
return nil
}
// LeaveCluster отключает узел от кластера.
// Устанавливает оффлайн-статус и удаляет узел из координатора.
//
// ПОРЯДОК ДЕЙСТВИЙ:
// 1. Установка статуса Offline
// 2. Удаление узла из координатора
// 3. Очистка координатора и времени присоединения
//
// Возвращает: ошибку, если отключение не удалось
func (n *Node) LeaveCluster() error {
if n.coordinator == nil {
return fmt.Errorf("node not in cluster")
}
if err := n.SetStatus(StatusOffline); err != nil {
n.logger.Warn(fmt.Sprintf("Failed to set offline status: %v", err))
}
if err := n.coordinator.RemoveNode(n.ID); err != nil {
n.logger.Warn(fmt.Sprintf("Failed to remove node from coordinator: %v", err))
}
n.coordinator = nil
n.joinedAt.Store(0)
if n.logger != nil {
n.logger.Info(fmt.Sprintf("Node %s left cluster at %s", n.ID, time.Now().Format("2006-01-02 15:04:05.000")))
}
return nil
}
// =============================================================================
// ИНФОРМАЦИЯ О ВРЕМЕНИ
// =============================================================================
// GetLastSeen возвращает время последнего контакта в миллисекундах.
func (n *Node) GetLastSeen() int64 {
return n.lastSeen.Load()
}
// GetLastSeenStr возвращает строковое представление времени последнего контакта.
func (n *Node) GetLastSeenStr() string {
lastSeen := n.lastSeen.Load()
if lastSeen == 0 {
return "never"
}
return time.UnixMilli(lastSeen).Format("2006-01-02 15:04:05.000")
}
// GetJoinedAt возвращает время присоединения к кластеру в миллисекундах.
func (n *Node) GetJoinedAt() int64 {
return n.joinedAt.Load()
}
// GetJoinedAtStr возвращает строковое представление времени присоединения.
func (n *Node) GetJoinedAtStr() string {
joinedAt := n.joinedAt.Load()
if joinedAt == 0 {
return "not joined"
}
return time.UnixMilli(joinedAt).Format("2006-01-02 15:04:05.000")
}
// GetStartedAt возвращает время запуска TCP-сервера в миллисекундах.
func (n *Node) GetStartedAt() int64 {
return n.startedAt
}
// GetStartedAtStr возвращает строковое представление времени запуска.
func (n *Node) GetStartedAtStr() string {
return time.UnixMilli(n.startedAt).Format("2006-01-02 15:04:05.000")
}
// GetCreatedAt возвращает время создания узла в миллисекундах.
func (n *Node) GetCreatedAt() int64 {
return n.createdAt
}
// GetCreatedAtStr возвращает строковое представление времени создания.
func (n *Node) GetCreatedAtStr() string {
return time.UnixMilli(n.createdAt).Format("2006-01-02 15:04:05.000")
}
// GetUptime возвращает продолжительность работы узла.
func (n *Node) GetUptime() time.Duration {
if n.startedAt == 0 {
return 0
}
return time.Duration(time.Now().UnixMilli()-n.startedAt) * time.Millisecond
}
// GetAddress возвращает адрес узла в формате "IP:PORT".
func (n *Node) GetAddress() string {
return fmt.Sprintf("%s:%d", n.IP, n.Port)
}
// =============================================================================
// СТАТИСТИКА
// =============================================================================
// GetStats возвращает полную статистику узла.
// Включает информацию о статусе, времени, нагрузке и компонентах.
func (n *Node) GetStats() map[string]interface{} {
stats := map[string]interface{}{
"id": n.ID,
"ip": n.IP,
"port": n.Port,
"status": n.GetNodeStatus(),
"created_at": n.GetCreatedAtStr(),
"started_at": n.GetStartedAtStr(),
"joined_at": n.GetJoinedAtStr(),
"last_seen": n.GetLastSeenStr(),
"uptime": n.GetUptime().String(),
"request_count": n.requestCount.Load(),
"bytes_rx": n.bytesRx.Load(),
"bytes_tx": n.bytesTx.Load(),
}
// Добавляем статистику пула воркеров, если доступен
if n.workerPool != nil {
stats["worker_pool"] = n.workerPool.GetStats()
}
// Добавляем статистику репликации, если доступна
if n.replicator != nil {
stats["replication"] = n.replicator.GetStats()
}
// Добавляем информацию о восстанавливаемых горутинах
if n.panicRecoveryMgr != nil {
n.routinesMu.RLock()
routineStatus := make(map[string]bool)
for name, routine := range n.recoverableRoutines {
routineStatus[name] = routine.IsRunning()
}
n.routinesMu.RUnlock()
stats["recoverable_routines"] = routineStatus
stats["panic_recovery_stats"] = n.panicRecoveryMgr.GetStats()
}
return stats
}
// GetWorkerPoolStats возвращает статистику пула воркеров.
func (n *Node) GetWorkerPoolStats() map[string]interface{} {
if n.workerPool == nil {
return map[string]interface{}{"enabled": false}
}
return n.workerPool.GetStats()
}
// GetReplicationStats возвращает статистику репликации.
func (n *Node) GetReplicationStats() map[string]interface{} {
if n.replicator == nil {
return map[string]interface{}{"enabled": false}
}
return n.replicator.GetStats()
}
// GetPanicRecoveryStats возвращает статистику механизма восстановления.
func (n *Node) GetPanicRecoveryStats() map[string]interface{} {
if n.panicRecoveryMgr == nil {
return map[string]interface{}{"enabled": false}
}
return n.panicRecoveryMgr.GetStats()
}
// =============================================================================
// РЕПЛИКАЦИЯ ДОКУМЕНТОВ
// =============================================================================
// generateReplicationID генерирует уникальный ID для операции репликации.
// Используется для отслеживания и дедупликации репликаций.
func generateReplicationID() string {
bytes := make([]byte, 16)
rand.Read(bytes)
return base64.URLEncoding.EncodeToString(bytes)
}
// ReplicateDocument реплицирует документ на все активные узлы кластера.
//
// ПОРЯДОК ДЕЙСТВИЙ:
// 1. Получение списка активных узлов от координатора
// 2. Формирование данных репликации
// 3. Асинхронная отправка на каждый узел (через пул воркеров)
// 4. Ожидание завершения всех репликаций с таймаутом
// 5. Возврат результата (успех/частичный успех/ошибка)
//
// Параметры:
// - database: имя базы данных
// - collection: имя коллекции
// - doc: документ для репликации
//
// Возвращает: ошибку, если репликация не удалась
func (n *Node) ReplicateDocument(database, collection string, doc *storage.Document) error {
if n.coordinator == nil {
if n.logger != nil {
n.logger.Warn("No coordinator set, skipping replication")
}
return fmt.Errorf("no coordinator set")
}
// Получаем список активных узлов
nodes := n.coordinator.GetActiveNodes()
if len(nodes) <= 1 {
if n.logger != nil {
n.logger.Debug("No other nodes for replication")
}
return nil // Только текущий узел - репликация не требуется
}
// Формируем данные для репликации
repData := struct {
Database string `json:"database"`
Collection string `json:"collection"`
Document map[string]interface{} `json:"document"`
SourceNode string `json:"source_node"`
ReplicaID string `json:"replica_id"`
}{
Database: database,
Collection: collection,
Document: doc.GetFields(),
SourceNode: n.ID,
ReplicaID: generateReplicationID(),
}
data, err := json.Marshal(repData)
if err != nil {
return fmt.Errorf("failed to marshal replication data: %v", err)
}
startTime := time.Now().UnixMilli()
// Отправляем репликацию на все узлы, кроме текущего
var wg sync.WaitGroup
var failedCount atomic.Int32
var successCount atomic.Int32
for _, nodeInfo := range nodes {
if nodeInfo.ID == n.ID {
continue // Пропускаем себя
}
wg.Add(1)
targetNodeID := nodeInfo.ID
targetAddress := fmt.Sprintf("%s:%d", nodeInfo.IP, nodeInfo.Port)
// Создаём задачу для каждого узла
taskID := fmt.Sprintf("replicate_%s_to_%s_%s", doc.ID, targetNodeID, repData.ReplicaID)
err := n.workerPool.SubmitFunc(taskID, func() error {
defer wg.Done()
if n.replicator == nil {
failedCount.Add(1)
return fmt.Errorf("replicator not initialized")
}
// Выполняем репликацию
err := n.replicator.Replicate(targetNodeID, targetAddress, data)
if err != nil {
failedCount.Add(1)
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Failed to replicate document %s to node %s after retries: %v",
doc.ID, targetNodeID, err))
}
return err
}
successCount.Add(1)
if n.logger != nil {
n.logger.Debug(fmt.Sprintf("Successfully replicated document %s to node %s", doc.ID, targetNodeID))
}
return nil
})
if err != nil {
wg.Done()
failedCount.Add(1)
if n.logger != nil {
n.logger.Error(fmt.Sprintf("Failed to submit replication task for %s to %s: %v",
doc.ID, targetNodeID, err))
}
}
}
// Ожидаем завершения всех репликаций с таймаутом 30 секунд
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
duration := time.Now().UnixMilli() - startTime
if n.logger != nil {
n.logger.Info(fmt.Sprintf("Replicated document %s to %d/%d nodes (took %d ms)",
doc.ID, successCount.Load(), len(nodes)-1, duration))
}
case <-time.After(30 * time.Second):
if n.logger != nil {
n.logger.Warn(fmt.Sprintf("Replication timeout for document %s after %d ms", doc.ID, time.Now().UnixMilli()-startTime))
}
return fmt.Errorf("replication timeout")
}
// Проверяем результат
if failedCount.Load() > 0 {
return fmt.Errorf("replication partially failed: %d of %d nodes failed", failedCount.Load(), len(nodes)-1)
}
return nil
}
// =============================================================================
// УПРАВЛЕНИЕ PANIC RECOVERY
// =============================================================================
// SetPanicRecoveryManager устанавливает менеджер восстановления после паники.
// Позволяет включить расширенный механизм восстановления после создания узла.
//
// Параметры:
// - mgr: менеджер восстановления
func (n *Node) SetPanicRecoveryManager(mgr *PanicRecoveryManager) {
n.panicRecoveryMgr = mgr
if n.logger != nil {
n.logger.Debug("Panic recovery manager set for node")
}
}
// =============================================================================
// МЕТОДЫ ДЛЯ GOSSIP И SELF-HEALING
// =============================================================================
// GetGossipManager возвращает gossip менеджер узла
func (n *Node) GetGossipManager() *GossipManager {
if n.coordinator != nil {
return n.coordinator.GetGossipManager()
}
return nil
}
// GetSelfHealingManager возвращает менеджер самоисцеления узла
func (n *Node) GetSelfHealingManager() *SelfHealingManager {
if n.coordinator != nil {
return n.coordinator.GetSelfHealingManager()
}
return nil
}
// GetDynamicConfigManager возвращает менеджер динамической конфигурации узла
func (n *Node) GetDynamicConfigManager() *config.DynamicConfigManager {
if n.coordinator != nil {
return n.coordinator.GetDynamicConfigManager()
}
return nil
}
// =============================================================================
// ОСТАНОВКА УЗЛА
// =============================================================================
// Stop останавливает узел и все его компоненты.
// Выполняет корректное завершение всех горутин и освобождение ресурсов.
//
// ПОРЯДОК ОСТАНОВКИ:
// 1. Обновление статуса на Offline
// 2. Остановка всех восстанавливаемых горутин
// 3. Закрытие канала stopChan (сигнал для всех горутин)
// 4. Остановка пула воркеров
// 5. Закрытие репликатора
// 6. Закрытие всех активных TCP-соединений
// 7. Логирование остановки
func (n *Node) Stop() {
// Обновляем статус перед остановкой
if n.coordinator != nil && n.coordinator.IsLeader() {
n.SetStatus(StatusOffline)
}
n.Status.Store(int32(StatusOffline))
n.stoppedAt = time.Now().UnixMilli()
// Останавливаем все восстанавливаемые горутины
n.routinesMu.RLock()
for name, routine := range n.recoverableRoutines {
routine.Stop()
if n.logger != nil {
n.logger.Debug(fmt.Sprintf("Stopped recoverable routine: %s", name))
}
}
n.routinesMu.RUnlock()
// Закрываем канал остановки
close(n.stopChan)
// Останавливаем пул воркеров
if n.workerPool != nil {
n.workerPool.Stop()
}
// Закрываем репликатор
if n.replicator != nil {
n.replicator.Close()
}
// Закрываем все активные соединения
n.connPool.Range(func(key, value interface{}) bool {
if conn, ok := value.(net.Conn); ok {
conn.Close()
}
return true
})
if n.logger != nil {
n.logger.Info(fmt.Sprintf("Node %s stopped at %s", n.ID, time.UnixMilli(n.stoppedAt).Format("2006-01-02 15:04:05.000")))
}
}