package redispkg import ( "context" "crypto/rand" "encoding/hex" "log" "time" "github.com/redis/go-redis/v9" ) type R struct{ c *redis.Client } func New(url string) *R { if url == "" { return &R{} } opt, err := redis.ParseURL(url) if err != nil { log.Printf("bad REDIS_URL (%v): redis disabled", err) return &R{} } return &R{c: redis.NewClient(opt)} } func (r *R) Get(ctx context.Context, key string) (string, bool) { if r.c == nil { return "", false } v, err := r.c.Get(ctx, key).Result() if err != nil { return "", false // 故障=miss } return v, true } func (r *R) Set(ctx context.Context, key, val string, ttl time.Duration) { if r.c == nil { return } if err := r.c.Set(ctx, key, val, ttl).Err(); err != nil { log.Printf("redis set %s: %v", key, err) } } func (r *R) IncrWindow(ctx context.Context, key string, ttl time.Duration) int { if r.c == nil { return 1 } n, err := r.c.Incr(ctx, key).Result() if err != nil { return 1 } if n == 1 { r.c.Expire(ctx, key, ttl) } return int(n) } func (r *R) Lock(ctx context.Context, key string, ttl time.Duration) (func(), bool) { noop := func() {} if r.c == nil { return noop, true } b := make([]byte, 8) rand.Read(b) tok := hex.EncodeToString(b) ok, err := r.c.SetNX(ctx, key, tok, ttl).Result() if err != nil { // spec §9: Redis 故障降级放行,锁只做尽力去重 log.Printf("redis lock %s: %v (proceeding without lock)", key, err) return noop, true } if !ok { return noop, false // 锁被持有,别的副本在扫 } return func() { r.c.Eval(ctx, "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end", []string{key}, tok) }, true }