
🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!
🎬 博主简介:

前言:
前面我们把五大核心数据类型(String、Hash、List、Set、ZSet)从命令用法到底层编码全部拆解完了,这五类覆盖了绝大多数通用业务场景。但 Redis 的能力远不止于此,它还提供了多款面向特定场景的特殊数据类型,能在海量去重、地理查询、事件流等场景下做到极致的空间效率或功能适配。与此同时,生产环境里很多人都知道
keys *是高危操作,但对替代方案scan渐进式遍历的正确用法、底层原理和踩坑点却一知半解;Redis 的数据库概念也经常和关系型数据库混淆。本文就一次性补齐这三块内容:五类特殊数据类型的定位与适用场景、scan 渐进式遍历的原理与生产注意事项、数据库管理命令与安全红线,帮你把 Redis 基础到进阶的知识闭环。

一. 特殊数据类型补充
除了五大基础类型,Redis 还提供了 Stream、Geospatial、HyperLogLog、Bitmaps、Bitfields 等类型,它们都面向特定场景设计,属于 "了解即可、按需使用" 的范畴,不需要像基础类型那样深入掌握,但要知道它们能解决什么问题。

1.1 Stream 流类型
Stream 是 Redis 5.0 推出的重量级类型,定位是追加式日志结构,专门用来做事件流与消息队列。
- 核心特性:每条写入的消息都会生成一个全局唯一、自增的 ID;支持范围读取、阻塞读取、消费者组、消息确认(ACK)、数据截断等完整能力。
- 典型场景:事件溯源(用户行为埋点)、传感器数据上报、用户通知流、可靠消息队列。
- 核心命令 :
XADD:向流中追加一条新消息XREAD:按位置向后读取消息,支持阻塞模式XRANGE:按 ID 范围读取消息XLEN:获取流中的消息数量
相比于 List 实现的简易消息队列,Stream 功能要完整得多:支持多消费组、消息回溯、未决消息处理,是 Redis 官方推荐的消息队列方案。


1.2 Geospatial 地理空间类型
Geospatial 专门用于存储经纬度坐标,并实现 "附近的点" 类查询。
- 底层本质:没有引入全新的数据结构,而是基于 ZSet 封装,使用 geohash 算法把经纬度编码成整数作为 score,既复用了 ZSet 的排序与范围能力,又实现了坐标存储。
- 核心能力:添加坐标、计算两点距离、按半径 / 矩形范围查找附近的点。
- 核心命令 :
GEOADD:添加一个或多个坐标点GEOSEARCH:按半径或矩形范围查找附近的点GEODIST:计算两个点之间的距离GEOSEARCHSTORE:把查询结果存入新的 key
- 适用场景:周边商家、附近的人、充电桩 / 站点查询等 LBS 业务。
- 边界说明:适合中小规模的位置查询,超大规模、高精度的地理分析还是要靠专业地理数据库。



1.3 HyperLogLog 基数统计
这是一个非常经典的概率型数据结构,唯一的作用就是估算集合的基数(即不重复元素的个数)。
- 核心优势:极致节省空间。无论统计多少不重复元素,最多只占用 12KB 内存,就能支撑亿级别的基数统计。
- 精度权衡:标准误差约 0.81%,用少量的精度损失换取了极大的空间收益。它不存储元素本身,只记录元素的特征信息,因此只能计数,不能取出元素内容。
- 核心命令 :
PFADD:添加元素PFCOUNT:获取估算的基数结果
做个直观对比:如果用 Set 统计 1 亿 UV,每个用户 ID 按 8 字节算,光存储元素就要 800MB 以上;而 HyperLogLog 只需要 12KB,差距是数量级的。
- 适用场景:超大规模页面 UV、日活用户统计等对精度要求不高、但对内存成本敏感的海量去重计数场景。



1.4 Bitmaps 位图
Bitmaps 本质上不是独立类型,是对 String 类型做的位级操作封装,用单个二进制位来表示 0/1 状态。
- 核心思想:把整数 ID 作为位偏移量,每个位对应一个对象的布尔状态。相比于用 Set 存 ID,空间效率提升几十上百倍。
- 核心命令 :
SETBIT:设置指定偏移量的位为 0 或 1GETBIT:获取指定偏移量的位状态BITOP:对多个位图做与、或、非、异或等位运算
- 适用场景:用户每日签到、在线状态标记、海量布尔型状态存储、多维度用户标签的快速交并运算。
- 可以理解为:Set 类型针对整数 ID 的特化优化版,只能存 0/1 状态,但空间和运算效率更高。



1.5 Bitfields 位域
Bitfields 同样是基于 String 的扩展,类比 C 语言里的 ** 位域(位段)** 语法,可以在一个字符串里操作任意长度的整数。
- 核心作用:把多个小整数紧凑地打包到同一块内存里,极致压缩存储空间。比如把多个 4 位、8 位的计数器打包存储,比用 Hash 存多个字段省很多内存。
- 核心命令 :
BITFIELD,一条命令里可以同时执行多个 SET、GET、INCRBY 操作,整体是原子的。 - 适用场景:游戏玩家的多维度计数器、多状态位打包存储等需要极致省内存的数值场景。



二. 渐进式遍历:scan 家族
2.1 为什么要禁用 keys *
keys 命令可以按通配符匹配并返回所有符合条件的 key,使用起来非常方便,但它是生产环境的高危操作。
- 它是全量遍历,时间复杂度 O (N),一次性返回全部结果;
- 当 Redis 中 key 数量很大时,会长时间阻塞主线程,导致所有业务请求卡顿,甚至触发服务雪崩。
同理,hgetall、smembers、lrange 0 -1 这类全量读取命令,在大集合场景下都有同样的阻塞风险。 那如果业务确实需要遍历所有 key 怎么办?答案就是 scan 渐进式遍历。







2.2 核心思想:化整为零
scan 的思路非常朴素:把一次完整的全量遍历,拆分成很多次小遍历,每次只处理一小部分元素。
- 单次 scan 的时间复杂度是 O (1),不会长时间卡住主线程;
- 通过多次调用,最终完成全量遍历;
- 代价是整体遍历的总耗时更长,但不会阻塞线上服务,把一次大卡顿打散成了多次几乎感知不到的小延迟。
scan 是一个家族,对应不同的数据结构有各自的渐进式遍历命令:
SCAN:遍历整个 Redis 实例的所有 keyHSCAN:遍历 Hash 类型的所有 fieldSSCAN:遍历 Set 类型的所有元素ZSCAN:遍历 ZSet 类型的所有元素
2.3 基本用法与游标机制
bash
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
游标 cursor
这是 scan 最核心的概念:
- 第一次遍历固定传入游标
0,表示从头开始; - 每次执行后,返回结果分为两部分:第一个值是下次遍历要用的新游标,第二个值是本次遍历到的元素列表;
- 当返回的游标为
0时,表示一轮完整遍历结束。
注意:游标不是连续的下标数字,只是 Redis 内部的位置标识,客户端不需要理解它的含义,每次原样传回即可。
示例:
bash
# 第一轮,游标从0开始,期望每次返回约3个key
127.0.0.1:6379> scan 0 count 3
1) "10"
2) 1) "k7"
2) "k6"
3) "k10"
4) "k1"
# 第二轮,用上一轮返回的10作为游标
127.0.0.1:6379> scan 10 count 3
1) "9"
2) 1) "k9"
2) "k2"
3) "k5"
常用选项
MATCH pattern:按通配符过滤 key,匹配规则和 keys 命令一致。COUNT count:提示 Redis 每次返回多少个元素,默认值 10。注意这只是一个参考值,实际返回数量可能多也可能少,不保证精确。TYPE type:按 value 的数据类型过滤 key。
2.4 重要特性与踩坑点
1. 服务端无状态,可随时终止
scan 遍历的过程中,Redis 服务端不保存任何遍历状态。这意味着:
- 客户端可以随时停止遍历,不会对服务端造成任何残留影响;
- 不需要像数据库游标那样手动关闭,没有资源泄漏风险。
打个比方:有的餐厅菜一旦下单开始做就不能退,因为后厨已经进入流程(有状态遍历);而 scan 就像逛超市,逛到一半不想逛了直接走就行,对超市没有任何副作用。
2. 遍历期间数据变更,可能出现重复或遗漏
如果在遍历过程中,有 key 被新增、删除、修改,那么最终遍历结果可能出现元素重复,也可能出现元素遗漏。这不是 bug,是无状态渐进式遍历的固有特性。
这一点和 C++ STL 的 "迭代器失效" 非常类似:一边遍历容器一边增删元素,会导致迭代器失效、行为未定义。Redis 不会崩溃,但会出现不重不漏的保证失效,业务设计时必须把这个不确定性考虑进去。
3. 能不遍历就不遍历
无论是 keys 还是 scan,全量遍历本质上都是低效操作。 好的业务设计应该通过合理的 key 命名、索引 key 等方式实现精准查询,尽量避免全量扫描。scan 只是兜底的运维工具,不是常规业务查询手段。
三. 数据库管理命令
3.1 Redis 的 database 概念
很多有 MySQL 经验的同学会下意识认为 Redis 的数据库和 MySQL 一样,可以自由创建删除,其实两者完全不同。
- Redis 默认内置了 16 个数据库,编号从 0 到 15;
- 用户不能创建新的数据库,也不能删除已有的数据库;
- 不同数据库之间的数据相互隔离,同一个 key 在不同库里互不影响;
- 客户端默认连接使用 0 号数据库。
切换数据库使用 SELECT 命令:
bash
# 切换到1号数据库
127.0.0.1:6379> SELECT 1
OK
# 提示符会带上当前库编号
127.0.0.1:6379[1]>
实际生产中几乎不会用多数据库,一般都默认使用 0 号库,通过 key 前缀来区分不同业务。一方面 Redis 是单线程,多库共享 CPU,隔离性有限;另一方面 Redis 集群模式下不支持多数据库,只用 0 号库。



3.2 常用管理命令
DBSIZE
查看当前数据库的 key 总数量。
bash
127.0.0.1:6379> DBSIZE
(integer) 10
时间复杂度 O (1),底层有专门的计数器记录,直接读取即可。
FLUSHDB / FLUSHALL
FLUSHDB:清空当前数据库的所有 keyFLUSHALL:清空所有 16 个数据库的全部 key
3.3 生产红线:严禁随意清空
这两个命令属于极高危操作,生产环境严禁随意执行,一旦误操作就是全量数据丢失,极易造成严重线上事故。
- 高版本 Redis 支持
ASYNC参数做异步清空,由后台线程释放内存,不会阻塞主线程,但数据丢失的风险依然存在。 - 标准生产做法是通过配置文件的
rename-command把这些危险命令重命名或者直接禁用,从根源上避免误操作。
源码视角:设计背后的工程考量
scan 的底层实现逻辑
scan 本质上是遍历 Redis 底层的全局哈希表(字典),游标值对应的就是哈希桶的索引。
为什么会出现重复和遗漏?核心原因是哈希表的 rehash。 Redis 的字典在扩容缩容时,会把元素从旧哈希表迁移到新哈希表,元素的桶位置会发生变化。如果遍历中途发生了 rehash,有些元素可能被重复访问到,也有些元素可能被跳过。 为了尽量降低这个问题的影响,scan 使用了特殊的反向二进制位迭代算法,在 rehash 场景下尽可能覆盖所有元素,但做不到 100% 不重不漏。这是 "无状态遍历" 必然要做的取舍。
位操作类的复用哲学
Bitmaps、Bitfields 都没有引入新的底层数据结构,全部基于 String 类型做位级扩展。 这也是 Redis 一贯的设计思路:能复用现有结构就不造新轮子,在已有编码的基础上扩展能力。既减少了实现复杂度,也能复用已有的内存优化、持久化等能力。 这和 C 语言里用一个整型的不同位段存储多个状态的思路完全一致 ------ 对于内存数据库来说,每一寸空间都值得精打细算。
核心考点总结
- 特殊数据类型
- Stream:高级事件流与消息队列,支持消费组、ACK、范围读取
- Geospatial:地理空间查询,底层基于 ZSet + geohash 编码
- HyperLogLog:基数估算,12KB 支撑亿级计数,标准误差 0.81%
- Bitmaps:位图存储布尔状态,位运算高效,极致节省空间
- Bitfields:位域打包多段整数,适配多计数器场景
- 渐进式遍历 scan
- 问题背景:keys 全量遍历阻塞主线程,生产环境禁用
- 核心思想:化整为零,单次 O (1),打散阻塞时间
- 游标机制:首次传 0,返回 0 表示遍历结束,游标为内部标识
- 重要特性:服务端无状态可随时终止;遍历中修改数据会出现重复与遗漏
- 家族命令:SCAN / HSCAN / SSCAN / ZSCAN 分别对应不同层级
- 数据库管理
- 默认 16 个数据库,编号 0~15,不可创建删除,使用 SELECT 切换
- DBSIZE 查看当前库 key 总数,时间复杂度 O (1)
- FLUSHDB / FLUSHALL 为高危操作,生产环境应重命名或禁用
- 生产惯例:默认使用 0 号库,通过 key 前缀区分业务
- 设计思想
- 时空权衡:特殊类型均为场景化取舍,用精度或功能换空间效率
- 化整为零:将大耗时操作拆分,规避单线程阻塞风险
- 结构复用:优先在现有数据结构上扩展能力,控制实现复杂度
结尾:
html
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!
结语:到这里,Redis 的核心数据类型与基础操作就全部梳理完成了。五大基础类型支撑通用业务场景,几类特殊类型解决特定场景的极致需求;而 scan、数据库命令这类运维操作,看似简单,却直接关系到生产环境的稳定性。学习 Redis 从来不是死记命令,更重要的是理解每个设计背后的取舍、适用边界和潜在风险,才能在实际工作中用对、用好、不出错。 后续我们会继续深入 Redis 的过期策略、持久化、事务、集群等核心原理,从使用层逐步走向底层。
✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど
