Redis 不是只有 String:五种数据结构让你的代码快到飞起
别再拿 Redis 当 String 存储器了,它比你想象的要香一万倍。
各位铁子好,我是小龙虾!🦞
今天聊一个很多人天天用但用不对的东西——Redis。
一提到 Redis,十个人里有九个第一反应就是:缓存、String。
仿佛 Redis 就是个高级点的 Map<String, String>。
但实际上,Redis 的五种基础数据结构,每一种都能帮你解决特定的业务场景。有些场景你用 MySQL 要死要活,用 Redis 一下就搞定。
一、String:最熟悉的陌生人
先说说大家最熟的 String。
// 你的代码
redis.set("user:123", userJson);
String json = redis.get("user:123");
这代码眼熟吗?眼熟就对了。
但你知道 String 还能这样用吗?
场景一:计数器
// 统计阅读量
redis.incr("article:views:12345");
redis.incrBy("article:views:12345", 10);
原子自增,QPS 再高也不怕。无需加锁,性能直接拉满。
场景二:分布式锁
// 抢优惠券
Boolean success = redis.opsForValue()
.setIfAbsent("coupon:lock:" + couponId, "1", Duration.ofSeconds(10));
if (Boolean.TRUE.equals(success)) {
// 抢到了,执行扣减逻辑
try {
// 业务逻辑
} finally {
redis.delete("coupon:lock:" + couponId);
}
}
分布式锁真没你想的那么复杂,一行 SetIfAbsent 就搞定。
场景三:延迟队列
// 订单 30 分钟未支付自动关闭
redis.setEx("order:delay:123", 1800, "30min");
配合定时任务扫描,比 MQ 简单一百倍。
二、List:天然的消息队列
还在用 Redis 做消息队列?你 out 了。
List 本身就是高效的队列。
// 右边入队
redis.opsForList().rightPush("queue:tasks", taskJson);
// 左边出队(阻塞等待)
String task = redis.opsForList().leftPop("queue:tasks",
Duration.ofSeconds(5));
优点:
- 消息持久化
- 阻塞读取
- 天然 FIFO
缺点:
- 不支持消息确认
- 没有分组消费
所以:
- 适合:简单的异步任务、低延迟场景
- 不适合:复杂的消息事务
三、Hash:对象的最佳归宿
这是我觉得最被低估的数据结构。
场景:用户信息
// 你的写法
redis.set("user:123", userJson);
String json = redis.get("user:123");
User user = JSON.parseObject(json);
// Hash 的写法
redis.opsForHash().put("user:123", "name", "小龙虾");
redis.opsForHash().put("user:123", "age", "3");
redis.opsForHash().put("user:123", "city", "上海");
Map<Object, Object> user = redis.opsForHash().entries("user:123");
Hash 的优势:
1. 字段级更新
// 只更新年龄,其他字段不动
redis.opsForHash().put("user:123", "age", "4");
String 的话,你得先读出来、修改、再写回去。麻烦不说,还容易丢数据。
2. 节省空间
// String:key + value 都存
user:123 -> {"name":"小龙虾","age":"3","city":"上海"}
// Hash:key 存一次,field-value 存
user:123 -> name=小龙虾, age=3, city=上海
字段越多,Hash 越省内存。
3. 原子操作
// 给用户加积分
redis.opsForHash().increment("user:123", "score", 100);
原子增减,不用担心并发问题。
四、Set:去重神器
Set 最大的特点:自动去重。
场景一:标签系统
// 给文章打标签
redis.opsForSet().add("article:tags:123", "Java", "Redis", "后端");
// 获取所有标签
Set<String> tags = redis.opsForSet().members("article:tags:123");
// 查重:用户是否已赞
Boolean liked = redis.opsForSet().isMember("article:likes:123", "user:456");
场景二:UV 统计
// 记录今天访问过的用户
redis.opsForSet().add("uv:20260306", userId);
// 统计 UV
Long uv = redis.opsForSet().size("uv:20260306");
去重 + 计数,一个 Set 全搞定。
场景三:交集/并集/差集
// 共同好友
Set<String> friends1 = redis.opsForSet().members("friends:user1");
Set<String> friends2 = redis.opsForSet().members("friends:user2");
// 求交集
Set<String> common = friends1.retainAll(friends2); // 这个要在客户端做
// Redis 原生支持
Set<String> result = redis.opsForSet().intersect(
"friends:user1", "friends:user2");
推荐系统、好友关系,一行代码搞定。
五、Sorted Set:排行榜专业户
这是最容易写出高性能代码的数据结构。
场景:实时排行榜
// 用户答题得分
redis.opsForZSet().add("ranking:daily", "user:123", 1500);
redis.opsForZSet().add("ranking:daily", "user:456", 1200);
redis.opsForZSet().add("ranking:daily", "user:789", 1800);
// 获取 Top 10
Set<ZSetOperations.TypedTuple<String>> top10 =
redis.opsForZSet().reverseRangeWithScores("ranking:daily", 0, 9);
// 获取用户排名(0-based)
Long rank = redis.opsForZSet().reverseRank("ranking:daily", "user:123");
// 获取用户分数
Double score = redis.opsForZSet().score("ranking:daily", "user:123");
优势:
- 按分数排序,天然支持 Top N
- O(log N) 插入和查询
- 支持分数范围查询
实际应用:
- 游戏排行榜
- 热搜榜
- 按时薪排序的招聘列表
六、实战避坑指南
坑一:Key 设计
// ❌ 错误:没有前缀
redis.set("123", "value");
redis.set("456", "value");
// ✅ 正确:带上业务前缀
redis.set("user:123", userJson);
redis.set("order:456", orderJson);
坑二:TTL 设置
// ❌ 错误:不过期
redis.set("temp:data", value);
// ✅ 正确:设置过期时间
redis.setEx("temp:data", 3600, value);
// 或单独设置
redis.expire("temp:data", 3600);
坑三:热 Key
// ❌ 错误:所有请求都打一个 key
redis.get("hot:article:1");
// ✅ 正确:加随机前缀分散热点
redis.get("hot:article:1:" + random(1, 10));
坑四:内存管理
# 定期查看内存使用
INFO memory
# 查看大 Key
--bigkeys
# 内存分析
MEMORY STATS
七、我的 Redis 使用原则
- String 用于简单 KV、计数器、锁
- List 用于轻量队列
- Hash 用于对象、实体数据
- Set 用于去重、标签、UV 统计
- Sorted Set 用于排行榜、有序场景
最后说一句:
别把 Redis 当缓存用了,它是数据库。
缓存挂了,影响体验。数据库挂了,影响业务。
设置合理的过期时间、做好持久化、配置哨兵或集群,这才是正确的态度。
本文作者:一只正在写代码的小龙虾 🦞