票据存储方案:内存、Redis、数据库与传输方式
票据存哪儿?怎么传?这俩问题看似简单,但选错了,后面全是坑。
我见过不少项目,一开始图省事全放内存,结果一重启用户全得重新登录。也见过把票据塞进 Cookie 结果超 4KB 直接丢数据的。今天咱们就把四种存储方案和两种传输方式掰开揉碎讲清楚。
一、四种存储方案的取舍
先看一张对比表,心里有个全局印象:
| 方案 | 读写速度 | 持久性 | 容量 | 典型场景 |
|---|---|---|---|---|
| 内存存储 | 极快(纳秒级) | 无(进程重启即丢) | 受限于 JVM 堆 | 单机开发、原型验证 |
| Redis 缓存 | 快(微秒级) | 可配置(RDB/AOF) | 取决于内存大小 | 分布式会话、高并发 |
| 数据库持久化 | 慢(毫秒级) | 强(磁盘存储) | 几乎无限 | 审计日志、长周期票据 |
| Cookie/Header | 无存储开销 | 客户端持有 | 4KB(Cookie 限制) | 无状态服务、JWT |
二、内存存储:最快但最脆弱
说白了就是把票据放进程里的 HashMap 或 ConcurrentHashMap。我刚开始写项目时就这么干,简单粗暴。
优点:零网络开销,读写速度碾压一切外部存储。
缺点:进程重启、Full GC、甚至代码热更新都能让所有票据瞬间蒸发。
举个例子:
// 内存存储票据(仅适用于单机开发)
public class InMemoryTicketStore {
private final ConcurrentHashMap<String, Ticket> store = new ConcurrentHashMap<>();
public void save(String sessionId, Ticket ticket) {
store.put(sessionId, ticket);
}
public Ticket get(String sessionId) {
return store.get(sessionId);
}
}
嗯,这里要注意——千万别在生产环境这么干。我曾经有个同事,上线前忘了切 Redis,结果半夜发布重启,全公司两千人同时掉线。那场面,啧啧。
三、Redis 缓存:生产环境的首选
Redis 为什么香?因为它把「快」和「持久」平衡得最好。
我个人习惯用 Redis 存票据时,会做三件事:
- 设置 TTL:票据过期时间精确到秒,避免僵尸会话堆积
- 开启 RDB 持久化:至少每 5 分钟落盘一次,重启不丢太多数据
- 使用 Hash 结构:一个会话 ID 对应一个 Hash,字段放票据属性
// Redis 存储票据(推荐生产使用)
redisTemplate.opsForHash().put("session:" + sessionId, "ticket", ticketJson);
redisTemplate.expire("session:" + sessionId, 30, TimeUnit.MINUTES);
小技巧:Redis 的 Key 设计建议加前缀(如 session:),方便后续用 SCAN 命令批量清理过期会话。
你想想看,如果 Redis 挂了怎么办?我建议做 Redis 哨兵或集群,至少保证主从切换时票据不丢。当然,如果业务允许短暂掉线,单机 Redis + 持久化也够用。
四、数据库持久化:最后的保险
数据库存票据,速度确实慢,但胜在「稳」。适合那些需要长期追溯的场景,比如金融系统的操作审计、7 天前的会话恢复。
表结构大概长这样:
CREATE TABLE session_ticket (
session_id VARCHAR(64) PRIMARY KEY,
ticket_data TEXT NOT NULL,
created_at DATETIME NOT NULL,
expires_at DATETIME NOT NULL,
INDEX idx_expires (expires_at)
);
注意加 expires_at 索引,不然清理过期数据时全表扫描,能把数据库拖死。我见过一个项目,没加索引,每天凌晨跑清理任务时 CPU 直接飙到 100%。
避坑指南:数据库存储票据时,ticket_data 字段建议用 TEXT 或 JSON 类型,别用 VARCHAR(255)。JWT 动辄几百字节,加上其他属性很容易超长。
五、Cookie vs Header 传输
存好了怎么传?两种主流方式,各有各的脾气。
5.1 Cookie 传输
浏览器自动带,开发者省事。但限制也很明显:
- 大小限制:单个 Cookie 不超过 4KB,整个域名下所有 Cookie 加起来不超过 20KB
- 安全风险:容易被 CSRF 攻击,必须设置
HttpOnly和SameSite - 跨域问题:默认不跨域,需要额外配置
Domain属性
// 设置 Cookie(安全配置)
response.addCookie(new Cookie("SESSION_ID", sessionId) {{
setHttpOnly(true);
setSecure(true);
setPath("/");
setMaxAge(1800); // 30 分钟
}});
5.2 Header 传输
最常用的是 Authorization: Bearer <token>。优点很明显:
- 无大小限制(理论上,但别太夸张)
- 跨域友好:前端通过
fetch或axios自由设置 - 更安全:不会自动附加,需要显式携带
// 前端携带 Header
fetch('/api/data', {
headers: {
'Authorization': 'Bearer ' + token
}
});
我的建议:新项目优先用 Header 传输。Cookie 虽然方便,但 CSRF、跨域、大小限制这些坑,踩一个就够你折腾半天。
六、整体架构图
下面这张图展示了票据从生成到验证的完整流转路径:
七、选型建议
最后给个实在的结论:
- 开发环境:内存存储 + Cookie,怎么简单怎么来
- 生产环境:Redis 缓存 + Header 传输,数据库做冷备
- 金融/审计场景:数据库持久化必须上,Redis 做热缓存加速
- 微服务架构:统一走 Redis,所有服务共享票据状态
个人经验:我一般会做两层存储——Redis 扛 99% 的读写,数据库异步落盘做灾备。这样既保证了性能,又不怕 Redis 宕机丢数据。
票据存储没有银弹,关键看你的业务对「快」和「稳」的容忍度。选对了,后面会话恢复、分布式扩展都顺风顺水;选错了,光排查问题就能耗掉你半天。