3、Redis核心数据结构:String、Hash、List、Set、Sorted Set在交易场景中的应用
说实话,Redis的数据结构就那五种,但怎么用在交易系统里,这里头门道可不少。我见过不少团队,把Redis当成了普通缓存用,存个K-V就完事了。其实每种结构都有它最擅长的战场,用对了,性能翻倍;用错了,坑死自己。
今天我就把这五种结构,结合交易场景,一个一个给你拆开讲。每个结构我都会说清楚:它长什么样、适合干什么、我在项目中踩过什么坑。
3.1 String:最基础,但别小看它
String是Redis里最简单的结构,就是一个key对应一个value。但你别因为它简单就轻视它,交易系统里很多核心数据都是用String存的。
典型场景:
- 行情快照:比如某只股票的当前价格、涨跌幅。key用"stock:price:600519",value就是JSON序列化的行情数据。
- 用户会话:登录token、session信息。key用"session:userId",value存用户信息。
- 分布式锁:用SETNX命令实现锁,防止并发操作。
代码示例:
// 存储行情快照
redisTemplate.opsForValue().set("stock:price:600519",
JSON.toJSONString(stockSnapshot), 5, TimeUnit.SECONDS);
// 获取行情
StockSnapshot snapshot = JSON.parseObject(
redisTemplate.opsForValue().get("stock:price:600519"),
StockSnapshot.class);
// 分布式锁
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent("lock:order:12345", "locked", 10, TimeUnit.SECONDS);
if (locked) {
try {
// 处理订单
} finally {
redisTemplate.delete("lock:order:12345");
}
}
3.2 Hash:存对象,比String更省内存
Hash结构,说白了就是一个key下面挂了一堆field-value对。它特别适合存对象,比如用户信息、账户余额。
为什么比String好?你想想看,如果用String存一个用户对象,每次更新一个字段都得把整个JSON读出来、改完、再写回去。用Hash呢?直接更新某个字段就行,省带宽、省CPU。
典型场景:
- 用户账户信息:key用"account:userId",field包括"balance"、"frozen"、"available"等。
- 持仓信息:key用"position:userId:stockCode",field包括"quantity"、"costPrice"等。
代码示例:
// 初始化账户
redisTemplate.opsForHash().putAll("account:10001", Map.of(
"balance", "1000000",
"frozen", "0",
"available", "1000000"
));
// 冻结资金
redisTemplate.opsForHash().increment("account:10001", "frozen", 50000);
redisTemplate.opsForHash().increment("account:10001", "available", -50000);
// 查询可用余额
String available = (String) redisTemplate.opsForHash()
.get("account:10001", "available");
3.3 List:排队,交易系统的命脉
List是个双向链表,支持从两端插入和弹出。在交易系统里,它最适合做消息队列和任务队列。
典型场景:
- 订单队列:用户下单后,把订单塞进List,后台worker从List里取出来处理。
- 行情推送队列:行情数据先写入List,然后推送给订阅的客户端。
- 日志收集:交易日志先写到List,再批量写入数据库。
代码示例:
// 生产者:下单
redisTemplate.opsForList().rightPush("order:queue", orderJson);
// 消费者:处理订单
String orderJson = redisTemplate.opsForList()
.leftPop("order:queue", 5, TimeUnit.SECONDS);
if (orderJson != null) {
// 处理订单逻辑
}
3.4 Set:去重,又快又准
Set是无序集合,元素不能重复。它最擅长做去重和集合运算。
典型场景:
- 黑名单:把违规用户ID加入Set,每次下单前检查是否在黑名单里。
- 已处理订单ID:防止重复处理,比如消息队列里的幂等性校验。
- 用户关注列表:用Set存用户关注的股票,方便做交集、并集运算。
代码示例:
// 加入黑名单
redisTemplate.opsForSet().add("blacklist:user", "10001", "10002");
// 检查是否在黑名单
Boolean isBlack = redisTemplate.opsForSet()
.isMember("blacklist:user", "10001");
// 已处理订单去重
Boolean added = redisTemplate.opsForSet()
.add("processed:order", "order12345");
if (added == 0) {
// 订单已处理,跳过
return;
}
3.5 Sorted Set:排行榜,交易系统的利器
Sorted Set(有序集合)比Set多了一个score(分数),元素按score排序。它特别适合做排行榜、延时队列、限流等场景。
典型场景:
- 龙虎榜:按成交量排序,展示当日最活跃的股票。
- 延时队列:用时间戳作为score,实现定时任务。
- 滑动窗口限流:用时间戳作为score,统计最近N秒内的请求数。
代码示例:
// 龙虎榜:增加成交量
redisTemplate.opsForZSet()
.incrementScore("ranking:daily", "600519", 1000000);
// 获取前10名
Set<ZSetOperations.TypedTuple<String>> top10 =
redisTemplate.opsForZSet()
.reverseRangeWithScores("ranking:daily", 0, 9);
// 延时队列:5分钟后执行
long executeTime = System.currentTimeMillis() + 5 * 60 * 1000;
redisTemplate.opsForZSet()
.add("delay:queue", "task:12345", executeTime);
// 取出到期的任务
Set<String> tasks = redisTemplate.opsForZSet()
.rangeByScore("delay:queue", 0, System.currentTimeMillis());
for (String task : tasks) {
// 执行任务
redisTemplate.opsForZSet().remove("delay:queue", task);
}
3.6 知识体系总览
下面这张图,是我整理的五种数据结构在交易场景中的核心应用。你可以把它当作一个速查表,用的时候看一眼。
3.7 总结一下
这五种数据结构,说白了就是五把不同的刀。String是万能刀,什么都能切;Hash是雕刻刀,适合精细操作;List是锯子,适合排队;Set是筛子,专门去重;Sorted Set是尺子,能排序。
选哪个,取决于你的场景。我个人习惯是:先想清楚数据的特点,再选结构。比如,数据是独立的还是关联的?需要排序吗?需要去重吗?想清楚了,选起来就简单了。
嗯,这一章就到这儿。记住,数据结构选对了,系统就成功了一半。