(三) Redis 高级数据结构与常用场景实践
(三) Redis 高级数据结构与常用场景实践
Redis 除了五中基础的数据结构(String、Set、sortedSet、Hash、List) 外,还提供了一些高级的数据结构,帮我们高效地处理大数据量、地理坐标、消息流、并发控制等业务场景。
Bitmaps: 用极小的空间存储大量用户状态
工作原理
bitmaps 是对String 的操作拓展,允许通过偏移量操作某一位的值(0或者 1),每个位只需 1bit 存储空间,因此在记录海量布尔状态时非常高效。
常用命令
1
2
3SETBIT sign:20250513 12345 1 # 用户 ID 12345 在 2025-05-13 签到
GETBIT sign:20250513 12345 # 判断是否签到
BITCOUNT sign:20250513 # 统计总签到人数应用场景
用户签到系统
每天一个 Key,offset 代表用户 ID,value 代表是否签到。
统计日活用户(DAU)
每天记录用户是否访问网站,BITCOUNT 获取当天活跃用户总数。
权限开关系统
对于某类功能,快速记录用户是否开启。
HyperLogLog:百万级数据去重统计神器
工作原理
HyperLogLog 是 Redis 基于概率统计的算法结构,专门用于估算集合中不重复元素的个数,特点是内存恒定(12KB)、速度极快,但存在小误差(~0.81%)。
它利用哈希函数将输入元素映射到一串二进制位,通过记录这些位的“最长前缀 0”来反推出集合的基数。
常用命令
1
2
3PFADD uv:20250513 192.168.1.100
PFCOUNT uv:20250513 # 估算今日独立访客数
PFMERGE uv:all uv:20250513 uv:20250514 # 估算20250513 - 20250514 的独立访客数应用场景
网站 UV 去重统计
页面访问量中同一 IP/用户 ID 多次访问只计一次。
营销活动用户去重
记录参与人数(手机号/ID)不重复。
Geo:地理位置存储与空间查询
工作原理
Redis GEO 模块是基于 Geohash + Sorted Set 实现的,将经纬度编码为 52bit 的整数,然后存入有序集合,支持高效的范围搜索和排序。
常用命令
1
2
3GEOADD shop:location 116.40 39.90 "北京店" # 添加地理坐标
GEORADIUS shop:location 116.39 39.91 10 km # 查找范围距离
GEODIST shop:location "北京店" "上海店" # 计算两个坐标之间的距离。应用场景
附近店铺/服务/司机查询
外卖、打车、快递系统的核心功能。
LBS 服务(Location-Based Service)
结合定位服务为用户推荐附近商家或优惠。
Stream:原生分布式消息队列系统
工作原理
Redis Stream 类似 Kafka 的日志系统,支持持久化、消费组、消费确认、阻塞读取等功能,适合构建轻量级消息处理系统。
每条消息都有一个唯一 ID(类似时间戳 + 序号),支持自动追踪消费进度、消息重试、死信处理等功能。
常用命令
1
2
3
4
5XADD orders * userId 123 itemId 456
XGROUP CREATE orders groupA $ # 创建消费组
XREADGROUP GROUP groupA consumer1 STREAMS orders > # 读取未读消息
XACK orders groupA <msg_id> # 确认消费应用场景
异步任务处理系统
订单生成后推送任务:发送短信、打印、库存更新等。
日志采集系统
前端或后端埋点数据通过 Stream 聚合到后端统一处理
分布式锁:Redis 实现高性能互斥控制
工作原理
使用 SET NX EX 原子指令实现加锁,配合唯一标识判断解锁权限,防止误删他人锁。可选用 Lua 脚本保证释放锁的原子性。
常用命令
加锁
1
SET lock:resource <uuid> NX EX 10
含义:
- lock:resource:锁的 Key
:唯一值,用于标记谁持有了这个锁(一般为线程 ID 或服务 ID) - NX:只有当 key 不存在时才设置成功(避免重复加锁)
- EX 10:自动过期时间(避免死锁)
释放锁
1
2
3
4
5if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
示例展示
Redis 分布式锁实践用例:防止订单重复支付
🧩 背景
在电商系统中,用户在结算时可能由于网络卡顿、浏览器重复提交等情况重复点击“支付”按钮。如果没有妥善处理,可能导致:
- 多次创建支付请求
- 重复扣款或下单
- 支付状态混乱,影响用户体验
🎯 目标
我们希望在支付接口中使用 Redis 分布式锁,确保同一个订单号只处理一次支付逻辑。
✅ 实现方案
1. 加锁逻辑(伪代码)
1
2
3
4
5
6
7String lockKey = "lock:pay:" + orderId;
String uuid = UUID.randomUUID().toString();
boolean success = redis.set(lockKey, uuid, "NX", "EX", 10);
if (!success) {
return "支付处理中,请勿重复点击";
}使用 SET NX EX 原子操作保证:只有第一个线程可以处理支付,其他线程直接返回提示信息。
2. 支付处理逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24try {
// 校验订单状态
if (orderService.isPaid(orderId)) {
return "订单已支付";
}
// 调用第三方支付接口
paymentService.call(orderId);
// 修改订单状态
orderService.markAsPaid(orderId);
return "支付成功";
} finally {
// 使用 Lua 脚本安全释放锁
String unlockScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
redis.eval(unlockScript, Collections.singletonList(lockKey), Collections.singletonList(uuid));
}应用场景
防止秒杀超卖
并发下操作库存前加锁,避免重复扣减。
防止重复提交订单/评论/投票
同一用户在极短时间内可能多次提交。