// Package harbor 灵泊 - 异步队列与暂存层 // // "泊"为船舶停靠。数据包如远行之舟, // 遇风浪(网络抖动)时暂泊于此, // 待风平浪静(目标就绪)再启航。 // 不丢包、不超时,给予 Agent 异步协作的从容。 package harbor import ( "context" "encoding/json" "fmt" "log" "sync" "time" "synapse/pkg/types" ) // ============================================================================= // Message Queue - 消息队列 // ============================================================================= // QueueType 队列类型 type QueueType string const ( QueueFIFO QueueType = "fifo" // 先进先出 QueuePriority QueueType = "priority" // 优先级队列 QueueLIFO QueueType = "lifo" // 后进先出 ) // QueuedMessage 队列中的消息 type QueuedMessage struct { ID string Message *types.UnifiedSynapseMessage Priority int EnqueueTime time.Time RetryCount int MaxRetries int NextRetry time.Time Status QueuedStatus Target string // 目标 endpoint } // QueuedStatus 队列消息状态 type QueuedStatus string const ( QueuedPending QueuedStatus = "pending" QueuedProcessing QueuedStatus = "processing" QueuedDelivered QueuedStatus = "delivered" QueuedFailed QueuedStatus = "failed" QueuedDead QueuedStatus = "dead" ) // MessageQueue 消息队列 type MessageQueue struct { mu sync.RWMutex messages []*QueuedMessage config *QueueConfig } // QueueConfig 队列配置 type QueueConfig struct { MaxSize int // 最大队列大小 DefaultMaxRetries int // 默认最大重试次数 DefaultRetryDelay time.Duration // 默认重试延迟 } // DefaultQueueConfig 返回默认队列配置 func DefaultQueueConfig() *QueueConfig { return &QueueConfig{ MaxSize: 10000, DefaultMaxRetries: 5, DefaultRetryDelay: 30 * time.Second, } } // NewMessageQueue 创建消息队列 func NewMessageQueue(config *QueueConfig) *MessageQueue { if config == nil { config = DefaultQueueConfig() } return &MessageQueue{ messages: make([]*QueuedMessage, 0), config: config, } } // Enqueue 入队 func (mq *MessageQueue) Enqueue(msg *types.UnifiedSynapseMessage, target string, priority int) (*QueuedMessage, error) { mq.mu.Lock() defer mq.mu.Unlock() if len(mq.messages) >= mq.config.MaxSize { return nil, fmt.Errorf("queue is full") } qm := &QueuedMessage{ ID: fmt.Sprintf("q-%d", time.Now().UnixNano()), Message: msg, Priority: priority, EnqueueTime: time.Now(), RetryCount: 0, MaxRetries: mq.config.DefaultMaxRetries, NextRetry: time.Now(), Status: QueuedPending, Target: target, } // 按优先级插入 inserted := false for i, m := range mq.messages { if priority > m.Priority { mq.messages = append(mq.messages[:i], append([]*QueuedMessage{qm}, mq.messages[i:]...)...) inserted = true break } } if !inserted { mq.messages = append(mq.messages, qm) } log.Printf("[灵泊] 消息入队: %s (目标: %s, 优先级: %d)", qm.ID, target, priority) return qm, nil } // Dequeue 出队 func (mq *MessageQueue) Dequeue() (*QueuedMessage, error) { mq.mu.Lock() defer mq.mu.Unlock() now := time.Now() for i, qm := range mq.messages { if qm.Status == QueuedPending && now.After(qm.NextRetry) { qm.Status = QueuedProcessing // 移到待处理 mq.messages = append(mq.messages[:i], mq.messages[i+1:]...) return qm, nil } } return nil, fmt.Errorf("no messages available") } // MarkDelivered 标记为已送达 func (mq *MessageQueue) MarkDelivered(msgID string) error { mq.mu.Lock() defer mq.mu.Unlock() for _, qm := range mq.messages { if qm.ID == msgID { qm.Status = QueuedDelivered log.Printf("[灵泊] 消息送达: %s", msgID) return nil } } return fmt.Errorf("message %s not found", msgID) } // MarkFailed 标记为失败(会重试) func (mq *MessageQueue) MarkFailed(msgID string, err error) error { mq.mu.Lock() defer mq.mu.Unlock() for _, qm := range mq.messages { if qm.ID == msgID { qm.RetryCount++ if qm.RetryCount >= qm.MaxRetries { qm.Status = QueuedDead log.Printf("[灵泊] 消息失败(已死): %s (重试: %d次)", msgID, qm.RetryCount) } else { qm.Status = QueuedPending // 指数退避 delay := mq.config.DefaultRetryDelay * time.Duration(1< messages config *BufferConfig } // BufferConfig 缓冲配置 type BufferConfig struct { MaxPerAgent int // 每个 Agent 最大缓冲数 MaxAge time.Duration // 消息最大存活时间 CleanupInterval time.Duration // 清理间隔 } // DefaultBufferConfig 返回默认缓冲配置 func DefaultBufferConfig() *BufferConfig { return &BufferConfig{ MaxPerAgent: 1000, MaxAge: 24 * time.Hour, CleanupInterval: 1 * time.Hour, } } // NewMessageBuffer 创建消息缓冲 func NewMessageBuffer(config *BufferConfig) *MessageBuffer { if config == nil { config = DefaultBufferConfig() } mb := &MessageBuffer{ buffers: make(map[string][]*types.UnifiedSynapseMessage), config: config, } // 启动清理协程 go mb.cleanupLoop() return mb } // BufferMessage 缓冲消息 func (mb *MessageBuffer) BufferMessage(agentID string, msg *types.UnifiedSynapseMessage) error { mb.mu.Lock() defer mb.mu.Unlock() buf, ok := mb.buffers[agentID] if !ok { buf = make([]*types.UnifiedSynapseMessage, 0) } if len(buf) >= mb.config.MaxPerAgent { // 丢弃最旧的 buf = buf[1:] } mb.buffers[agentID] = append(buf, msg) log.Printf("[灵泊] 缓冲消息: %s -> %s (缓冲区大小: %d)", msg.Sender.AgentID, agentID, len(mb.buffers[agentID])) return nil } // FlushBuffer 清空缓冲并返回所有消息 func (mb *MessageBuffer) FlushBuffer(agentID string) []*types.UnifiedSynapseMessage { mb.mu.Lock() defer mb.mu.Unlock() buf, ok := mb.buffers[agentID] if !ok { return []*types.UnifiedSynapseMessage{} } delete(mb.buffers, agentID) log.Printf("[灵泊] 清空缓冲: %s (消息数: %d)", agentID, len(buf)) return buf } // GetBufferSize 获取缓冲大小 func (mb *MessageBuffer) GetBufferSize(agentID string) int { mb.mu.RLock() defer mb.mu.RUnlock() return len(mb.buffers[agentID]) } // cleanupLoop 清理过期消息 func (mb *MessageBuffer) cleanupLoop() { ticker := time.NewTicker(mb.config.CleanupInterval) defer ticker.Stop() for range ticker.C { mb.cleanup() } } func (mb *MessageBuffer) cleanup() { mb.mu.Lock() defer mb.mu.Unlock() now := time.Now() cleaned := 0 for agentID, buf := range mb.buffers { var newBuf []*types.UnifiedSynapseMessage for _, msg := range buf { msgTime, _ := time.Parse(time.RFC3339, string(msg.Timestamp)) if now.Sub(msgTime) < mb.config.MaxAge { newBuf = append(newBuf, msg) } else { cleaned++ } } mb.buffers[agentID] = newBuf } if cleaned > 0 { log.Printf("[灵泊] 清理过期消息: %d 条", cleaned) } } // ============================================================================= // Retry Manager - 重试管理器 // ============================================================================= // RetryManager 重试管理器 type RetryManager struct { mu sync.RWMutex retries map[string]*RetryEntry config *RetryConfig } // RetryEntry 重试条目 type RetryEntry struct { ID string Message *types.UnifiedSynapseMessage Target string RetryCount int MaxRetries int NextRetry time.Time LastError string BackoffBase time.Duration } // RetryConfig 重试配置 type RetryConfig struct { MaxRetries int BaseDelay time.Duration MaxDelay time.Duration JitterFactor float64 } // DefaultRetryConfig 返回默认重试配置 func DefaultRetryConfig() *RetryConfig { return &RetryConfig{ MaxRetries: 5, BaseDelay: 1 * time.Second, MaxDelay: 5 * time.Minute, JitterFactor: 0.1, } } // NewRetryManager 创建重试管理器 func NewRetryManager(config *RetryConfig) *RetryManager { if config == nil { config = DefaultRetryConfig() } return &RetryManager{ retries: make(map[string]*RetryEntry), config: config, } } // AddRetry 添加重试条目 func (rm *RetryManager) AddRetry(msg *types.UnifiedSynapseMessage, target string) { rm.mu.Lock() defer rm.mu.Unlock() id := fmt.Sprintf("retry-%d", time.Now().UnixNano()) entry := &RetryEntry{ ID: id, Message: msg, Target: target, RetryCount: 0, MaxRetries: rm.config.MaxRetries, NextRetry: time.Now().Add(rm.config.BaseDelay), BackoffBase: rm.config.BaseDelay, } rm.retries[id] = entry log.Printf("[灵泊] 添加重试: %s -> %s (最多 %d 次)", id, target, rm.config.MaxRetries) } // GetPendingRetries 获取待重试的条目 func (rm *RetryManager) GetPendingRetries() []*RetryEntry { rm.mu.RLock() defer rm.mu.RUnlock() now := time.Now() var pending []*RetryEntry for _, entry := range rm.retries { if now.After(entry.NextRetry) && entry.RetryCount < entry.MaxRetries { pending = append(pending, entry) } } return pending } // MarkSuccess 标记成功 func (rm *RetryManager) MarkSuccess(retryID string) { rm.mu.Lock() defer rm.mu.Unlock() delete(rm.retries, retryID) log.Printf("[灵泊] 重试成功: %s", retryID) } // MarkFailure 标记失败 func (rm *RetryManager) MarkFailure(retryID string, err error) bool { rm.mu.Lock() defer rm.mu.Unlock() entry, ok := rm.retries[retryID] if !ok { return false } entry.RetryCount++ entry.LastError = err.Error() if entry.RetryCount >= entry.MaxRetries { delete(rm.retries, retryID) log.Printf("[灵泊] 重试失败(已达上限): %s (重试 %d 次)", retryID, entry.RetryCount) return false } // 指数退避 + 抖动 delay := entry.BackoffBase * time.Duration(1< rm.config.MaxDelay { delay = rm.config.MaxDelay } entry.NextRetry = time.Now().Add(delay) log.Printf("[灵泊] 重试失败: %s (%d/%d, 下次: %v)", retryID, entry.RetryCount, entry.MaxRetries, delay) return true } // Stats 获取统计信息 func (rm *RetryManager) Stats() (pending, failed int) { rm.mu.RLock() defer rm.mu.RUnlock() for _, entry := range rm.retries { if entry.RetryCount == 0 { pending++ } } return pending, 0 } // ============================================================================= // Harbor - 灵泊整体入口 // ============================================================================= // Harbor 灵泊 - 异步队列与暂存 type Harbor struct { Queue *MessageQueue Buffer *MessageBuffer Retry *RetryManager } // NewHarbor 创建灵泊 func NewHarbor() *Harbor { return &Harbor{ Queue: NewMessageQueue(nil), Buffer: NewMessageBuffer(nil), Retry: NewRetryManager(nil), } } // SendWithRetry 发送消息并重试 func (h *Harbor) SendWithRetry(ctx context.Context, msg *types.UnifiedSynapseMessage, target string, sendFunc func(ctx context.Context, msg *types.UnifiedSynapseMessage, target string) error) error { // 先入队 qm, err := h.Queue.Enqueue(msg, target, 5) if err != nil { return err } // 尝试发送 err = sendFunc(ctx, msg, target) if err == nil { h.Queue.MarkDelivered(qm.ID) return nil } // 失败,加入重试 h.Retry.AddRetry(msg, target) h.Queue.MarkFailed(qm.ID, err) return err } // BufferForOffline 为离线 Agent 缓冲消息 func (h *Harbor) BufferForOffline(agentID string, msg *types.UnifiedSynapseMessage) { h.Buffer.BufferMessage(agentID, msg) } // FlushAndSend 清空缓冲并发送 func (h *Harbor) FlushAndSend(agentID string, sendFunc func(msg *types.UnifiedSynapseMessage) error) int { msgs := h.Buffer.FlushBuffer(agentID) sent := 0 for _, msg := range msgs { if err := sendFunc(msg); err == nil { sent++ } } return sent } // ToJSON 导出为 JSON func (qm *QueuedMessage) ToJSON() string { data, _ := json.MarshalIndent(qm, "", " ") return string(data) }