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,失败时明确降级或报错,不要当本地内存用。
目标
读完并能落地时,你应能:
- 用 context 超时配置
go-redis客户端并Ping - 实现 cache-aside 读路径与写后失效
- 用带 TTL 的键做 token 黑名单或会话
- 区分
redis.Nil(未命中)与真实故障 - 说明 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(可选,防强依赖拖垮发布) |
边界
- Redis 故障时:缓存可降级打 DB;黑名单/会话可能需失败开/关策略。
- 不把大对象无限塞 Redis。
- 集群/Cluster 与单机 API 差异。
- 事务/
MULTI与 Lua 脚本用于原子性,勿滥用。
常见误区
[!warning] 忽略 redis.Nil
把未命中当系统错误。
[!warning] 无超时
网络抖动拖死 goroutine。
[!warning] 缓存与 DB 双写无策略
脏读/脏缓存。
可验证实验
- 本地 Redis:
SET/GET带 TTL,过期后GET为 Nil。 - 取消 context,观察命令返回。
- 黑名单键在 TTL 后自动消失。
本节总结
- Redis 是带超时的远程依赖,不是魔法全局 map。
- 缓存/会话/限流/黑名单场景清晰建模。
- 与 go-cache-strategies、鉴权链路一起设计失败模式。
自测题
GET未命中时 go-redis 典型错误是什么?- 为何黑名单键要设 TTL?
- readiness 是否必须依赖 Redis?
答案
redis.Nil。- token 本身会过期,键应一并回收,防内存泄漏。
- 视是否强依赖;可降级服务可只做 liveness,readiness 策略产品化决定。
延伸阅读
笔记元信息
- 文件:
redis-session-and-caching-go.md - 状态:已深化