Redis 数据结构全景表格
| 数据结构 | 核心命令 | 使用场景 |
|---|---|---|
| String (字符串) | SET, GET, INCR, DECR, APPEND, STRLEN, MSET, MGET |
缓存对象、计数器、分布式锁、Session存储、限速器 |
| Hash (哈希表) | HSET, HGET, HGETALL, HDEL, HINCRBY, HLEN, HMSET, HMGET |
存储对象属性(如用户信息)、购物车、配置项、结构化数据存储 |
| List (列表) | LPUSH, RPUSH, LPOP, RPOP, LRANGE, LLEN, BLPOP, BRPOP |
消息队列、最新消息排行、任务队列、关注列表、时间线 |
| Set (集合) | SADD, SREM, SMEMBERS, SISMEMBER, SCARD, SINTER, SUNION, SDIFF |
标签系统、好友关系、抽奖活动、共同关注、去重集合 |
| Sorted Set (有序集合) | ZADD, ZREM, ZRANGE, ZREVRANGE, ZRANK, ZSCORE, ZINCRBY, ZCARD |
排行榜、延时队列、范围查询、带权重的任务调度、热门内容 |
| Bitmap (位图) | SETBIT, GETBIT, BITCOUNT, BITOP, BITPOS |
用户签到、活跃用户统计、布隆过滤器、特征标记、日活统计 |
| HyperLogLog | PFADD, PFCOUNT, PFMERGE |
海量数据去重计数(UV统计)、基数估算、大数据量唯一值统计 |
| Geospatial (地理空间) | GEOADD, GEODIST, GEOPOS, GEORADIUS, GEORADIUSBYMEMBER |
附近的人/店、地理位置存储、距离计算、基于位置的服务(LBS) |
| Stream (流) | XADD, XREAD, XRANGE, XLEN, XGROUP, XREADGROUP, ACK |
事件溯源、消息队列(支持消费者组)、日志收集、实时数据处理 |
一、String(字符串)
1.核心命令
bash
# 基础操作
SET key value [EX seconds] [NX|XX] # NX=不存在才设置(分布式锁),XX=存在才更新
GET key
DEL key
# 数值操作(原子性)
INCR key # 原子自增(计数器)
INCRBY key 100 # 增加指定值
DECR key
SETNX key value # 等价于 SET key value NX
# 批量操作
MSET key1 val1 key2 val2 # 批量设置(原子性)
MGET key1 key2 # 批量获取
# 高级
SETEX key 60 value # 设置并带过期(原子)
GETSET key newvalue # 设置新值并返回旧值
STRRLEN key # 获取字符串长度
2.使用场景
- 缓存热点数据:SET user:1001 "{name:'tom',age:25}" EX 3600
- 分布式锁:SET lock:order:1001 "uuid" NX EX 30
- 计数器:INCR page:view:home(原子,无竞态)
- 全局唯一 ID:INCR global:order:id
3.注意点
- 值最大 512MB,避免存储大对象(BigKey 问题)
- 数值范围:有符号 64 位整数(-2^63 ~ 2^63-1)
- SETNX 非原子:SETNX + EXPIRE 分两步,可能死锁,用 SET key value NX EX 30 替代
- **避免 KEYS **:生产环境禁用,用 SCAN 0 MATCH user: COUNT 100 替代
二、List(列表)
1.核心命令
bash
# 左右插入(栈/队列)
LPUSH list:a value1 value2 # 从左侧插入(栈:后进先出)
RPUSH list:a value3 # 从右侧插入(队列:先进先出)
# 弹出
LPOP list:a # 左侧弹出
RPOP list:a # 右侧弹出
BLPOP list:a 10 # 阻塞弹出(10秒超时),用于消息队列
# 范围查询
LRANGE list:a 0 -1 # 获取全部元素
LRANGE list:a 0 9 # 获取前10个(注意:O(n),大数据量慎用)
# 长度
LLEN list:a
# 指定位置操作
LINDEX list:a 0 # 获取索引0元素
LSET list:a 0 newval # 设置索引0值
LINSERT list:a BEFORE value2 value1.5 # 插入
LREM list:a 2 value # 删除前2个value
2.使用场景
- 消息队列:LPUSH + RPOP(生产者左插,消费者右弹)
- 最新列表:LPUSH news:latest "news1" + LTRIM news:latest 0 99(保留最新100条)
- 任务队列:BRPOP 阻塞消费
- 消息广播:多个消费者订阅同一 List(无 ACK 机制,消息可能被重复消费)
3.注意点
- LRANGE 0 -1 慎用:大 List 会阻塞 Redis(时间复杂度 O(n))
- BigKey 问题:List 元素超过 5000 个视为 BigKey,需拆分
- 内存消耗:每个元素独立分配内存,小元素用 Hash 替代更省
- 无 ACK:消息弹出即删除,消费者崩溃会丢消息(Stream 可解决)
三、Set(集合)
1.核心命令
bash
# 增删
SADD set:a member1 member2
SREM set:a member1
# 查询
SMEMBERS set:a # 获取所有成员(O(n),大 Set 慎用)
SCARD set:a # 获取成员数量
SISMEMBER set:a member1 # 判断成员是否存在(O(1))
# 随机操作
SRANDMEMBER set:a 3 # 随机获取3个成员(不删除)
SPOP set:a # 随机弹出1个成员(删除)
# 集合运算
SINTER set:a set:b # 交集(共同好友)
SUNION set:a set:b # 并集
SDIFF set:a set:b # 差集(a 有但 b 没有)
2.使用场景
- 标签系统:SADD user:1001:tags "vip" "active" "buyer"
- 共同好友:SINTER user:1001:friends user:1002:friends
- 抽奖/随机:SRANDMEMBER 随机选取
- 去重:SADD 自动去重
3.注意点
- SMEMBERS 大 Set 危险:10 万成员的 Set 执行 SMEMBERS 会阻塞 1 秒以上
- 用 SSCAN 替代:SSCAN set:a 0 MATCH * COUNT 100
- 内存优化:整数集合(intset)编码可节省内存(所有成员为整数时自动启用)
- 交并集复杂度:SINTER/SUNION 时间复杂度 O(N*M),大集合运算在客户端做
四、Sorted Set(ZSet,有序集合)
1.核心命令
bash
# 添加(score 为 double 类型)
ZADD zset:a 100 "player1" 200 "player2" 150 "player3"
# 范围查询(按 score 排序)
ZRANGE zset:a 0 -1 WITHSCORES # 升序获取全部
ZREVRANGE zset:a 0 9 WITHSCORES # 降序获取 Top10
# 按 score 范围查询
ZRANGEBYSCORE zset:a 100 200 WITHSCORES LIMIT 0 10
# 成员操作
ZSCORE zset:a "player1" # 获取成员 score
ZINCRBY zset:a 50 "player1" # score 增加 50
ZCARD zset:a # 成员数量
# 排名
ZRANK zset:a "player1" # 升序排名(从0开始)
ZREVRANK zset:a "player1" # 降序排名(Top1 返回 0)
# 删除
ZREM zset:a "player1"
ZREMRANGEBYRANK zset:a 0 2 # 删除排名 0-2 的成员
ZREMRANGEBYSCORE zset:a 0 100 # 删除 score 0-100 的成员
2.使用场景
- 排行榜:游戏积分、热销榜
- 延迟队列:ZADD delay_queue "task1" + 定时扫描
- 范围查询:成绩区间、价格区间
- 带权重的队列:优先级任务
java
// 延迟队列实现
// 生产者
ZADD delay_queue 1704067200 "task:email:1001" // 2024-01-01 执行
// 消费者(定时任务)
while (true) {
Set<String> tasks = ZRANGEBYSCORE delay_queue 0 NOW() LIMIT 0 10;
for (String task : tasks) {
// 处理任务
processTask(task);
ZREM delay_queue task; // 移除
}
Thread.sleep(100);
}
3.注意点
- 跳表实现:ZSet 底层是跳表 + Hash,内存占用较高(每个元素约 100 字节)
- BigKey 阈值:成员数 > 5000 为 BigKey
- ZREVRANGE 0 -1 慎用:O(n),可能阻塞
- Score 精度:double 类型,大范围整数可能丢失精度
- 相同 Score:按字典序排序,不保证插入顺序
五、Hash(哈希表)
1.核心命令
bash
# 单字段操作
HSET user:1001 name "tom" age 25
HGET user:1001 name
HDEL user:1001 age
# 批量操作
HMSET user:1001 name "tom" age 25 email "tom@example.com"
HMGET user:1001 name age
# 查询
HGETALL user:1001 # 获取所有字段(O(n))
HKEYS user:1001 # 获取所有 key
HVALS user:1001 # 获取所有 value
HLEN user:1001 # 字段数量
# 存在判断
HEXISTS user:1001 name
# 数值操作
HINCRBY user:1001 age 1 # age 原子加 1
2.使用场景
- 对象存储:HSET user:1001 field value(比 String 序列化更灵活)
- 配置项:HSET config:app max_connections 100
- 购物车:HSET cart:1001 product:123 2 product:456 1(商品ID:数量)
3.注意点
- 小对象优化:Hash 的 ziplist 编码(字段 < 512 且每个值 < 64 字节)可节省内存
- HGETALL 大 Hash:可能导致 Redis 阻塞(O(n)),用 HSCAN 替代
- 内存占用:字段名重复存储(每个 Hash 独立),小对象适合,大对象用 String 压缩更优
- 不支持二级结构:Hash 的值只能是 String,不能嵌套 List/Set
六、Bitmap(位图)
1.核心命令
bash
# 设置位
SETBIT active:20240101 1001 1 # 用户1001 在 2024-01-01 活跃
# 获取位
GETBIT active:20240101 1001 # 返回 0/1
# 统计位数
BITCOUNT active:20240101 # 统计 1 的个数(日活)
# 位运算
BITOP AND result:3days active:20210101 active:20210102 active:20210103 # 3日留存用户
BITOP OR result:active active:20210101 active:20210102 # 两日总活跃
2.使用场景
- 日活统计:SETBIT + BITCOUNT,内存占用极低(1 亿用户 = 12.5MB)
- 用户签到:SETBIT sign:1001:202401 0 1(0 表示第 1 天)
- 权限系统:SETBIT permissions:admin 1001 1(用户1001有管理员权限)
- 去重:SETBIT seen:page 1001 1(标记用户1001访问过页面)
3.注意点
- 位数限制:2^32 位(约 42.9 亿),足够大多数场景
- SETBIT 返回旧值:注意返回值是之前的位值(0 或 1)
- BITCOUNT 范围:可指定字节范围 BITBIT key start end
- 线程安全:位操作是原子的,无需担心并发
- **内存:Bitmap 是 String 实现:最大 512MB,存储约 40 亿位
七、HyperLogLog(基数统计)
1.核心命令
bash
# 添加元素
PFADD page:uv:20240101 "user1" "user2" "user3"
# 统计基数(去重后数量)
PFCOUNT page:uv:20240101 # 返回估算值
# 合并多个 HLL
PFMERGE result:uv page:uv:20240101 page:uv:20240102 # 合并两日 UV
2.使用场景
- UV 统计:页面独立访客(误差率 0.81%)
- 去重计数:统计搜索关键词去重数
- 日活/月活:快速估算 DAU/MAU
3.注意点
- 误差率:标准误差 0.81%,不适合需要精确计数的场景(如金融)
- 固定内存:每个 HLL 占用 固定 12KB,与元素数量无关
- 无法获取原始数据:只能统计数量,无法返回具体元素
- PFADD 可重复:重复添加同一元素不影响计数
八、Geospatial(地理位置)
1.核心命令
bash
# 添加坐标
GEOADD cities 116.40 39.90 "Beijing" 121.47 31.23 "Shanghai"
# 查询坐标
GEOPOS cities "Beijing"
# 计算距离(默认米)
GEODIST cities "Beijing" "Shanghai" km # 返回公里数
# 查询半径内成员
GEORADIUS cities 116.40 39.90 100 km # 北京100km内的城市
GEORADIUSBYMEMBER cities "Beijing" 100 km # 以成员为中心查询
# 获取哈希
GEOHASH cities "Beijing" # 返回52位geohash字符串
2.使用场景
- 附近的人:查询 5km 内的用户
- 门店搜索:查找附近的餐馆、加油站
- 轨迹记录:存储用户移动轨迹
3.注意点
- 距离单位:默认为米,支持 km/m/mi/ft
- 坐标精度:有效经度 -180~180,纬度 -85~85
- 数据类型:底层是 ZSet,可用 ZSet 命令操作(但会破坏地理结构)
- 误差:地球建模为球体,有 0.5% 误差
- 性能:GEORADIUS 复杂度 O(N+log(m)),N 是半径内数量,m 是总成员数
九、Stream(消息流,Redis 5.0+)
1.核心命令
bash
# 添加消息
XADD stream:orders * user_id 1001 amount 99.00 # * 表示自动生成 ID(时间戳-序列号)
# 读取消息
XRANGE stream:orders - + # 读取所有消息
XRANGE stream:orders 1704067200000-0 1704153600000-0 # 按时间范围
# 阻塞读取(消费者)
XREAD COUNT 10 BLOCK 1000 STREAMS stream:orders $ # $ 表示只读取新消息,阻塞1000ms
# 消费者组
XGROUP CREATE stream:orders order_group $ MKSTREAM # 创建消费者组
# 消费者读取(ACK 机制)
XREADGROUP GROUP order_group consumer1 COUNT 1 BLOCK 1000 STREAMS stream:orders >
# 确认消息(ACK)
XACK stream:orders order_group 1704067200000-0
# 未 ACK 消息查询
XPENDING stream:orders order_group
# 消息删除
XDEL stream:orders 1704067200000-0 # 标记删除(非真正删除)
2.使用场景
- 可靠消息队列:替代 List,支持 ACK 和重试
- 事件溯源:存储用户行为事件流
- 流计算:实时统计订单金额
3.注意点
- ID 格式:毫秒时间戳-序列号(如 1704067200000-0),保证递增
- 消费者组:多个消费者共享消息(负载均衡),需 ACK 确认
- 消息积压:未 ACK 的消息会保留,可能导致内存膨胀
- 不支持删除中间消息:只能删除两端(XRANGE 后惰性删除)
- Stream 不是 Kafka:不支持分区副本,单机容量有限
十、通用注意事项(所有数据结构)
1. BigKey 问题
bash
# 识别 BigKey
redis-cli --bigkeys
# 阈值
String: value > 10KB
List/Set/ZSet/Hash: 元素数 > 5000
- 危害:删除、查询操作会阻塞 Redis(时间复杂度 O(n))
- 解决:拆分、压缩、异步删除(UNLINK 命令)
2. 慢查询
bash
CONFIG SET slowlog-log-slower-than 10000 # 超过 10ms 记录
SLOWLOG GET 10 # 查看慢查询
- 常见慢操作:KEYS *、FLUSHALL、LRANGE 0 -1、HGETALL 大 Hash
3. 过期策略
bash
# 设置过期
EXPIRE key 60 # 60秒后过期
SETEX key 60 value # 原子设置+过期
PERSIST key # 移除过期
# 淘汰策略(maxmemory-policy)
noeviction # 不淘汰,返回错误(默认)
allkeys-lru # 所有 key LRU 淘汰
volatile-lru # 仅过期 key LRU 淘汰
allkeys-random # 随机淘汰
volatile-ttl # 淘汰 TTL 短的
4. 事务
bash
# MULTI/EXEC 打包命令(非原子性,仅排队)
MULTI
SET a 1
SET b 2
EXEC # 一起执行,但中间可能被其他命令插入
# WATCH 实现乐观锁
WATCH balance
MULTI
DECRBY balance 100
INCRBY points 10
EXEC # 如果 balance 被修改,EXEC 返回 nil(回滚)
注意 :Redis 事务不支持回滚,命令语法错误会全部失败,运行时错误会继续执行后续命令
5. Lua 脚本(原子操作)
bash
-- 原子执行,保证多个命令不被打断
EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock:order:1001 "uuid"
6. 数据一致性
- 缓存与 DB 不一致:更新 DB 后删除缓存(非更新缓存),或 Canal 监听 Binlog
- 主从延迟:从库读取可能读到旧数据,重要读走主库
7. 客户端
- 连接池:Jedis 需配置连接池,Lettuce 基于 Netty 自动管理
- 管道(Pipeline):批量操作减少 RTT
bash
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 1000; i++) {
pipeline.set("key" + i, "value" + i);
}
pipeline.sync(); // 一次性发送
十一、数据结构选型决策树
bash
存储单个值?
├── 是 → String(缓存、计数器)
│
存储对象(多个字段)?
├── 是 → Hash(用户资料、配置)
│
需要队列/栈?
├── 是 → List(消息队列、最新列表)
│
需要去重集合?
├── 是 → Set(标签、抽奖)
│
需要排序/排行榜?
├── 是 → ZSet(积分排行)
│
需要判断存在性且数据量大?
├── 是 → Bitmap(日活、签到)
│
需要估算去重数?
├── 是 → HyperLogLog(UV)
│
需要地理位置?
├── 是 → Geospatial(附近的人)
│
需要可靠消息队列?
└── 是 → Stream(带 ACK 的 MQ)
十二、总结
Redis 的数据结构是"为场景而生":String 负责缓存计数,Hash 存储对象,List 实现队列,Set 去重,ZSet 排序,Bitmap 极致空间,HyperLogLog 估算去重,Geospatial 处理位置,Stream 支撑可靠消息。核心注意点是:避免 BigKey、警惕慢查询、理解内存编码、合理设置过期