Upload files to "internal/cluster"
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
// Назначение: Реализация узла кластера (node) для распределённой СУБД с поддержкой временных меток.
|
// Назначение: Реализация узла кластера (node) для распределённой СУБД с поддержкой временных меток.
|
||||||
// Полностью lock-free с использованием атомарных операций.
|
// Полностью lock-free с использованием атомарных операций.
|
||||||
// Новая функциональность: Автоматическое восстановление после паники горутин.
|
// Новая функциональность: Автоматическое восстановление после паники горутин.
|
||||||
|
// Lock-free: WorkerPool полностью lock-free, остальные компоненты оптимизированы.
|
||||||
|
|
||||||
package cluster
|
package cluster
|
||||||
|
|
||||||
@@ -42,6 +43,68 @@ const (
|
|||||||
StatusFailed
|
StatusFailed
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NodeRequest представляет запрос между узлами
|
||||||
|
type NodeRequest struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
FromNode string `json:"from_node"`
|
||||||
|
Data json.RawMessage `json:"data"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeInfo представляет информацию об узле в кластере
|
||||||
|
type NodeInfo struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
LastSeen int64 `json:"last_seen"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeGoWithLogger безопасно запускает горутину с обработкой паники
|
||||||
|
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 представляет отдельный узел в распределённой системе
|
||||||
type Node struct {
|
type Node struct {
|
||||||
ID string // Уникальный идентификатор узла
|
ID string // Уникальный идентификатор узла
|
||||||
@@ -63,14 +126,13 @@ type Node struct {
|
|||||||
bytesTx atomic.Uint64 // Отправлено байт
|
bytesTx atomic.Uint64 // Отправлено байт
|
||||||
mu sync.RWMutex // Для синхронизации изменений статуса
|
mu sync.RWMutex // Для синхронизации изменений статуса
|
||||||
|
|
||||||
// Новые компоненты для production-ready реализации
|
// Lock-free компоненты
|
||||||
workerPool *WorkerPool // Пул воркеров для ограничения горутин
|
workerPool *WorkerPool // Lock-free пул воркеров
|
||||||
replicator *NetworkReplicator // Сетевой репликатор с retry и backoff
|
replicator *NetworkReplicator // Сетевой репликатор с retry и backoff
|
||||||
connPool sync.Map // Пул соединений к другим узлам
|
connPool sync.Map // Пул соединений к другим узлам (lock-free)
|
||||||
|
|
||||||
// НОВЫЕ КОМПОНЕНТЫ ДЛЯ ВОССТАНОВЛЕНИЯ ПОСЛЕ ПАНИКИ
|
panicRecoveryMgr *PanicRecoveryManager
|
||||||
panicRecoveryMgr *PanicRecoveryManager // Менеджер восстановления после паник
|
recoverableRoutines map[string]*RecoverableRoutine
|
||||||
recoverableRoutines map[string]*RecoverableRoutine // Карта восстанавливаемых горутин
|
|
||||||
routinesMu sync.RWMutex
|
routinesMu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +155,7 @@ func NewNode(ip string, port int, store *storage.Storage, logger *log.Logger) *N
|
|||||||
func NewNodeWithRecovery(ip string, port int, store *storage.Storage, logger *log.Logger, panicRecoveryMgr *PanicRecoveryManager) *Node {
|
func NewNodeWithRecovery(ip string, port int, store *storage.Storage, logger *log.Logger, panicRecoveryMgr *PanicRecoveryManager) *Node {
|
||||||
now := time.Now().UnixMilli()
|
now := time.Now().UnixMilli()
|
||||||
|
|
||||||
// Создаём пул воркеров с ограничением (максимум 500 одновременных горутин)
|
// Создаём lock-free пул воркеров
|
||||||
workerPool := NewWorkerPool(500, logger)
|
workerPool := NewWorkerPool(500, logger)
|
||||||
|
|
||||||
// Создаём сетевой репликатор с настройками retry и backoff
|
// Создаём сетевой репликатор с настройками retry и backoff
|
||||||
@@ -105,7 +167,7 @@ func NewNodeWithRecovery(ip string, port int, store *storage.Storage, logger *lo
|
|||||||
Port: port,
|
Port: port,
|
||||||
Storage: store,
|
Storage: store,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
incomingConn: make(chan net.Conn, 10000), // Увеличенный буфер
|
incomingConn: make(chan net.Conn, 10000),
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
startedAt: now,
|
startedAt: now,
|
||||||
@@ -118,9 +180,7 @@ func NewNodeWithRecovery(ip string, port int, store *storage.Storage, logger *lo
|
|||||||
node.lastSeen.Store(now)
|
node.lastSeen.Store(now)
|
||||||
node.joinedAt.Store(0)
|
node.joinedAt.Store(0)
|
||||||
|
|
||||||
// ========== НОВАЯ ФУНКЦИОНАЛЬНОСТЬ: Безопасный запуск горутин с восстановлением ==========
|
|
||||||
if panicRecoveryMgr != nil {
|
if panicRecoveryMgr != nil {
|
||||||
// Используем восстанавливаемые горутины через PanicRecoveryManager
|
|
||||||
node.startRecoverableRoutine("TCPServer", node.startTCPServer)
|
node.startRecoverableRoutine("TCPServer", node.startTCPServer)
|
||||||
node.startRecoverableRoutine("IncomingConnections", node.handleIncomingConnections)
|
node.startRecoverableRoutine("IncomingConnections", node.handleIncomingConnections)
|
||||||
node.startRecoverableRoutine("HeartbeatLoop", node.heartbeatLoop)
|
node.startRecoverableRoutine("HeartbeatLoop", node.heartbeatLoop)
|
||||||
@@ -128,13 +188,12 @@ func NewNodeWithRecovery(ip string, port int, store *storage.Storage, logger *lo
|
|||||||
|
|
||||||
logger.Info(fmt.Sprintf("Node %s created with panic recovery (max workers: 500)", node.ID))
|
logger.Info(fmt.Sprintf("Node %s created with panic recovery (max workers: 500)", node.ID))
|
||||||
} else {
|
} else {
|
||||||
// Безопасный запуск сервера с обработкой паник (fallback)
|
|
||||||
SafeGoWithLogger(node.startTCPServer, logger, "TCPServer")
|
SafeGoWithLogger(node.startTCPServer, logger, "TCPServer")
|
||||||
SafeGoWithLogger(node.handleIncomingConnections, logger, "IncomingConnections")
|
SafeGoWithLogger(node.handleIncomingConnections, logger, "IncomingConnections")
|
||||||
SafeGoWithLogger(node.heartbeatLoop, logger, "HeartbeatLoop")
|
SafeGoWithLogger(node.heartbeatLoop, logger, "HeartbeatLoop")
|
||||||
SafeGoWithLogger(node.connectionHealthMonitor, logger, "ConnectionHealthMonitor")
|
SafeGoWithLogger(node.connectionHealthMonitor, logger, "ConnectionHealthMonitor")
|
||||||
|
|
||||||
logger.Info(fmt.Sprintf("Node %s created at %s with worker pool (max: 500)", node.ID, node.GetCreatedAtStr()))
|
logger.Info(fmt.Sprintf("Node %s created at %s with lock-free worker pool (max: 500)", node.ID, node.GetCreatedAtStr()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return node
|
return node
|
||||||
@@ -150,11 +209,10 @@ func (n *Node) startRecoverableRoutine(name string, fn func()) {
|
|||||||
n.routinesMu.Lock()
|
n.routinesMu.Lock()
|
||||||
defer n.routinesMu.Unlock()
|
defer n.routinesMu.Unlock()
|
||||||
|
|
||||||
// Создаём восстанавливаемую горутину
|
|
||||||
routine := NewRecoverableRoutine(name, func() error {
|
routine := NewRecoverableRoutine(name, func() error {
|
||||||
fn()
|
fn()
|
||||||
return nil
|
return nil
|
||||||
}, n.panicRecoveryMgr, 10) // максимум 10 перезапусков
|
}, n.panicRecoveryMgr, 10)
|
||||||
|
|
||||||
n.recoverableRoutines[name] = routine
|
n.recoverableRoutines[name] = routine
|
||||||
routine.Start()
|
routine.Start()
|
||||||
@@ -185,10 +243,8 @@ func (n *Node) startTCPServer() {
|
|||||||
if n.logger != nil {
|
if n.logger != nil {
|
||||||
n.logger.Error(fmt.Sprintf("TCP server panicked: %v\n%s", r, debug.Stack()))
|
n.logger.Error(fmt.Sprintf("TCP server panicked: %v\n%s", r, debug.Stack()))
|
||||||
}
|
}
|
||||||
// Перезапускаем сервер с задержкой
|
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
// Используем восстанавливаемый запуск
|
|
||||||
if n.panicRecoveryMgr != nil {
|
if n.panicRecoveryMgr != nil {
|
||||||
n.startRecoverableRoutine("TCPServer", n.startTCPServer)
|
n.startRecoverableRoutine("TCPServer", n.startTCPServer)
|
||||||
} else {
|
} else {
|
||||||
@@ -220,7 +276,6 @@ func (n *Node) startTCPServer() {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
// Устанавливаем таймаут для Accept
|
|
||||||
if err := listener.(*net.TCPListener).SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
|
if err := listener.(*net.TCPListener).SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -236,7 +291,6 @@ func (n *Node) startTCPServer() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Устанавливаем таймауты на соединение
|
|
||||||
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
||||||
conn.SetWriteDeadline(time.Now().Add(30 * time.Second))
|
conn.SetWriteDeadline(time.Now().Add(30 * time.Second))
|
||||||
|
|
||||||
@@ -253,14 +307,13 @@ func (n *Node) startTCPServer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleIncomingConnections обрабатывает входящие соединения с использованием пула воркеров
|
// handleIncomingConnections обрабатывает входящие соединения с использованием lock-free пула воркеров
|
||||||
func (n *Node) handleIncomingConnections() {
|
func (n *Node) handleIncomingConnections() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
if n.logger != nil {
|
if n.logger != nil {
|
||||||
n.logger.Error(fmt.Sprintf("Incoming connections handler panicked: %v\n%s", r, debug.Stack()))
|
n.logger.Error(fmt.Sprintf("Incoming connections handler panicked: %v\n%s", r, debug.Stack()))
|
||||||
}
|
}
|
||||||
// Перезапускаем обработчик с задержкой
|
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
if n.panicRecoveryMgr != nil {
|
if n.panicRecoveryMgr != nil {
|
||||||
@@ -277,7 +330,6 @@ func (n *Node) handleIncomingConnections() {
|
|||||||
return
|
return
|
||||||
case conn := <-n.incomingConn:
|
case conn := <-n.incomingConn:
|
||||||
n.requestCount.Add(1)
|
n.requestCount.Add(1)
|
||||||
// Используем пул воркеров вместо создания неограниченного количества горутин
|
|
||||||
taskID := fmt.Sprintf("handle_conn_%d_%s", time.Now().UnixNano(), conn.RemoteAddr().String())
|
taskID := fmt.Sprintf("handle_conn_%d_%s", time.Now().UnixNano(), conn.RemoteAddr().String())
|
||||||
err := n.workerPool.SubmitFunc(taskID, func() error {
|
err := n.workerPool.SubmitFunc(taskID, func() error {
|
||||||
n.handleNodeRequest(conn)
|
n.handleNodeRequest(conn)
|
||||||
@@ -304,7 +356,6 @@ func (n *Node) handleNodeRequest(conn net.Conn) {
|
|||||||
conn.Close()
|
conn.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Устанавливаем таймаут на чтение
|
|
||||||
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
||||||
|
|
||||||
decoder := json.NewDecoder(conn)
|
decoder := json.NewDecoder(conn)
|
||||||
@@ -384,7 +435,6 @@ func (n *Node) handleReplicateRequest(data []byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Безопасное получение ID документа
|
|
||||||
docID, ok := repData.Document["_id"].(string)
|
docID, ok := repData.Document["_id"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
if n.logger != nil {
|
if n.logger != nil {
|
||||||
@@ -393,10 +443,10 @@ func (n *Node) handleReplicateRequest(data []byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
doc := &storage.Document{
|
doc := storage.NewDocumentWithID(docID)
|
||||||
ID: docID,
|
for k, v := range repData.Document {
|
||||||
Fields: repData.Document,
|
doc.SetField(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := coll.Insert(doc); err != nil {
|
if err := coll.Insert(doc); err != nil {
|
||||||
if n.logger != nil {
|
if n.logger != nil {
|
||||||
@@ -576,7 +626,7 @@ func (n *Node) handleHeartbeatRequest(req NodeRequest, conn net.Conn) {
|
|||||||
encoder.Encode(response)
|
encoder.Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleStatusSyncRequest обрабатывает запрос синхронизации статуса (для split-brain recovery)
|
// handleStatusSyncRequest обрабатывает запрос синхронизации статуса
|
||||||
func (n *Node) handleStatusSyncRequest(req NodeRequest, conn net.Conn) {
|
func (n *Node) handleStatusSyncRequest(req NodeRequest, conn net.Conn) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@@ -636,7 +686,6 @@ func (n *Node) heartbeatLoop() {
|
|||||||
if n.logger != nil {
|
if n.logger != nil {
|
||||||
n.logger.Error(fmt.Sprintf("Heartbeat loop panicked: %v\n%s", r, debug.Stack()))
|
n.logger.Error(fmt.Sprintf("Heartbeat loop panicked: %v\n%s", r, debug.Stack()))
|
||||||
}
|
}
|
||||||
// Перезапускаем heartbeat loop с задержкой
|
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
if n.panicRecoveryMgr != nil {
|
if n.panicRecoveryMgr != nil {
|
||||||
@@ -702,7 +751,6 @@ func (n *Node) cleanupStaleConnections() {
|
|||||||
|
|
||||||
n.connPool.Range(func(key, value interface{}) bool {
|
n.connPool.Range(func(key, value interface{}) bool {
|
||||||
if conn, ok := value.(net.Conn); ok {
|
if conn, ok := value.(net.Conn); ok {
|
||||||
// Проверяем, активно ли соединение
|
|
||||||
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||||
buf := make([]byte, 1)
|
buf := make([]byte, 1)
|
||||||
_, err := conn.Read(buf)
|
_, err := conn.Read(buf)
|
||||||
@@ -896,7 +944,6 @@ func (n *Node) GetStats() map[string]interface{} {
|
|||||||
stats["replication"] = n.replicator.GetStats()
|
stats["replication"] = n.replicator.GetStats()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Новая статистика о восстанавливаемых горутинах
|
|
||||||
if n.panicRecoveryMgr != nil {
|
if n.panicRecoveryMgr != nil {
|
||||||
n.routinesMu.RLock()
|
n.routinesMu.RLock()
|
||||||
routineStatus := make(map[string]bool)
|
routineStatus := make(map[string]bool)
|
||||||
@@ -919,7 +966,6 @@ func (n *Node) Stop() {
|
|||||||
n.Status.Store(int32(StatusOffline))
|
n.Status.Store(int32(StatusOffline))
|
||||||
n.stoppedAt = time.Now().UnixMilli()
|
n.stoppedAt = time.Now().UnixMilli()
|
||||||
|
|
||||||
// Останавливаем все восстанавливаемые горутины
|
|
||||||
n.routinesMu.RLock()
|
n.routinesMu.RLock()
|
||||||
for name, routine := range n.recoverableRoutines {
|
for name, routine := range n.recoverableRoutines {
|
||||||
routine.Stop()
|
routine.Stop()
|
||||||
@@ -939,7 +985,6 @@ func (n *Node) Stop() {
|
|||||||
n.replicator.Close()
|
n.replicator.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Закрываем все соединения в пуле
|
|
||||||
n.connPool.Range(func(key, value interface{}) bool {
|
n.connPool.Range(func(key, value interface{}) bool {
|
||||||
if conn, ok := value.(net.Conn); ok {
|
if conn, ok := value.(net.Conn); ok {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
@@ -964,7 +1009,7 @@ func generateReplicationID() string {
|
|||||||
return base64.URLEncoding.EncodeToString(bytes)
|
return base64.URLEncoding.EncodeToString(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplicateDocument отправляет документ на репликацию всем активным узлам с retry и backoff
|
// ReplicateDocument отправляет документ на репликацию всем активным узлам
|
||||||
func (n *Node) ReplicateDocument(database, collection string, doc *storage.Document) error {
|
func (n *Node) ReplicateDocument(database, collection string, doc *storage.Document) error {
|
||||||
if n.coordinator == nil {
|
if n.coordinator == nil {
|
||||||
if n.logger != nil {
|
if n.logger != nil {
|
||||||
@@ -981,7 +1026,6 @@ func (n *Node) ReplicateDocument(database, collection string, doc *storage.Docum
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Подготавливаем данные для репликации
|
|
||||||
repData := struct {
|
repData := struct {
|
||||||
Database string `json:"database"`
|
Database string `json:"database"`
|
||||||
Collection string `json:"collection"`
|
Collection string `json:"collection"`
|
||||||
@@ -1003,7 +1047,6 @@ func (n *Node) ReplicateDocument(database, collection string, doc *storage.Docum
|
|||||||
|
|
||||||
startTime := time.Now().UnixMilli()
|
startTime := time.Now().UnixMilli()
|
||||||
|
|
||||||
// Отправляем на все узлы, кроме себя
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var failedCount atomic.Int32
|
var failedCount atomic.Int32
|
||||||
var successCount atomic.Int32
|
var successCount atomic.Int32
|
||||||
@@ -1017,7 +1060,6 @@ func (n *Node) ReplicateDocument(database, collection string, doc *storage.Docum
|
|||||||
targetNodeID := nodeInfo.ID
|
targetNodeID := nodeInfo.ID
|
||||||
targetAddress := fmt.Sprintf("%s:%d", nodeInfo.IP, nodeInfo.Port)
|
targetAddress := fmt.Sprintf("%s:%d", nodeInfo.IP, nodeInfo.Port)
|
||||||
|
|
||||||
// Асинхронная репликация с повторными попытками через пул воркеров
|
|
||||||
taskID := fmt.Sprintf("replicate_%s_to_%s_%s", doc.ID, targetNodeID, repData.ReplicaID)
|
taskID := fmt.Sprintf("replicate_%s_to_%s_%s", doc.ID, targetNodeID, repData.ReplicaID)
|
||||||
err := n.workerPool.SubmitFunc(taskID, func() error {
|
err := n.workerPool.SubmitFunc(taskID, func() error {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
@@ -1054,7 +1096,6 @@ func (n *Node) ReplicateDocument(database, collection string, doc *storage.Docum
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ждём завершения всех репликаций с таймаутом
|
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|||||||
Reference in New Issue
Block a user