【数据库】【Redis】数据结构全景图:命令、场景与避坑指南

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、警惕慢查询、理解内存编码、合理设置过期

相关推荐
想用offer打牌2 小时前
数据库大事务有什么危害(面试版)
数据库·后端·架构
sin_hielo2 小时前
leetcode 3573(买卖股票问题,状态机dp)
数据结构·算法·leetcode
Jaising6662 小时前
Spring 错误使用事务导致数据可见性问题分析
数据库·spring boot
xixingzhe22 小时前
数据、数据库分类
数据库
松涛和鸣2 小时前
34、 Linux IPC进程间通信:无名管道(Pipe) 和有名管道(FIFO)
linux·服务器·c语言·网络·数据结构·数据库
云老大TG:@yunlaoda3602 小时前
如何使用华为云国际站代理商的FunctionGraph进行事件驱动的应用开发?
大数据·数据库·华为云·云计算
清水白石0082 小时前
《用 Python 单例模式打造稳定高效的数据库连接管理器》
数据库·python·单例模式
小虾米vivian2 小时前
dmetl5 web管理平台 监控-流程监控 看不到运行信息
linux·服务器·网络·数据库·达梦数据库
yuzhucu2 小时前
django4.1.2+xadmin配置
数据库·sqlite