522 lines
12 KiB
Go
522 lines
12 KiB
Go
// 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<<uint(qm.RetryCount-1))
|
|
qm.NextRetry = time.Now().Add(delay)
|
|
log.Printf("[灵泊] 消息失败(将重试): %s (重试: %d/%d, 下次: %v)",
|
|
msgID, qm.RetryCount, qm.MaxRetries, delay)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("message %s not found", msgID)
|
|
}
|
|
|
|
// Size 队列大小
|
|
func (mq *MessageQueue) Size() int {
|
|
mq.mu.RLock()
|
|
defer mq.mu.RUnlock()
|
|
return len(mq.messages)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Message Buffer - 消息缓冲
|
|
// =============================================================================
|
|
|
|
// MessageBuffer 消息缓冲(暂存离线消息)
|
|
type MessageBuffer struct {
|
|
mu sync.RWMutex
|
|
buffers map[string][]*types.UnifiedSynapseMessage // agent_id -> 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<<uint(entry.RetryCount-1))
|
|
if delay > 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)
|
|
}
|