240 lines
6.3 KiB
Go
240 lines
6.3 KiB
Go
// 织忆 MemoryWeave — 分布式同步 + CRDT + 速率限制
|
||
package distributed
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"fmt"
|
||
"log"
|
||
"net/http"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// ─── 事件传播(HTTP 替代 Redis Streams)─────────────────
|
||
|
||
type Event struct {
|
||
ID string `json:"id"`
|
||
Type string `json:"type"`
|
||
AgentID string `json:"agent_id"`
|
||
Payload json.RawMessage `json:"payload"`
|
||
Timestamp time.Time `json:"timestamp"`
|
||
}
|
||
|
||
type EventBus struct {
|
||
mu sync.RWMutex
|
||
handlers map[string]func(Event)
|
||
peers []string // 其他织忆实例的地址
|
||
httpClient *http.Client
|
||
}
|
||
|
||
func NewEventBus(peers []string) *EventBus {
|
||
return &EventBus{
|
||
handlers: make(map[string]func(Event)),
|
||
peers: peers,
|
||
httpClient: &http.Client{Timeout: 5 * time.Second},
|
||
}
|
||
}
|
||
|
||
// Subscribe 订阅事件
|
||
func (eb *EventBus) Subscribe(eventType string, handler func(Event)) {
|
||
eb.mu.Lock()
|
||
defer eb.mu.Unlock()
|
||
eb.handlers[eventType] = handler
|
||
}
|
||
|
||
// Publish 发布本地事件并广播到所有 peer
|
||
func (eb *EventBus) Publish(event Event) {
|
||
// 广播到 peers
|
||
for _, peer := range eb.peers {
|
||
go eb.sendToPeer(peer, event)
|
||
}
|
||
// 触发本地 handler
|
||
eb.mu.RLock()
|
||
if h, ok := eb.handlers[event.Type]; ok {
|
||
go h(event)
|
||
}
|
||
eb.mu.RUnlock()
|
||
}
|
||
|
||
func (eb *EventBus) sendToPeer(peer string, event Event) {
|
||
body, _ := json.Marshal(event)
|
||
resp, err := eb.httpClient.Post(
|
||
"http://"+peer+"/api/v1/events/receive",
|
||
"application/json",
|
||
bytes.NewReader(body))
|
||
if err != nil {
|
||
log.Printf("[eventbus] peer %s unreachable: %v", peer, err)
|
||
return
|
||
}
|
||
resp.Body.Close()
|
||
}
|
||
|
||
// POST /api/v1/events/receive — 接收 peer 事件
|
||
func (eb *EventBus) ReceiveHandler(w http.ResponseWriter, r *http.Request) {
|
||
var event Event
|
||
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
|
||
http.Error(w, "invalid event", 400)
|
||
return
|
||
}
|
||
eb.mu.RLock()
|
||
if h, ok := eb.handlers[event.Type]; ok {
|
||
go h(event)
|
||
}
|
||
eb.mu.RUnlock()
|
||
w.WriteHeader(200)
|
||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||
}
|
||
|
||
// ─── CRDT 合并 ──────────────────────────────────────────
|
||
|
||
type CRDTMerge struct {
|
||
mu sync.RWMutex
|
||
}
|
||
|
||
// Merge 两条记录冲突时合并(时间戳 + 来源优先级)
|
||
func (c *CRDTMerge) Merge(a, b map[string]interface{}) map[string]interface{} {
|
||
aTime := parseTime(a["updated_at"])
|
||
bTime := parseTime(b["updated_at"])
|
||
|
||
var winner, loser map[string]interface{}
|
||
if aTime.After(bTime) {
|
||
winner, loser = a, b
|
||
} else {
|
||
winner, loser = b, a
|
||
}
|
||
|
||
// 来源优先级:牧尘修正 > 时间戳
|
||
if isMuchenCorrection(loser) {
|
||
winner, loser = loser, winner
|
||
}
|
||
|
||
// 标记合并历史
|
||
winner["merged_from"] = loser["id"]
|
||
winner["merge_reason"] = "crdt_merge"
|
||
winner["merged_at"] = time.Now().Format(time.RFC3339)
|
||
|
||
return winner
|
||
}
|
||
|
||
func parseTime(v interface{}) time.Time {
|
||
switch t := v.(type) {
|
||
case time.Time:
|
||
return t
|
||
case string:
|
||
parsed, _ := time.Parse(time.RFC3339, t)
|
||
return parsed
|
||
}
|
||
return time.Now()
|
||
}
|
||
|
||
func isMuchenCorrection(record map[string]interface{}) bool {
|
||
source, ok := record["source"].(string)
|
||
return ok && (source == "muchen_oral" || source == "muchen_correction")
|
||
}
|
||
|
||
// ─── 速率限制(令牌桶)───────────────────────────────────
|
||
|
||
type RateLimiter struct {
|
||
mu sync.RWMutex
|
||
buckets map[string]*tokenBucket
|
||
rate float64 // 令牌/秒
|
||
burst int
|
||
}
|
||
|
||
type tokenBucket struct {
|
||
tokens float64
|
||
lastTime time.Time
|
||
}
|
||
|
||
func NewRateLimiter(rate float64, burst int) *RateLimiter {
|
||
return &RateLimiter{
|
||
buckets: make(map[string]*tokenBucket),
|
||
rate: rate,
|
||
burst: burst,
|
||
}
|
||
}
|
||
|
||
// Allow 检查指定 agent 是否允许本次请求
|
||
func (rl *RateLimiter) Allow(agentID string) bool {
|
||
rl.mu.Lock()
|
||
defer rl.mu.Unlock()
|
||
|
||
bucket, ok := rl.buckets[agentID]
|
||
if !ok {
|
||
bucket = &tokenBucket{tokens: float64(rl.burst), lastTime: time.Now()}
|
||
rl.buckets[agentID] = bucket
|
||
}
|
||
|
||
// 补充令牌
|
||
elapsed := time.Since(bucket.lastTime).Seconds()
|
||
bucket.tokens += elapsed * rl.rate
|
||
if bucket.tokens > float64(rl.burst) {
|
||
bucket.tokens = float64(rl.burst)
|
||
}
|
||
bucket.lastTime = time.Now()
|
||
|
||
if bucket.tokens >= 1 {
|
||
bucket.tokens--
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
// ─── 健康检查 ────────────────────────────────────────────
|
||
|
||
type HealthChecker struct {
|
||
mu sync.RWMutex
|
||
started time.Time
|
||
peers map[string]time.Time // peer → last_seen
|
||
}
|
||
|
||
func NewHealthChecker(peers []string) *HealthChecker {
|
||
h := &HealthChecker{
|
||
started: time.Now(),
|
||
peers: make(map[string]time.Time),
|
||
}
|
||
for _, p := range peers {
|
||
h.peers[p] = time.Time{}
|
||
}
|
||
return h
|
||
}
|
||
|
||
func (h *HealthChecker) Uptime() string { return time.Since(h.started).String() }
|
||
|
||
// ─── Prometheus 指标端点 ─────────────────────────────────
|
||
|
||
type MetricsHandler struct {
|
||
dashboard interface{ Metrics() map[string]float64 }
|
||
stats func() map[string]interface{}
|
||
}
|
||
|
||
func NewMetricsHandler(dash interface{ Metrics() map[string]float64 }, statsFn func() map[string]interface{}) *MetricsHandler {
|
||
return &MetricsHandler{dashboard: dash, stats: statsFn}
|
||
}
|
||
|
||
func (mh *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "text/plain; version=0.0.4")
|
||
|
||
metrics := mh.dashboard.Metrics()
|
||
s := mh.stats()
|
||
|
||
fmt.Fprintf(w, "# HELP zhiyi_recall_usefulness_rate Recall有用率\n")
|
||
fmt.Fprintf(w, "# TYPE zhiyi_recall_usefulness_rate gauge\n")
|
||
fmt.Fprintf(w, "zhiyi_recall_usefulness_rate %f\n", metrics["recall_usefulness_rate"])
|
||
|
||
fmt.Fprintf(w, "# HELP zhiyi_recall_hit_rate Recall命中率\n")
|
||
fmt.Fprintf(w, "# TYPE zhiyi_recall_hit_rate gauge\n")
|
||
fmt.Fprintf(w, "zhiyi_recall_hit_rate %f\n", metrics["recall_hit_rate"])
|
||
|
||
fmt.Fprintf(w, "# HELP zhiyi_gap_closure_rate 缺口闭环率\n")
|
||
fmt.Fprintf(w, "# TYPE zhiyi_gap_closure_rate gauge\n")
|
||
fmt.Fprintf(w, "zhiyi_gap_closure_rate %f\n", metrics["gap_closure_rate"])
|
||
|
||
fmt.Fprintf(w, "# HELP zhiyi_episodes_total 总episode数\n")
|
||
fmt.Fprintf(w, "# TYPE zhiyi_episodes_total gauge\n")
|
||
if epCount, ok := s["episodes"]; ok {
|
||
fmt.Fprintf(w, "zhiyi_episodes_total %v\n", epCount)
|
||
}
|
||
}
|