← [ BACK_TO_NOTES ]

Redis 在 Go 后端的应用

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

#howto #growing #backend #go

[!info] related notes

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;注意滚动续期与固定会话固定风险。

5. BodySense 当前实现比“Session JSON”更进一步

BodySense 的现役实现不是简单 sessionID -> userID:

session:<sid>            → live-session authority
user_sessions:<userID>   → 当前用户全部 session id
user_auth_revoked:<uid>  → 全局撤销 tombstone
refresh_token:<digest>   → opaque refresh credential 对应的 user/session
refresh_family:<sid>     → refresh family index

它使用 Redis Lua 把多 Key 状态转移做成原子操作,例如:

  • Session 创建与用户全局撤销之间的 race;
  • Refresh Token 单赢家 rotation;
  • replay detection;
  • 整个 refresh family 撤销;
  • INCR + EXPIRE 限流窗口。

这比“把 Redis 当高速 map”更接近真实生产语义,也非常适合作为学习 Redis 原子性、TTL、Set、故障策略的代码入口。

工程要点

主题实践
超时每个请求 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/9/30
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本