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命令实现锁,防止并发操作。
重点提醒:String的value最大512MB,但千万别真存那么大。我见过有人把整个交易日志塞进去,结果内存爆了。一般建议value控制在几KB以内。

代码示例:

// 存储行情快照
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");
    }
}
我的经验:用String存JSON时,记得设置过期时间。交易数据时效性很强,比如行情快照,5秒过期就够了。别让旧数据占着内存。

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");
避坑指南:我曾经犯过一个错——把Hash的field设得太多。一个key下面挂了上千个field,结果查询时慢得要命。后来我拆成了多个key,每个key只存10-20个field,性能立马就上来了。记住,Hash不是万能的,field太多就该考虑拆分。

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) {
    // 处理订单逻辑
}
我的习惯:用List做队列时,一定要用BRPOP或BLPOP这种阻塞式弹出。别用RPOP轮询,那会浪费大量CPU。阻塞等待,有数据才处理,没数据就休眠,这才是正确姿势。

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;
}
注意:Set的SISMEMBER命令时间复杂度是O(1),非常快。但如果你要遍历整个Set,用SMEMBERS命令,那就要小心了——数据量大时会阻塞Redis。我建议用SSCAN分批遍历。

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);
}
避坑指南:我曾经用Sorted Set做延时队列,结果发现score精度不够。多个任务在同一毫秒内触发时,score会重复,导致任务丢失。后来我改用"时间戳+自增ID"作为score,才解决了这个问题。记住,score是double类型,精度有限,高并发场景下要小心。

3.6 知识体系总览

下面这张图,是我整理的五种数据结构在交易场景中的核心应用。你可以把它当作一个速查表,用的时候看一眼。

Redis数据结构在交易场景中的应用 Redis 数据结构 String 行情快照 用户会话 分布式锁 Hash 账户信息 持仓信息 用户资料 List 订单队列 行情推送 日志收集 Set 黑名单 去重校验 关注列表 Sorted Set 龙虎榜 延时队列 滑动限流 选择建议 • 简单K-V、临时数据 → String • 对象、多字段更新 → Hash • 队列、FIFO场景 → List • 去重、集合运算 → Set • 排序、排行榜、延时任务 → Sorted Set

3.7 总结一下

这五种数据结构,说白了就是五把不同的刀。String是万能刀,什么都能切;Hash是雕刻刀,适合精细操作;List是锯子,适合排队;Set是筛子,专门去重;Sorted Set是尺子,能排序。

选哪个,取决于你的场景。我个人习惯是:先想清楚数据的特点,再选结构。比如,数据是独立的还是关联的?需要排序吗?需要去重吗?想清楚了,选起来就简单了。

嗯,这一章就到这儿。记住,数据结构选对了,系统就成功了一半。


专注资料整理