Redis 在 Go 后端的应用

用 go-redis 做连接、缓存、会话/令牌黑名单与简单限流;超时、键设计、健康检查与 cache-aside 边界。

#type / howto #status / growing #tech / dev / backend #resource / go

[!info] 关联笔记

Redis 在 Go 后端的应用

这个概念为什么出现

多实例服务需要共享的快存

  • 会话 / refresh 元数据
  • 热点缓存
  • 分布式限流计数
  • 登出后的 token 黑名单

进程内 map 无法跨副本;Redis 成为常见外部依赖。Go 侧主流客户端是 go-redis(社区库,非标准库)。

[!abstract] 一句话理解 把 Redis 当网络服务:所有命令带 context 与超时,键带命名空间与 TTL,失败时明确降级或报错,不要当本地内存用。

目标

读完并能落地时,你应能:

  1. 用 context 超时配置 go-redis 客户端并 Ping
  2. 实现 cache-aside 读路径与写后失效
  3. 用带 TTL 的键做 token 黑名单或会话
  4. 区分 redis.Nil(未命中)与真实故障
  5. 说明 readiness 是否应强依赖 Redis

最小连接示例

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/redis/go-redis/v9"
)

func main() {
	rdb := redis.NewClient(&redis.Options{
		Addr:         "127.0.x.x:6379",
		ReadTimeout:  200 * time.Millisecond,
		WriteTimeout: 200 * time.Millisecond,
		PoolSize:     10,
	})
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	if err := rdb.Ping(ctx).Err(); err != nil {
		panic(err)
	}
	if err := rdb.Set(ctx, "app:demo:hello", "world", time.Minute).Err(); err != nil {
		panic(err)
	}
	val, err := rdb.Get(ctx, "app:demo:hello").Result()
	fmt.Println(val, err)
}

典型场景

1. 缓存(Cache-aside)

func (s *Service) GetUser(ctx context.Context, id string) (User, error) {
	key := "user:" + id
	if b, err := s.rdb.Get(ctx, key).Bytes(); err == nil {
		var u User
		if json.Unmarshal(b, &u) == nil {
			return u, nil
		}
	}
	u, err := s.repo.Find(ctx, id)
	if err != nil {
		return User{}, err
	}
	if b, err := json.Marshal(u); err == nil {
		_ = s.rdb.Set(ctx, key, b, 5*time.Minute).Err()
	}
	return u, nil
}

写后删除或短 TTL,见 go-cache-strategies

2. Token 黑名单

// 注销:在 access token 剩余 TTL 内拉黑
rdb.Set(ctx, "blacklist:"+jti, "1", remainingTTL)

// 鉴权中间件
n, err := rdb.Exists(ctx, "blacklist:"+jti).Result()
if n > 0 { /* 401 */ }

3. 简单限流(固定窗口示意)

key := fmt.Sprintf("rl:%s:%d", ip, time.Now().Unix()/60)
n, err := rdb.Incr(ctx, key).Result()
if n == 1 {
	rdb.Expire(ctx, key, 2*time.Minute)
}
if n > 100 {
	// 429
}

生产更常用令牌桶/滑动窗口或网关;见 go-rate-limiting

4. 会话

sessionID -> userID JSON,设 TTL;注意滚动续期与固定会话固定风险。

工程要点

主题实践
超时每个请求 context;客户端 Read/WriteTimeout
键设计服务:实体:id;避免无前缀碰撞
TTL几乎总是要有;防内存涨死
序列化JSON/msgpack;版本化字段
PoolSize 与 GOMAXPROCS/并发匹配,测量
错误区分 redis.Nil(未命中)与真实故障
健康检查readiness 对 Redis Ping(可选,防强依赖拖垮发布)

边界

  1. Redis 故障时:缓存可降级打 DB;黑名单/会话可能需失败开/关策略。
  2. 不把大对象无限塞 Redis。
  3. 集群/Cluster 与单机 API 差异。
  4. 事务/MULTI 与 Lua 脚本用于原子性,勿滥用。

常见误区

[!warning] 忽略 redis.Nil
把未命中当系统错误。

[!warning] 无超时
网络抖动拖死 goroutine。

[!warning] 缓存与 DB 双写无策略
脏读/脏缓存。

可验证实验

  1. 本地 Redis:SET/GET 带 TTL,过期后 GET 为 Nil。
  2. 取消 context,观察命令返回。
  3. 黑名单键在 TTL 后自动消失。

本节总结

  • Redis 是带超时的远程依赖,不是魔法全局 map。
  • 缓存/会话/限流/黑名单场景清晰建模。
  • go-cache-strategies、鉴权链路一起设计失败模式。

自测题

  1. GET 未命中时 go-redis 典型错误是什么?
  2. 为何黑名单键要设 TTL?
  3. readiness 是否必须依赖 Redis?
答案
  1. redis.Nil
  2. token 本身会过期,键应一并回收,防内存泄漏。
  3. 视是否强依赖;可降级服务可只做 liveness,readiness 策略产品化决定。

延伸阅读


笔记元信息

  • 文件:redis-session-and-caching-go.md
  • 状态:已深化
创建于 2026/6/25 更新于 2026/7/15