redis数据类型及使用场景

发布时间:2026/7/1 16:45:03
redis数据类型及使用场景 redis数据类型及使用场景一、数据类型与场景总览数据类型底层结构核心特性典型场景性能边界StringSDS / int / embstr二进制安全最大 512MB缓存、计数、分布式锁、SessionO(1)Hashziplist / hashtable字段级操作节省内存对象缓存、购物车、配置存储O(1) 字段访问Listquicklist双向链表有序消息队列、时间线、栈/队列O(1) 头尾O(N) 中间Setintset / hashtable唯一性集合运算标签、关注关系、抽奖、去重O(1) 增删查ZSetskiplist dict有序按分数排名排行榜、延迟队列、滑动窗口限流O(log N)Bitmapraw string位操作极省空间签到、在线状态、布隆过滤器O(1) 位操作HyperLogLogsparse / dense基数估算固定 12KBUV 统计、海量去重计数O(1)误差 0.81%Geosorted set经纬度存储距离计算附近的人、位置服务O(log N) 添加Streamradix tree listpack持久化消息队列消费者组事件溯源、消息队列、日志收集O(log N) 添加JSONReJSON 模块路径查询原子更新文档存储、配置管理、嵌套对象依赖路径深度二、String最基础也最万能2.1 典型场景┌─────────────────────────────────────────────────────────────────┐ │ 场景 1对象缓存KV 缓存 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ SET user:1001 {id:1001,name:Alice,level:5} EX 3600 │ │ GET user:1001 │ │ │ │ 适用用户信息、商品详情、配置项等读多写少的完整对象 │ │ 注意Value 较大时 10KB考虑 Hash 字段拆分或压缩 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 2计数器原子操作 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ INCR article:1001:views # 文章阅读量 1 │ │ INCRBY user:1001:credits 10 # 用户积分 10 │ │ DECR stock:sku:8888 # 库存扣减 │ │ │ │ 原子性保证INCR/DECR 是单命令无需事务 │ │ 注意计数上限 2^63-1溢出后报错 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 3分布式锁Redlock 算法 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ SET lock:order:1001 my-thread-id NX EX 30 │ │ # NX 仅不存在时才设置互斥 │ │ # EX 30 30 秒过期防止死锁 │ │ │ │ 释放锁 │ │ if redis.call(get, KEYS[1]) ARGV[1] then │ │ return redis.call(del, KEYS[1]) │ │ else return 0 end │ │ │ │ 陷阱时钟漂移、主从切换导致锁丢失Redisson 已解决 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 4Session 存储 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ SET session:abc123 {userId:1001,loginTime:...} EX 1800 │ │ │ │ 替代 Tomcat 本地 Session支持分布式部署 │ │ 配合 Spring Session 自动集成 │ │ │ └─────────────────────────────────────────────────────────────────┘2.2 实战陷阱陷阱说明解决大 Key单个 String 10MB拆分为 Hash 字段或压缩Snappy/LZ4热 Key单个 Key 被百万 QPS 访问本地缓存 随机后缀打散如config:1~config:10批量操作循环 GET 1000 次改用 MGET或 Pipeline三、Hash对象字段级操作3.1 典型场景┌─────────────────────────────────────────────────────────────────┐ │ 场景 1用户信息对象替代 JSON String │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ String 方案不好 │ │ SET user:1001 {name:Alice,age:25,city:Beijing} │ │ 修改年龄GET → 解析 JSON → 修改 → 序列化 → SET全量覆盖 │ │ │ │ Hash 方案推荐 │ │ HSET user:1001 name Alice age 25 city Beijing │ │ 修改年龄HSET user:1001 age 26 # 仅修改字段O(1) │ │ 获取城市HGET user:1001 city │ │ │ │ 内存优势ziplist 编码时字段少且短比 String 省 50% 内存 │ │ 编码转换字段数 512 或字段值 64B 时ziplist → hashtable │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 2购物车字段 SKU值 数量 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ HSET cart:user:1001 sku:8888 2 │ │ HSET cart:user:1001 sku:9999 1 │ │ HGETALL cart:user:1001 # 获取整个购物车 │ │ HINCRBY cart:user:1001 sku:8888 1 # 加购 │ │ HDEL cart:user:1001 sku:8888 # 删除商品 │ │ │ │ 过期控制Hash 不支持整体过期需配合顶层 Key 或使用 String JSON │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 3配置项分组存储 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ HSET config:db host localhost port 3306 user admin │ │ HSET config:cache ttl 3600 maxSize 10000 │ │ │ │ 优势配置按组管理批量读取HGETALL或单字段更新 │ │ │ └─────────────────────────────────────────────────────────────────┘3.2 Hash vs String 选型决策┌─────────────────────────────────────────────────────────────────┐ │ 决策树对象缓存选 Hash 还是 String │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 对象字段是否经常单独更新 │ │ │ │ │ YES → 用 HashHSET/HGET 字段级操作 │ │ │ │ │ NO → 对象是否很大 10KB │ │ │ │ │ YES → 用 String 压缩Snappy │ │ │ │ │ NO → 是否需要整体过期 │ │ │ │ │ YES → StringSET EX 原子过期 │ │ │ │ │ NO → Hash内存更省 │ │ │ └─────────────────────────────────────────────────────────────────┘四、List有序队列4.1 典型场景┌─────────────────────────────────────────────────────────────────┐ │ 场景 1消息队列轻量级非高可靠场景 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 生产者LPUSH queue:email {to:userx.com,content:...} │ │ 消费者BRPOP queue:email 30 # 阻塞等待 30 秒 │ │ │ │ 特点 │ │ - 单消费者LPUSH BRPOP 简单可靠 │ │ - 多消费者BRPOP 竞争消费每条消息只被一个消费者处理 │ │ - 无 ACK 机制消费者崩溃消息丢失对比 Stream 的消费者组 │ │ │ │ 适用日志收集、异步任务、通知推送允许少量丢失 │ │ 不适用金融交易、订单处理需 Stream 或专业 MQ │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 2时间线/Feed 流最新 N 条 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ LPUSH timeline:user:1001 {id:123,text:hello} │ │ LTRIM timeline:user:1001 0 99 # 只保留最新 100 条 │ │ LRANGE timeline:user:1001 0 19 # 获取首页 20 条 │ │ │ │ 特点O(1) 头尾操作LTRIM 原子裁剪 │ │ 对比ZSet 可实现按时间分数排序但 List 更轻量 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 3栈Stack和队列Queue │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 栈后进先出LPUSH LPOP │ │ 队列先进先出LPUSH BRPOP │ │ 双端队列LPUSH/RPUSH LPOP/RPOP │ │ │ └─────────────────────────────────────────────────────────────────┘4.2 List 的陷阱陷阱说明解决LINDEX/LSET 中间操作O(N) 遍历大 List 性能灾难避免或用 ZSet无 ACK 机制消费者崩溃消息丢失重要业务用 Stream阻塞连接占用BRPOP 长时间阻塞消耗连接池控制超时或使用 Stream五、Set唯一集合5.1 典型场景┌─────────────────────────────────────────────────────────────────┐ │ 场景 1标签系统Tag │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ SADD article:1001:tags java redis distributed-system │ │ SADD article:1002:tags java spring microservice │ │ │ │ 查找共同标签SINTER article:1001:tags article:1002:tags │ │ → {java} │ │ │ │ 查找文章 1001 的所有标签SMEMBERS article:1001:tags │ │ │ │ 查找有 redis 标签的所有文章反向索引 │ │ SADD tag:redis article:1001 article:1005 article:1010 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 2关注关系社交图谱 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ user:1001 关注了 user:1002, 1003, 1004 │ │ SADD following:user:1001 1002 1003 1004 │ │ │ │ user:1002 的粉丝SADD followers:user:1002 1001 1005 │ │ │ │ 共同关注SINTER following:user:1001 following:user:1002 │ │ 可能认识SDIFF following:user:1002 following:user:1001 │ │ user:1002 关注但 user:1001 没关注的 │ │ │ │ 注意大数据量时百万粉丝Set 内存消耗大考虑 │ │ - 分片followers:user:1002:0, followers:user:1002:1... │ │ - 或改用 ZSet带时间分数支持范围查询 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 3抽奖/随机选取SRANDMEMBER/SPOP │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ SADD lottery:pool user1 user2 user3 ... user10000 │ │ SRANDMEMBER lottery:pool 3 # 随机抽 3 个不删除 │ │ SPOP lottery:pool 3 # 随机抽 3 个从池子移除 │ │ │ │ 适用抽奖、随机推荐、A/B 测试分组 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 4UV 去重配合 HyperLogLog 的精确版 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ SADD uv:2024-01-01 user1 user2 user3 ... │ │ SCARD uv:2024-01-01 # 精确 UV 数 │ │ │ │ 内存问题百万 UV 百万个字符串内存爆炸 │ │ 优化用 HyperLogLog 估算12KB 固定内存误差 0.81% │ │ │ └─────────────────────────────────────────────────────────────────┘六、ZSet有序集合最强大类型6.1 典型场景┌─────────────────────────────────────────────────────────────────┐ │ 场景 1排行榜Leaderboard │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ZADD leaderboard 1500 player:Alice │ │ ZADD leaderboard 2300 player:Bob │ │ ZADD leaderboard 1800 player:Carol │ │ │ │ 获取前 10 名ZREVRANGE leaderboard 0 9 WITHSCORES │ │ → Bob(2300), Carol(1800), Alice(1500)... │ │ │ │ 获取 Alice 的排名ZREVRANK leaderboard player:Alice │ │ → 2从 0 开始 │ │ │ │ 获取 Alice 的分数ZSCORE leaderboard player:Alice │ │ → 1500 │ │ │ │ 更新分数ZINCRBY leaderboard 100 player:Alice │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 2延迟队列Delayed Queue │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 分数 执行时间戳毫秒 │ │ ZADD delay:queue 1705312800000 task:sendEmail:1001 │ │ ZADD delay:queue 1705316400000 task:refund:2002 │ │ │ │ 定时轮询每秒 │ │ ZRANGEBYSCORE delay:queue 0 1705312805000 LIMIT 0 1 │ │ # 获取当前时间前到期的任务 │ │ │ │ 取出执行ZPOPMIN delay:queue 1 # 原子弹出最小分数 │ │ │ │ 对比 List 的 BRPOPZSet 支持任意时间精度List 只能 FIFO │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 3滑动窗口限流Sliding Window Rate Limit │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 记录用户请求时间戳 │ │ ZADD ratelimit:user:1001 1705312800000 req:1 │ │ ZADD ratelimit:user:1001 1705312800100 req:2 │ │ ZADD ratelimit:user:1001 1705312800200 req:3 │ │ │ │ 清理过期窗口60 秒前 │ │ ZREMRANGEBYSCORE ratelimit:user:1001 0 1705312740000 │ │ │ │ 检查窗口内请求数 │ │ ZCARD ratelimit:user:1001 # 100 则允许否则拒绝 │ │ │ │ 优势精确滑动窗口而非粗糙的固定窗口 │ │ 注意高并发时 ZADD ZREMRANGEBYSCORE ZCARD 非原子需 Lua 脚本 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 4地理位置Geo底层是 ZSet │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ GEOADD locations 116.40 39.90 Beijing │ │ GEOADD locations 121.47 31.23 Shanghai │ │ GEOADD locations 113.26 23.13 Guangzhou │ │ │ │ 获取北京坐标GEOPOS locations Beijing │ │ │ │ 计算北京到上海距离GEODIST locations Beijing Shanghai km │ │ │ │ 查找北京 500km 内的城市 │ │ GEORADIUS locations 116.40 39.90 500 km WITHDIST WITHCOORD │ │ → 北京(0km), 天津(110km)... │ │ │ │ 原理GeoHash 编码为 52 位整数作为 ZSet 分数范围查询即地理范围 │ │ │ └─────────────────────────────────────────────────────────────────┘七、Bitmap位图极致空间效率7.1 典型场景┌─────────────────────────────────────────────────────────────────┐ │ 场景 1用户签到365 天仅需 46 字节/用户 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 第 1 天签到SETBIT sign:user:1001 0 1 │ │ 第 2 天签到SETBIT sign:user:1001 1 1 │ │ 第 365 天签到SETBIT sign:user:1001 364 1 │ │ │ │ 检查第 100 天是否签到GETBIT sign:user:1001 99 │ │ 统计本月签到天数BITCOUNT sign:user:1001 0 30 │ │ │ │ 内存1 亿用户 × 365 天 / 8 4.56GB对比 MySQL 存储省 100 倍 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 2在线状态亿级用户 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 用户 1001 上线SETBIT online:20240101 1001 1 │ │ 用户 1001 下线SETBIT online:20240101 1001 0 │ │ │ │ 统计今日在线人数BITCOUNT online:20240101 │ │ 检查用户是否在线GETBIT online:20240101 1001 │ │ │ │ 注意用户 ID 必须连续或映射为连续整数否则稀疏 Bitmap 浪费空间 │ │ 稀疏优化Redis 4.0 引入 BITFIELD 或改用 Roaring Bitmap外部 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 场景 3布隆过滤器Bloom Filter │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 原理多个 Hash 函数映射到 Bitmap 位判断可能存在或肯定不存在 │ │ │ │ 添加元素 │ │ for hash in hashFunctions(element): │ │ SETBIT bloom:filter hash 1 │ │ │ │ 检查存在 │ │ for hash in hashFunctions(element): │ │ if GETBIT bloom:filter hash 0: return False │ │ return True # 可能存在有误判或肯定存在 │ │ │ │ 误判率位数组越大、Hash 函数越多误判率越低 │ │ 适用缓存穿透防护、URL 去重、垃圾邮件过滤 │ │ Redis 4.0 原生模块RedisBloom │ │ │ └─────────────────────────────────────────────────────────────────┘八、HyperLogLog基数估算8.1 典型场景┌─────────────────────────────────────────────────────────────────┐ │ 场景海量 UV 统计误差可接受 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 问题1 亿独立访客用 Set 存储 → 内存 1GB │ │ │ │ HyperLogLog 方案 │ │ PFADD uv:2024-01-01 user1 user2 user3 ... user100000000 │ │ PFCOUNT uv:2024-01-01 │ │ → 100023456真实值 100000000误差 0.023% │ │ │ │ 内存固定 12KB与数据量无关 │ │ │ │ 合并多天的 UV去重总 UV │ │ PFMERGE uv:total uv:2024-01-01 uv:2024-01-02 uv:2024-01-03 │ │ PFCOUNT uv:total │ │ │ │ 适用网站 UV、广告曝光去重、搜索关键词去重 │ │ 不适用精确计数如订单金额统计 │ │ │ └─────────────────────────────────────────────────────────────────┘九、Stream持久化消息队列9.1 典型场景┌─────────────────────────────────────────────────────────────────┐ │ 场景事件溯源 消费者组替代 List 做可靠队列 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 生产者 │ │ XADD orders * userId 1001 amount 199.99 status created │ │ → 返回消息 ID1705312800000-0 │ │ │ │ 消费者组多个消费者并行处理不重复消费 │ │ XGROUP CREATE orders payment-group $ MKSTREAM │ │ │ │ 消费者 1 │ │ XREADGROUP GROUP payment-group consumer-1 COUNT 1 STREAMS orders │ │ → 读取新消息处理支付 │ │ XACK orders payment-group 1705312800000-0 # 确认已处理 │ │ │ │ 消费者 2 │ │ XREADGROUP GROUP payment-group consumer-2 COUNT 1 STREAMS orders │ │ → 读取下一条新消息 │ │ │ │ 未确认消息检查处理超时重试 │ │ XPENDING orders payment-group │ │ → 查看哪些消息已分配但未 ACK │ │ │ │ 对比 Kafka │ │ - Stream轻量无需外部依赖适合中小规模 │ │ - Kafka高吞吐、持久化、分区副本适合大规模 │ │ │ └─────────────────────────────────────────────────────────────────┘十、选型决策树┌─────────────────────────────────────────────────────────────────┐ │ Redis 数据类型选型决策树 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 需要消息队列 消费者组 ACK │ │ │ │ │ YES → Stream │ │ │ │ │ NO → 需要按分数排序/范围查询 │ │ │ │ │ YES → ZSet排行榜、延迟队列、滑动窗口 │ │ │ │ │ NO → 需要地理位置查询 │ │ │ │ │ YES → Geo底层 ZSet │ │ │ │ │ NO → 需要集合运算交并差 │ │ │ │ │ YES → Set标签、关系、抽奖 │ │ │ │ │ NO → 需要字段级更新 │ │ │ │ │ YES → Hash对象缓存、购物车 │ │ │ │ │ NO → 需要精确去重计数 │ │ │ │ │ YES → Set小数据 │ │ │ HyperLogLog大数据│ │ │ │ │ NO → 需要位操作 │ │ │ │ │ YES → Bitmap │ │ │ │ │ NO → 需要队列语义│ │ │ │ │ YES → List│ │ │ │ │ NO → String│ │ │ └─────────────────────────────────────────────────────────────────┘十一、一句话总结String是万能缓存Hash做对象字段级操作List做轻量队列Set做关系与去重ZSet是时间/分数驱动的核心引擎Bitmap/HyperLogLog用极致空间换统计能力Stream是原生持久化消息队列。架构师选型时先问是否需要排序再问是否需要精确最后才考虑是否足够简单。