memoryweave/go/internal/storage/redis.go

468 lines
11 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.

// 织忆 MemoryWeave — Redis 原生客户端RESP 协议,零外部依赖)
package storage
import (
"bufio"
"errors"
"fmt"
"io"
"log"
"net"
"os"
"strconv"
"strings"
"sync"
"time"
)
// ─── RESP 客户端 ───────────────────────────────────────
// RedisConn 原生 TCP 连接 + RESP 读写
type RedisConn struct {
mu sync.Mutex
conn net.Conn
r *bufio.Reader
}
func DialRedis(addr string) (*RedisConn, error) {
conn, err := net.DialTimeout("tcp", addr, 3*time.Second)
if err != nil {
return nil, err
}
return &RedisConn{conn: conn, r: bufio.NewReader(conn)}, nil
}
func (rc *RedisConn) Close() error { return rc.conn.Close() }
// Do 执行 Redis 命令,返回 RESP 解析结果
func (rc *RedisConn) Do(args ...string) (interface{}, error) {
rc.mu.Lock()
defer rc.mu.Unlock()
// 编码 RESP
cmd := fmt.Sprintf("*%d\r\n", len(args))
for _, a := range args {
cmd += fmt.Sprintf("$%d\r\n%s\r\n", len(a), a)
}
if _, err := rc.conn.Write([]byte(cmd)); err != nil {
return nil, fmt.Errorf("redis write: %w", err)
}
return rc.readRESP()
}
func (rc *RedisConn) readRESP() (interface{}, error) {
line, err := rc.r.ReadString('\n')
if err != nil {
return nil, fmt.Errorf("redis read: %w", err)
}
line = strings.TrimSuffix(line, "\r\n")
switch {
case strings.HasPrefix(line, "+"):
return line[1:], nil
case strings.HasPrefix(line, "-"):
return nil, errors.New(line[1:])
case strings.HasPrefix(line, ":"):
return strconv.ParseInt(line[1:], 10, 64)
case strings.HasPrefix(line, "$"):
length, _ := strconv.Atoi(line[1:])
if length < 0 {
return nil, nil
}
buf := make([]byte, length+2)
if _, err := io.ReadFull(rc.r, buf); err != nil {
return nil, err
}
return string(buf[:length]), nil
case strings.HasPrefix(line, "*"):
count, _ := strconv.Atoi(line[1:])
if count < 0 {
return nil, nil
}
arr := make([]interface{}, count)
for i := 0; i < count; i++ {
arr[i], _ = rc.readRESP()
}
return arr, nil
}
return line, nil
}
// ─── Redis 客户端封装 ─────────────────────────────────
type RedisClient struct {
conn *RedisConn
addr string
}
func NewRedisClient() *RedisClient {
addr := os.Getenv("REDIS_ADDR")
if addr == "" {
addr = "127.0.0.1:6379"
}
return &RedisClient{addr: addr}
}
func (rc *RedisClient) Connect() error {
if rc.conn != nil {
return nil
}
conn, err := DialRedis(rc.addr)
if err != nil {
return err
}
rc.conn = conn
// 健康检查
if _, err := conn.Do("PING"); err != nil {
conn.Close()
rc.conn = nil
return err
}
log.Printf("[redis] connected to %s", rc.addr)
return nil
}
// ─── 基础命令 ─────────────────────────────────────────
func (rc *RedisClient) Set(key, value string, ttl time.Duration) error {
if rc.conn == nil {
return errors.New("redis: not connected")
}
args := []string{"SET", key, value}
if ttl > 0 {
args = append(args, "EX", strconv.Itoa(int(ttl.Seconds())))
}
_, err := rc.conn.Do(args...)
return err
}
func (rc *RedisClient) Get(key string) (string, error) {
if rc.conn == nil {
return "", errors.New("redis: not connected")
}
v, err := rc.conn.Do("GET", key)
if err != nil {
return "", err
}
if v == nil {
return "", nil
}
return v.(string), nil
}
func (rc *RedisClient) Del(keys ...string) error {
if rc.conn == nil {
return errors.New("redis: not connected")
}
args := append([]string{"DEL"}, keys...)
_, err := rc.conn.Do(args...)
return err
}
func (rc *RedisClient) Expire(key string, ttl time.Duration) error {
if rc.conn == nil {
return errors.New("redis: not connected")
}
_, err := rc.conn.Do("EXPIRE", key, strconv.Itoa(int(ttl.Seconds())))
return err
}
func (rc *RedisClient) Incr(key string) (int64, error) {
if rc.conn == nil {
return 0, errors.New("redis: not connected")
}
v, err := rc.conn.Do("INCR", key)
if err != nil {
return 0, err
}
return v.(int64), nil
}
// ─── Hash ─────────────────────────────────────────────
func (rc *RedisClient) HSet(key, field, value string) error {
if rc.conn == nil {
return errors.New("redis: not connected")
}
_, err := rc.conn.Do("HSET", key, field, value)
return err
}
// HDel 从 Hash 中删除一个或多个 field
func (rc *RedisClient) HDel(key, field string) error {
if rc.conn == nil {
return errors.New("redis: not connected")
}
_, err := rc.conn.Do("HDEL", key, field)
return err
}
func (rc *RedisClient) HGetAll(key string) (map[string]string, error) {
if rc.conn == nil {
return nil, errors.New("redis: not connected")
}
v, err := rc.conn.Do("HGETALL", key)
if err != nil {
return nil, err
}
arr, ok := v.([]interface{})
if !ok {
return nil, errors.New("redis: unexpected HGETALL response")
}
result := make(map[string]string)
for i := 0; i < len(arr); i += 2 {
if s, ok := arr[i].(string); ok {
if val, ok := arr[i+1].(string); ok {
result[s] = val
}
}
}
return result, nil
}
// ─── Pub/Sub ──────────────────────────────────────────
func (rc *RedisClient) Publish(channel, message string) error {
if rc.conn == nil {
return errors.New("redis: not connected")
}
_, err := rc.conn.Do("PUBLISH", channel, message)
return err
}
// ─── 全局客户端 ───────────────────────────────────────
var redisClient *RedisClient
var redisOnce sync.Once
func GetRedisClient() *RedisClient {
redisOnce.Do(func() {
redisClient = NewRedisClient()
if err := redisClient.Connect(); err != nil {
log.Printf("[redis] connect failed: %v — falling back to in-memory", err)
redisClient = nil
}
})
return redisClient
}
// ─── Redis 限流器 ─────────────────────────────────────
type RedisRateLimiter struct {
client *RedisClient
mu sync.Mutex
local map[string]*tokenBucket // fallback
}
type tokenBucket struct {
tokens float64
lastTime time.Time
rate float64
burst float64
}
func NewRedisRateLimiter() *RedisRateLimiter {
return &RedisRateLimiter{
client: GetRedisClient(),
local: make(map[string]*tokenBucket),
}
}
func (rl *RedisRateLimiter) Allow(key string, rate, burst float64) bool {
if rl.client != nil {
// 用 Redis INCR + EXPIRE 做简单计数限流
redisKey := "zhiyi:ratelimit:" + key
count, err := rl.client.Incr(redisKey)
if err == nil && count == 1 {
rl.client.Expire(redisKey, time.Minute)
}
return count <= int64(burst)
}
// Fallback: 内存令牌桶
rl.mu.Lock()
defer rl.mu.Unlock()
bucket, ok := rl.local[key]
if !ok {
bucket = &tokenBucket{tokens: burst, lastTime: time.Now(), rate: rate, burst: burst}
rl.local[key] = bucket
}
now := time.Now()
elapsed := now.Sub(bucket.lastTime).Seconds()
bucket.tokens += elapsed * bucket.rate
if bucket.tokens > bucket.burst {
bucket.tokens = bucket.burst
}
bucket.lastTime = now
if bucket.tokens >= 1 {
bucket.tokens--
return true
}
return false
}
// ─── Redis 缓存 ───────────────────────────────────────
type RedisCache struct {
client *RedisClient
ttl time.Duration
mu sync.RWMutex
fallback map[string]*cachedItem
}
type cachedItem struct {
data []byte
expiresAt time.Time
}
func NewRedisCache(ttl time.Duration) *RedisCache {
return &RedisCache{
client: GetRedisClient(),
ttl: ttl,
fallback: make(map[string]*cachedItem),
}
}
func (rc *RedisCache) Get(key string) ([]byte, bool) {
if rc.client != nil {
v, err := rc.client.Get("zhiyi:cache:" + key)
if err == nil && v != "" {
return []byte(v), true
}
}
rc.mu.RLock()
item, ok := rc.fallback[key]
rc.mu.RUnlock()
if !ok || time.Now().After(item.expiresAt) {
return nil, false
}
return item.data, true
}
func (rc *RedisCache) Set(key string, data []byte) {
if rc.client != nil {
rc.client.Set("zhiyi:cache:"+key, string(data), rc.ttl)
return
}
rc.mu.Lock()
rc.fallback[key] = &cachedItem{data: data, expiresAt: time.Now().Add(rc.ttl)}
rc.mu.Unlock()
}
func (rc *RedisCache) Del(key string) {
if rc.client != nil {
rc.client.Del("zhiyi:cache:" + key)
return
}
rc.mu.Lock()
delete(rc.fallback, key)
rc.mu.Unlock()
}
// ─── Redis 心跳 ───────────────────────────────────────
type Heartbeat struct {
client *RedisClient
instance string
done chan struct{}
}
func NewHeartbeat(instance string) *Heartbeat {
h := &Heartbeat{
client: GetRedisClient(),
instance: instance,
done: make(chan struct{}),
}
go h.beat()
return h
}
func (h *Heartbeat) beat() {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if h.client != nil {
h.client.Set("zhiyi:heartbeat:"+h.instance, time.Now().Format(time.RFC3339), 30*time.Second)
}
case <-h.done:
return
}
}
}
func (h *Heartbeat) Stop() { close(h.done) }
// ─── Redis 事件总线 ───────────────────────────────────
type RedisEventBus struct {
client *RedisClient
}
func NewRedisEventBus() *RedisEventBus {
return &RedisEventBus{client: GetRedisClient()}
}
func (reb *RedisEventBus) Publish(channel string, data []byte) error {
if reb.client != nil {
return reb.client.Publish(channel, string(data))
}
return errors.New("redis: not available")
}
// ─── 自优化指标存储 ──────────────────────────────────
type MetricsStore struct {
client *RedisClient
mu sync.RWMutex
local map[string]map[string]float64
}
var GlobalMetricsStore = &MetricsStore{
client: GetRedisClient(),
local: make(map[string]map[string]float64),
}
func (ms *MetricsStore) Set(date, metric string, value float64) {
if ms.client != nil {
ms.client.HSet("zhiyi:metrics:"+date, metric, fmt.Sprintf("%.4f", value))
ms.client.Expire("zhiyi:metrics:"+date, 90*24*time.Hour)
return
}
ms.mu.Lock()
if ms.local[date] == nil {
ms.local[date] = make(map[string]float64)
}
ms.local[date][metric] = value
ms.mu.Unlock()
}
func (ms *MetricsStore) GetRange(days int) []map[string]interface{} {
var result []map[string]interface{}
for t := time.Now(); t.After(time.Now().AddDate(0, 0, -days)); t = t.AddDate(0, 0, -1) {
date := t.Format("2006-01-02")
if ms.client != nil {
data, err := ms.client.HGetAll("zhiyi:metrics:" + date)
if err == nil && len(data) > 0 {
result = append(result, map[string]interface{}{
"date": date,
"metrics": data,
})
}
} else {
ms.mu.RLock()
if m, ok := ms.local[date]; ok {
result = append(result, map[string]interface{}{
"date": date,
"metrics": m,
})
}
ms.mu.RUnlock()
}
}
return result
}