前言Redis 之所以能成为后端开发的“瑞士军刀”很大程度上归功于它丰富且高效的数据结构。不同于传统的 key-value 数据库Redis 的 value 支持多种结构让开发者能够像使用编程语言中的集合一样操作数据。本文将结合 Redis 7深入剖析九大数据结构的命令、底层设计、使用场景及避坑指南。本文所有命令均可在Redis7中运行建议开启一个redis-cil边看边试。一、字符串String——最灵活的基石字符串是Redis中最简单的类型但是它的能力远超你的想象。一个key对应一个值值可以是文本、整数、浮点数、甚至是二进制数据。1.1基本命令# 基本存取SET user:1001royGET user:1001# 批量操作减少网络往返MSET user:1001royuser:1002loulanMGET user:1001 user:1002# 仅当 key 不存在时设置分布式锁核心SETNX lock:orderlocked# 带过期时间的原子设置防止死锁SET lock:orderlockedEX10NX# 追加内容APPEND user:1001 is a coder# 原子增减用于计数、限流INCR article:1001:views DECR product:stock INCRBY balance100DECRBY balance501.2底层原理Redis 3.2 以后字符串使用 SDSSimple Dynamic String 结构。SDS 记录了长度获取长度 O(1)且杜绝缓冲区溢出。整数编码当值是整数且在-2^63 ~ 2^63-1范围内Redis 会使用 int 编码存储极大节省内存并支持高效加减。1.3经典应用场景场景实现方式对象缓存set user:1 ‘{“name”:“roy”}’ 或 MSET 拆字段分布式锁SET key value NX EX seconds计数器文章阅读量、商品库存、限流INCR 过期全局ID生成INCR global:uuid二、哈希Hash——对象储存神器哈希相当于一个 key 对应一个内部字典适合存储对象的多字段。2.1基本命令HSET user:1001 name roy age28HGET user:1001 name HMGET user:1001 name age HGETALL user:1001 HDEL user:1001 age HLEN user:1001 HINCRBY user:1001 age1# 字段自增HINCRBYFLOAT user:1001 score0.5# 浮点数增量HSETNX user:1001 emailxxxx.com# 字段不存在才设置2.2应用案例电商购物车# 以用户 1001 的购物车为例商品 10088 数量 1HSET cart:1001100881HINCRBY cart:1001100881# 增加一件HLEN cart:1001# 购物车商品种类数HGETALL cart:1001# 获取所有商品及数量HDEL cart:100110088# 删除商品2.3底层原理Redis 哈希底层使用 ziplist压缩列表 或 hashtable字典。当字段少且 value 短时使用 ziplist 连续存储节省内存。字段增多后自动转为 hashtable查询 O(1)。Redis 7 优化了 ziplist 的内存布局进一步减少碎片。三、列表List——双端队列List是一个双向链表结构支持头尾操作。3.1核心命令LPUSH queue a b c# 左侧插入三个元素最终顺序 c b aRPUSH queue x y z# 右侧插入LPOP queue# 弹出左边元素RPOP queue LRANGE queue0-1# 查看全部LINDEX queue1# 按下标取值LREM queue2a# 删除两个值为 a 的元素BLPOP queue10# 阻塞左弹超时 10 秒0 表示永久阻塞BRPOPLPUSH src dest10# 阻塞右弹并左推到另一个列表数据模型及应用数据结构命令组合应用场景栈StackLPUSH LPOP最新消息、撤销操作队列QueueLPUSH RPOP异步任务、订单处理阻塞队列LPUSH BRPOP简易消息队列生产者-消费者有限列表LTRIM 修剪保留最近 N 条日志3.2底层原理Redis 3.2 之后列表底层使用 quicklist——一个由 ziplist 组成的双向链表。每个 ziplist 存放一段连续数据减少内存碎片。两端插入是 O(1)中间插入/删除是 O(N)。注意事项容量上限2^32 - 1 个元素约 42 亿但注意 大 key 问题。不要用 LRANGE 取超大范围的元素会阻塞主线程。阻塞命令 BLPOP 等可能造成客户端假死需合理设置超时。四、集合Set——无序且唯一Set是一个无序的字符串集合支持交、并、差集运算4.1常用命令SADD tagsredisdatabaseSMEMBERS tags SISMEMBER tagsredisSCARD tags# 元素个数SREM tagsdatabaseSRANDMEMBER tags2# 随机取 2 个不删除SPOP tags1# 随机弹出 1 个删除4.2集合运算用于数据分析SINTER set1 set2# 交集SUNION set1 set2# 并集SDIFF set1 set2# 差集属于 set1 不属于 set2# 并存储到新集合SINTERSTORE new_set set1 set24.3经典案例微信抽奖小程序参与SADD lottery:20250101 user:1001查看所有参与者SMEMBERS lottery:20250101抽 3 名中奖者允许重复抽SRANDMEMBER lottery:20250101 3抽 3 名并移除一人只能中一次SPOP lottery:20250101 3社交关系我关注的人SADD follow:roy tom jerry共同关注SINTER follow:roy follow:loulan我关注的人也关注她交集判断点赞/收藏点赞SADD like:article:1001 user:1002取消SREM是否点赞SISMEMBER点赞总数SCARD4.4底层实现整数集合 (intset)当所有元素都是整数且数量少时使用有序整数数组存储二分查找 O(logN)。哈希表 (dict)元素多或含字符串时转为哈希表操作 O(1)。五、有序集合ZSet——排行榜之王ZSet 每个元素关联一个 double 类型的分数score按分数从小到大排序。5.1基本命令ZADD rank100roy90loulan# 添加或更新分数ZSCORE rankroyZINCRBY rank10roy# 增加分数ZRANGE rank0-1WITHSCORES# 正序带分数ZREVRANGE rank0-1WITHSCORES# 倒序高分在前ZRANK rankroy# 排名从 0 开始ZREVRANK rankroy# 倒序排名ZREM rankloulanZCARD rank# 元素个数ZREVRANGEBYSCORE rank200100WITHSCORES# 按分数区间倒序5.2聚合操作合并排行榜ZUNIONSTORE week_rank2day1_rank day2_rank# 合并两个集合ZINTERSTORE common2set1 set2# 交集分数可自定义聚合方式5.3实战案例新闻热搜榜# 某新闻被点击一次ZINCRBY hot:202501011Redis 7 发布# 获取今日前十ZREVRANGE hot:2025010109WITHSCORES# 计算七日热点合并 7 个日榜ZUNIONSTORE hot:week7hot:20250101 hot:20250102... hot:20250107# 显示七日榜前十ZREVRANGE hot:week09WITHSCORES六、Bitmap —— 位级别的魔法Bitmap 其实不是新的数据类型它是 string 类型的一种按位操作模式。一个 bit 只能存 0 或 1。6.1核心命令SETBIT sign:user:10013651# 第 365 天签到365 位偏移量GETBIT sign:user:1001365BITCOUNT sign:user:1001# 统计这位用户一共签到多少天BITPOS sign:user:10011# 第一个签到日的偏移量BITOP AND result bit1 bit2# 位运算AND/OR/XOR/NOT6.2应用案列每日签到# 用户 1001 在 2025 年第 100 天签到SETBIT sign:2025:10011001# 统计该用户 2025 年签到次数BITCOUNT sign:2025:1001# 统计某一天内签到的总用户数# 需要将所有用户的当天位集中到一个 bitmap 中然后 BITCOUNT优点与限制极省内存存储 1 亿个标志位仅需 12 MB 左右。操作极快位操作由 CPU 直接支持。偏移量最大 2^32约 4.29e9足够日常使用。七、HyperLogLog —— 基数统计的轻骑兵HyperLogLogHLL是一种概率数据结构用于统计集合中 不重复元素的数量基数误差率约 0.81%但内存仅需 12KB7.1核心命令PFADD uv:20250101ip1ip2ip1# 添加元素重复自动去重PFCOUNT uv:20250101# 返回估算的独立访客数PFMERGE uv:week uv:mon uv:tue...# 合并多日数据用于周 UV7.2应用场景统计网站的独立访客UV统计搜索词的去重数量注册IP去重计数7.3原理浅析HLL 使用哈希函数将元素映射成一个二进制串然后观察前导零的最大长度来估算基数。Redis 实现时用了 16384 个寄存器桶每个 6 位总内存 12KB。八、GEO——位置即信息GEO基于ZSet实现将经纬度编码成score存储8.1核心命令GEOADD cities113.01748928.200454火车站112.9690328.201195橘子洲GEOPOS cities火车站# 获取经纬度GEODIST cities火车站橘子洲km# 计算距离# 查询某个点半径 5km 内的地点GEORADIUS cities113.01728.2005km WITHDIST ASC# 查询某个成员附近的地点GEORADIUSBYMEMBER cities火车站2km# Redis 6.2 引入的新命令GEOSEARCH cities FROMLONLAT113.01728.200BYRADIUS5km8.2实战附近商家GEOADD shops113.01748928.200454麦当劳113.01928.201星巴克GEORADIUS shops113.01728.2001km WITHDIST# 显示 1 公里内的店铺8.3底层原理GEO 将经度、纬度转换为 52 位的 Geohash 整数作为 ZSet 的 score。因此 GEO 的命令本质是 ZSet 命令的封装支持范围查询。九、Stream——强大的消息队列Redis 5.0 引入 Stream弥补了 List 和 Pub/Sub 的不足支持持久化、消费者组、消息确认等。9.1基本命令# 添加消息* 表示自动生成 ID格式 时间戳-序号XADD mystream * name roy age28XLEN mystream XRANGE mystream - COUNT2# 遍历消息# 创建消费者组从头部开始消费XGROUP CREATE mystream group10# 消费者 consumer1 读取消息 表示从未消费过的开始XREADGROUP GROUP group1 consumer1 COUNT1STREAMS mystream# 确认消息处理完成XACK mystream group1 message_id消费者组特性多个消费者自动分配分区类似 Kafka挂起的消息可通过 XPENDING 查看和重试支持 XCLAIM 转移未确认的消息9.2应用场景简单的订单消息系统日志收集管道实时数据同步十、总结与选型建议数据结构最优场景不适合场景String缓存、计数器、简单存储复杂查询、对象内部操作Hash对象存储、购物车需要单个 field 过期、集群倾斜List队列、栈、最新消息随机访问、超大列表Set唯一性判断、标签、共同关注需要排序ZSet排行榜、范围查询、延迟队列频繁更新分数、内存敏感Bitmap签到、布尔状态、布隆过滤器稀疏数据浪费空间HyperLogLog海量数据基数统计UV需要精确计数或获取元素GEOLBS 应用、附近的人/店铺复杂的空间运算Stream可靠消息队列、消费者组超高吞吐不如 KafkaRedis 的世界远不止 CRUD理解数据结构的内核才能写出高效、可靠的代码。希望这篇长文能成为你 Redis 进阶之路上的一个路标。如有疑问欢迎评论区讨论
Redis核心数据结构全解:从底层原理到实战应用
发布时间:2026/6/1 9:07:00
前言Redis 之所以能成为后端开发的“瑞士军刀”很大程度上归功于它丰富且高效的数据结构。不同于传统的 key-value 数据库Redis 的 value 支持多种结构让开发者能够像使用编程语言中的集合一样操作数据。本文将结合 Redis 7深入剖析九大数据结构的命令、底层设计、使用场景及避坑指南。本文所有命令均可在Redis7中运行建议开启一个redis-cil边看边试。一、字符串String——最灵活的基石字符串是Redis中最简单的类型但是它的能力远超你的想象。一个key对应一个值值可以是文本、整数、浮点数、甚至是二进制数据。1.1基本命令# 基本存取SET user:1001royGET user:1001# 批量操作减少网络往返MSET user:1001royuser:1002loulanMGET user:1001 user:1002# 仅当 key 不存在时设置分布式锁核心SETNX lock:orderlocked# 带过期时间的原子设置防止死锁SET lock:orderlockedEX10NX# 追加内容APPEND user:1001 is a coder# 原子增减用于计数、限流INCR article:1001:views DECR product:stock INCRBY balance100DECRBY balance501.2底层原理Redis 3.2 以后字符串使用 SDSSimple Dynamic String 结构。SDS 记录了长度获取长度 O(1)且杜绝缓冲区溢出。整数编码当值是整数且在-2^63 ~ 2^63-1范围内Redis 会使用 int 编码存储极大节省内存并支持高效加减。1.3经典应用场景场景实现方式对象缓存set user:1 ‘{“name”:“roy”}’ 或 MSET 拆字段分布式锁SET key value NX EX seconds计数器文章阅读量、商品库存、限流INCR 过期全局ID生成INCR global:uuid二、哈希Hash——对象储存神器哈希相当于一个 key 对应一个内部字典适合存储对象的多字段。2.1基本命令HSET user:1001 name roy age28HGET user:1001 name HMGET user:1001 name age HGETALL user:1001 HDEL user:1001 age HLEN user:1001 HINCRBY user:1001 age1# 字段自增HINCRBYFLOAT user:1001 score0.5# 浮点数增量HSETNX user:1001 emailxxxx.com# 字段不存在才设置2.2应用案例电商购物车# 以用户 1001 的购物车为例商品 10088 数量 1HSET cart:1001100881HINCRBY cart:1001100881# 增加一件HLEN cart:1001# 购物车商品种类数HGETALL cart:1001# 获取所有商品及数量HDEL cart:100110088# 删除商品2.3底层原理Redis 哈希底层使用 ziplist压缩列表 或 hashtable字典。当字段少且 value 短时使用 ziplist 连续存储节省内存。字段增多后自动转为 hashtable查询 O(1)。Redis 7 优化了 ziplist 的内存布局进一步减少碎片。三、列表List——双端队列List是一个双向链表结构支持头尾操作。3.1核心命令LPUSH queue a b c# 左侧插入三个元素最终顺序 c b aRPUSH queue x y z# 右侧插入LPOP queue# 弹出左边元素RPOP queue LRANGE queue0-1# 查看全部LINDEX queue1# 按下标取值LREM queue2a# 删除两个值为 a 的元素BLPOP queue10# 阻塞左弹超时 10 秒0 表示永久阻塞BRPOPLPUSH src dest10# 阻塞右弹并左推到另一个列表数据模型及应用数据结构命令组合应用场景栈StackLPUSH LPOP最新消息、撤销操作队列QueueLPUSH RPOP异步任务、订单处理阻塞队列LPUSH BRPOP简易消息队列生产者-消费者有限列表LTRIM 修剪保留最近 N 条日志3.2底层原理Redis 3.2 之后列表底层使用 quicklist——一个由 ziplist 组成的双向链表。每个 ziplist 存放一段连续数据减少内存碎片。两端插入是 O(1)中间插入/删除是 O(N)。注意事项容量上限2^32 - 1 个元素约 42 亿但注意 大 key 问题。不要用 LRANGE 取超大范围的元素会阻塞主线程。阻塞命令 BLPOP 等可能造成客户端假死需合理设置超时。四、集合Set——无序且唯一Set是一个无序的字符串集合支持交、并、差集运算4.1常用命令SADD tagsredisdatabaseSMEMBERS tags SISMEMBER tagsredisSCARD tags# 元素个数SREM tagsdatabaseSRANDMEMBER tags2# 随机取 2 个不删除SPOP tags1# 随机弹出 1 个删除4.2集合运算用于数据分析SINTER set1 set2# 交集SUNION set1 set2# 并集SDIFF set1 set2# 差集属于 set1 不属于 set2# 并存储到新集合SINTERSTORE new_set set1 set24.3经典案例微信抽奖小程序参与SADD lottery:20250101 user:1001查看所有参与者SMEMBERS lottery:20250101抽 3 名中奖者允许重复抽SRANDMEMBER lottery:20250101 3抽 3 名并移除一人只能中一次SPOP lottery:20250101 3社交关系我关注的人SADD follow:roy tom jerry共同关注SINTER follow:roy follow:loulan我关注的人也关注她交集判断点赞/收藏点赞SADD like:article:1001 user:1002取消SREM是否点赞SISMEMBER点赞总数SCARD4.4底层实现整数集合 (intset)当所有元素都是整数且数量少时使用有序整数数组存储二分查找 O(logN)。哈希表 (dict)元素多或含字符串时转为哈希表操作 O(1)。五、有序集合ZSet——排行榜之王ZSet 每个元素关联一个 double 类型的分数score按分数从小到大排序。5.1基本命令ZADD rank100roy90loulan# 添加或更新分数ZSCORE rankroyZINCRBY rank10roy# 增加分数ZRANGE rank0-1WITHSCORES# 正序带分数ZREVRANGE rank0-1WITHSCORES# 倒序高分在前ZRANK rankroy# 排名从 0 开始ZREVRANK rankroy# 倒序排名ZREM rankloulanZCARD rank# 元素个数ZREVRANGEBYSCORE rank200100WITHSCORES# 按分数区间倒序5.2聚合操作合并排行榜ZUNIONSTORE week_rank2day1_rank day2_rank# 合并两个集合ZINTERSTORE common2set1 set2# 交集分数可自定义聚合方式5.3实战案例新闻热搜榜# 某新闻被点击一次ZINCRBY hot:202501011Redis 7 发布# 获取今日前十ZREVRANGE hot:2025010109WITHSCORES# 计算七日热点合并 7 个日榜ZUNIONSTORE hot:week7hot:20250101 hot:20250102... hot:20250107# 显示七日榜前十ZREVRANGE hot:week09WITHSCORES六、Bitmap —— 位级别的魔法Bitmap 其实不是新的数据类型它是 string 类型的一种按位操作模式。一个 bit 只能存 0 或 1。6.1核心命令SETBIT sign:user:10013651# 第 365 天签到365 位偏移量GETBIT sign:user:1001365BITCOUNT sign:user:1001# 统计这位用户一共签到多少天BITPOS sign:user:10011# 第一个签到日的偏移量BITOP AND result bit1 bit2# 位运算AND/OR/XOR/NOT6.2应用案列每日签到# 用户 1001 在 2025 年第 100 天签到SETBIT sign:2025:10011001# 统计该用户 2025 年签到次数BITCOUNT sign:2025:1001# 统计某一天内签到的总用户数# 需要将所有用户的当天位集中到一个 bitmap 中然后 BITCOUNT优点与限制极省内存存储 1 亿个标志位仅需 12 MB 左右。操作极快位操作由 CPU 直接支持。偏移量最大 2^32约 4.29e9足够日常使用。七、HyperLogLog —— 基数统计的轻骑兵HyperLogLogHLL是一种概率数据结构用于统计集合中 不重复元素的数量基数误差率约 0.81%但内存仅需 12KB7.1核心命令PFADD uv:20250101ip1ip2ip1# 添加元素重复自动去重PFCOUNT uv:20250101# 返回估算的独立访客数PFMERGE uv:week uv:mon uv:tue...# 合并多日数据用于周 UV7.2应用场景统计网站的独立访客UV统计搜索词的去重数量注册IP去重计数7.3原理浅析HLL 使用哈希函数将元素映射成一个二进制串然后观察前导零的最大长度来估算基数。Redis 实现时用了 16384 个寄存器桶每个 6 位总内存 12KB。八、GEO——位置即信息GEO基于ZSet实现将经纬度编码成score存储8.1核心命令GEOADD cities113.01748928.200454火车站112.9690328.201195橘子洲GEOPOS cities火车站# 获取经纬度GEODIST cities火车站橘子洲km# 计算距离# 查询某个点半径 5km 内的地点GEORADIUS cities113.01728.2005km WITHDIST ASC# 查询某个成员附近的地点GEORADIUSBYMEMBER cities火车站2km# Redis 6.2 引入的新命令GEOSEARCH cities FROMLONLAT113.01728.200BYRADIUS5km8.2实战附近商家GEOADD shops113.01748928.200454麦当劳113.01928.201星巴克GEORADIUS shops113.01728.2001km WITHDIST# 显示 1 公里内的店铺8.3底层原理GEO 将经度、纬度转换为 52 位的 Geohash 整数作为 ZSet 的 score。因此 GEO 的命令本质是 ZSet 命令的封装支持范围查询。九、Stream——强大的消息队列Redis 5.0 引入 Stream弥补了 List 和 Pub/Sub 的不足支持持久化、消费者组、消息确认等。9.1基本命令# 添加消息* 表示自动生成 ID格式 时间戳-序号XADD mystream * name roy age28XLEN mystream XRANGE mystream - COUNT2# 遍历消息# 创建消费者组从头部开始消费XGROUP CREATE mystream group10# 消费者 consumer1 读取消息 表示从未消费过的开始XREADGROUP GROUP group1 consumer1 COUNT1STREAMS mystream# 确认消息处理完成XACK mystream group1 message_id消费者组特性多个消费者自动分配分区类似 Kafka挂起的消息可通过 XPENDING 查看和重试支持 XCLAIM 转移未确认的消息9.2应用场景简单的订单消息系统日志收集管道实时数据同步十、总结与选型建议数据结构最优场景不适合场景String缓存、计数器、简单存储复杂查询、对象内部操作Hash对象存储、购物车需要单个 field 过期、集群倾斜List队列、栈、最新消息随机访问、超大列表Set唯一性判断、标签、共同关注需要排序ZSet排行榜、范围查询、延迟队列频繁更新分数、内存敏感Bitmap签到、布尔状态、布隆过滤器稀疏数据浪费空间HyperLogLog海量数据基数统计UV需要精确计数或获取元素GEOLBS 应用、附近的人/店铺复杂的空间运算Stream可靠消息队列、消费者组超高吞吐不如 KafkaRedis 的世界远不止 CRUD理解数据结构的内核才能写出高效、可靠的代码。希望这篇长文能成为你 Redis 进阶之路上的一个路标。如有疑问欢迎评论区讨论