根本原因是网关多个实例未共享Redis客户端实例,导致连接隔离、pipeline缓存独立、健康状态不一致,A实例写入后B实例无法立即读取。
Go网关里用 Redis 存 Session,为什么读不到刚写入的值?
根本原因不是 redis 慢,而是网关多个实例没共享连接池或没复用 client 实例。go 的 redis.client 是线程安全但非进程共享——每个 go service 实例若各自 new 一个 client,哪怕连的是同一个 redis 地址,也仍是独立连接、独立 pipeline 缓存、独立健康检查状态。
常见现象:SET session:abc {"uid":123} EX 1800 NX 在 A 实例返回 OK,B 实例立刻 GET session:abc 却返回空。这不是 Redis 集群槽迁移问题,是 B 实例的 client 连接尚未收到 A 实例写入的传播(Redis 本身无跨 client 通知机制)。
- 必须在应用启动时全局初始化单个
redis.Client实例,所有 handler 共享它(例如放在globalsessionkeeper.GlobalRedisClient包变量里) - 禁用
redis.NewClient()在 handler 内调用——这是高频误操作,会导致连接数爆炸和连接复用失效 - 若用
redis.ClusterClient,注意ClusterClient.Do(ctx, cmd)不保证命令路由到正确哈希槽;应改用ClusterClient.Get(ctx, key)等封装方法,它们内部会自动重试并定位槽位 - 本地开发时容易忽略:Docker Compose 中 Redis 容器名被硬编码为
localhost,而 Go 实例运行在宿主机,实际连不到——务必用host.docker.internal或真实局域网 IP
Session ID 生成和校验不一致,导致“登录成功却跳回登录页”
典型表现是前端拿到 Set-Cookie: session_id=xxx,但后续请求 Header 里没带这个 Cookie,或者服务端解析出的 session_id 和存储 key 对不上。这不是 JWT 签名错误,而是协议层和存储层脱节。
- Session ID 必须由服务端用
crypto/rand.Read()生成,绝不能接受客户端传入的任意字符串(防碰撞+防伪造) - Cookie 的
Domain字段要显式设为当前主域名(如.example.com),否则子域名(api.example.com)无法读取主站设置的 Cookie - 若网关启用了 HTTPS,但测试环境用 HTTP,需确保
Secure属性未开启,否则浏览器拒绝发送该 Cookie - 校验逻辑里别直接比对
r.Header.Get("Cookie")字符串——要用r.Cookie("session_id"),它自动处理分号分隔、URL 解码和匹配逻辑
并发登录覆盖旧 Session,WebSocket 连接突然断开
用户快速点击两次登录按钮,后端收到两个几乎同时的请求,都生成新 Session 并写入 Redis,结果旧 Session 被覆盖,但已建立的 WebSocket 连接仍持有旧 session_id,下次心跳时 GET 失败,触发强制下线。
使用 Playwright 为 jimeng.jianying.com 管理浏览器会话,导入 cookies/localStorage 以自动化视频生成工作流,支持图片上传等功能
- 写入 Session 必须用原子命令:
SET session:{id} {value} EX 1800 NX,其中NX是关键,确保只在 key 不存在时才写入 - 不要用先
GET再SET的两步操作——中间存在竞态窗口 - 如果业务允许“踢掉前一次登录”,那应在写入成功后,主动向旧 Session 关联的 WebSocket 连接发
close帧,而不是等它自己超时断开 - Redis 的
EX时间建议设为max(登录耗时 + 30s, 30min),别设成永不过期;故障时脏数据不会残留
网关多协议转发时,Session 状态在 HTTP/WS/gRPC-Web 间不同步
用户通过 HTTP 登录后,WebSocket 连接建立时拿不到 Session 数据,或 gRPC-Web 请求里 Authorization: Bearer xxx 无法映射到对应 Session——本质是各协议入口没走同一套 Session 解析链路。
- 所有协议入口(HTTP handler、WebSocket upgrade 分支、gRPC-Web middleware)必须统一调用同一个函数,例如
session.LoadFromRequest(r),该函数内部统一从 Cookie 或 Header 提取 token,再查 Redis - WebSocket 连接建立后,首次消息必须携带认证信息(如
{"type":"auth","token":"xxx"}),不能依赖 Upgrade 请求里的 Cookie——因为浏览器 WebSocket API 不自动带 Cookie - gRPC-Web 请求若带
Authorizationheader,需在网关层解析 JWT 或 session_id,并注入到context.WithValue(ctx, sessionKey, sess),下游服务才能透传使用 - 避免在 WebSocket handler 里另起 goroutine 去查 Redis——这会丢失 request context 的 timeout 控制,应复用 HTTP 请求时已加载好的 Session 实例
最易被忽略的一点:Session 数据序列化用 json.Marshal 时,struct 字段若首字母小写(非导出),会被静默忽略,导致反序列化后字段为空。上线前务必用真实 struct 实例跑一遍 encode/decode 测试。










