ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【Redis 初阶】特殊数据类型、渐进式遍历与数据库操作生产指南

【Redis 初阶】特殊数据类型、渐进式遍历与数据库操作生产指南 草莓熊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 基本用法与游标机制SCAN cursor[MATCH pattern][COUNT count][TYPE type]游标 cursor这是 scan 最核心的概念第一次遍历固定传入游标0表示从头开始每次执行后返回结果分为两部分第一个值是下次遍历要用的新游标第二个值是本次遍历到的元素列表当返回的游标为0时表示一轮完整遍历结束。注意游标不是连续的下标数字只是 Redis 内部的位置标识客户端不需要理解它的含义每次原样传回即可。示例# 第一轮游标从0开始期望每次返回约3个key127.0.0.1:6379scan0count31)102)1)k72)k63)k104)k1# 第二轮用上一轮返回的10作为游标127.0.0.1:6379scan10count31)92)1)k92)k23)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命令# 切换到1号数据库127.0.0.1:6379SELECT1OK# 提示符会带上当前库编号127.0.0.1:6379[1]实际生产中几乎不会用多数据库一般都默认使用 0 号库通过 key 前缀来区分不同业务。一方面 Redis 是单线程多库共享 CPU隔离性有限另一方面 Redis 集群模式下不支持多数据库只用 0 号库。3.2 常用管理命令DBSIZE查看当前数据库的 key 总数量。127.0.0.1:6379DBSIZE(integer)10时间复杂度 O (1)底层有专门的计数器记录直接读取即可。FLUSHDB / FLUSHALLFLUSHDB清空当前数据库的所有 keyFLUSHALL清空所有 16 个数据库的全部 key3.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 前缀区分业务设计思想时空权衡特殊类型均为场景化取舍用精度或功能换空间效率化整为零将大耗时操作拆分规避单线程阻塞风险结构复用优先在现有数据结构上扩展能力控制实现复杂度结尾 我是草莓熊 Lotso若这篇技术干货帮你打通了学习中的卡点 【关注】跟我一起深耕技术领域从基础到进阶见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好需要时直接查、随时用 【评论】分享你的经验或疑问比如曾踩过的技术坑一起交流避坑 ️ 【投票】用你的选择助力社区内容方向告诉大家哪个技术点最该重点拆解 技术之路难免有困惑但同行的人会让前进更有方向愿我们都能在自己专注的领域里一步步靠近心中的技术目标结语到这里Redis 的核心数据类型与基础操作就全部梳理完成了。五大基础类型支撑通用业务场景几类特殊类型解决特定场景的极致需求而 scan、数据库命令这类运维操作看似简单却直接关系到生产环境的稳定性。学习 Redis 从来不是死记命令更重要的是理解每个设计背后的取舍、适用边界和潜在风险才能在实际工作中用对、用好、不出错。 后续我们会继续深入 Redis 的过期策略、持久化、事务、集群等核心原理从使用层逐步走向底层。✨把这些内容吃透超牛的放松下吧✨ʕ˘ᴥ˘ʔづきらど
返回列表