当前位置: 首页 > news >正文

【Redis#10】渐进式遍历 | 数据库管理 | redis_cli | RES

一、渐进式遍历 – scan

📕 在 Redis 中,渐进式遍历 是一种 “分批次”地遍历大型数据集的机制 ,主要用于处理 KEYSSCANSSCANHSCANZSCAN 等命令。

它解决了传统一次性遍历带来的性能问题:一次性遍历大量数据会导致 Redis 阻塞,影响其他客户端请求响应时间。❗

  • 比如:keys * 指令用于获取 Redis 中所有的 key,采用 遍历 的方式。但当 Redis 存储的 key 较多时,该操作可能会 阻塞 Redis,影响其他指令执行。【不推荐使用 KEYS 的原因🚫 】

为避免上述问题,可以使用 scan 指令进行渐进式遍历。这种方式可以在 不卡死服务器(不占用主线程过久) 的情况下获取所有 key,并且 每次执行命令只获取其中的一小部分数据,如果 key 较多,则需要多次执行 scan 命令,这样 保证 Redis 的高可用性和低延迟

1. 渐进式遍历的核心思想

Redis 使用 哈希表 + rehashing + 游标(cursor)机制 实现了非阻塞式的遍历方式。

你可以把它理解为:“一边走,一边看”,而不是一口气把所有东西都翻一遍。

底部实现原理如下:

Redis 内部使用 两个哈希表(ht[0] 和 ht[1]) 来实现动态扩容和 rehash渐进式遍历 就是在这个基础上实现的:

  1. 初始游标 0 表示从 ht[0] 的第一个桶开始。
  2. 每次遍历一部分 bucket,并返回下一个游标。
  3. 如果正在进行 rehash,则同时遍历 ht[0]ht[1]
  4. 当游标再次变为 0 时,表示遍历结束。

2. Redis 支持渐进式遍历的命令

命令描述
SCAN cursor [MATCH pattern] [COUNT count]遍历数据库中的 key
SSCAN key cursor [MATCH pattern] [COUNT count]遍历 Set 中的元素
HSCAN key cursor [MATCH pattern] [COUNT count]遍历 Hash 中的字段
ZSCAN key cursor [MATCH pattern] [COUNT count]遍历 Sorted Set 中的元素

这些命令都会返回一个 新的游标值 ,用于下一次调用。

常见指令 - scan

语法:scan cursor [MATCH pattern] [COUNT count] [TYPE type]

功能:以渐进式方式对 Redis 中的所有键进行遍历。

  • cursor:表示从哪个 光标 开始遍历。光标为 0 时,表示从头开始遍历。光标的概念不可以理解下标 ,它不是一个连续递增的整数,他只是一个普通的字符串在每次遍历后返回下次遍历开始的光标(若 返回的字符串是 0,说明遍历已经完成)。
    • 可能程序员不理解,但是 Redis服务器是可以知道这个光标对应的元素的位置的.
  • pattern:匹配指定模式的 key。
  • count:限制每次返回的元素个数,默认为 10。需要注意的是,这里的 count 只是客户端给 Redis 服务器的一个 建议,具体返回多少取决于实际情况。
  • type:指定 key 对应 value 的类型,包含 5 个通用类型和 5 个特殊场景使用的类型。

示例

# 第一次调用,从游标 0 开始
127.0.0.1:6379> SCAN 0 MATCH user:* COUNT 10
1) "15"          # 新的游标值
2) 1) "user:1001"2) "user:1002"...10) "user:1010"# 第二次调用,使用上一次返回的游标值 15
127.0.0.1:6379> SCAN 15 MATCH user:* COUNT 10
1) "0"           # 游标为 0 表示遍历完成
2) 1) "user:1011"2) "user:1012"...10) "user:1020"
  • 注意:尽管 scan 解决了阻塞问题,但在 遍历期间如果键有所变化(增加、修改或删除),可能会导致重复遍历或遗漏。

不仅仅是 Redis,遍历其他内容的时候,也是比较忌讳一边遍历一边修改的,比如:C++ STL 的遍历 + 修改/新增/删除 => 迭代器失效,如下:

std::vector<int> v = {1, 2, 3, 4};
for (auto it = v.begin(); it != v.end(); ++it) {v.erase(it);
}
  • 注意!!这样的代码,就会导致“迭代器失效”。当删除完成之后,it 这个迭代器指向哪里,已经不知道了!

此时循环体结束之后再次 ++it,不一定就能指向下一个元素了,因此 在遍历过程中,不要一边遍历一边修改。

【C++ 中的迭代器在删除元素后可能会失效】

大部分编程语言中,++ 都是后置写的,C++ 更偏好前置 ++,原因如下:

  • 在 C++ 里 前置 ++ 比后置 ++ 性能更高(少了一次临时对象的构造)
  • Java 中 ++ 只是针对数字,数字本身无论是前置后置都是足够快。而 C++ 中 ++ 可能是针对一个对象(C++ 有运算符重载),不像 C++ 里可能就崩溃了

Redis 虽然不会给你崩溃,但是可能会出现 遗漏重复。

3. 渐进式遍历的特点

特性描述
⏱️ 非阻塞每次只返回一部分数据,避免长时间阻塞 Redis,适合 大数据集
📦 分批获取可控制每次获取的数据量(通过COUNT参数),相当于分批次返回结果
🔁 可中断可随时暂停或停止遍历,下次继续
📁 元素可能重复或遗漏因为 Redis 是边遍历边修改的结构,所以可能会有重复或遗漏(不保证唯一性)
🧩 适用于大数据尤其适合键空间巨大、不能容忍阻塞的场景

4. 为什么 SCAN 不保证绝对一致性?

理解:在遍历过程中,不会在服务器中保存任何状态信息,如光标位置等。遍历可以随时终止,不会对服务器产生副作用。

  • 就像我们去吃烧烤,如果有一部分烧烤还没有上来,我们就需要退款,这时候老板就到后厨看了一眼,说已经烤上了,退不了了。
  • 再比如我们在遍历 Redis 服务器中的 key 的时候,遍历了一半,不想遍历了,但是这时候又取消不了,如果强行退出,这时候 服务器就会保存状态,这时候就会对服务造成一定的影响,这时候就相当于服务器保存了客户端的状态。
  • 再比如我们去超市买东西,如果我们在结账的时候,发现钱没有带够,后面的东西不想要了,这时候 可以不扫后面的商品。
  • 这时就像 Redis 中的渐进式遍历,没有保存之前遍历的状态,可以随时停止。

原因:因为 Redis 的 SCAN 是“基于快照的近似遍历 ”,它并不冻结数据。因此可能出现以下情况:

情况是否可能
同一个 key 被返回多次✅ 是
某些新增/删除的 key 被遗漏✅ 是
所有 key 都被准确返回一次❌ 否

这与你是否正在做 rehash、是否有写入操作有关。

5. 适用场景

场景推荐使用 SCAN 系列指令
查看数据库中有哪些 keySCAN
统计某个 Set 或 Hash 的全部内容SSCAN/HSCAN
定期清理或迁移数据SCAN+ 匹配规则
大数据量下避免阻塞✅ 必须使用 SCAN 替代 KEYS

🔥 小结:Redis 的渐进式遍历是一种非阻塞、分批次、支持匹配模式的遍历方式,适用于大数据量下的安全访问,特别适合生产环境替代 KEYS、SMEMBERS 等一次性全量命令。

二、数据库管理

1. 多数据库支持(Multi-database)

🔥类似于 MySQL 中的 database 概念,Redis 中也有 database 的概念,但用户不能随意创建或删除数据库。Redis提供了几个面向Redis数据库的操作,分别是 dbsizeselectflushdbflushall

说明

  • 许多关系型数据库,例如MySQL支持在⼀个实例下有多个数据库存在的,但是 与关系型数据库用字符来区分不同数据库名不同,Redis只是用数字作为多个数据库的实现
  • Redis默认配置中是有 16 个数据库,可以以通过 SELECT <index> 命令切换。
    • 比如:select 0 操作会切换到第一个数据库,select 15 会切换到最后一个数据库
  • 0号数据库和15号数据库 保存的数据是完全不冲突的,即各种有各自的键值对。默认情况下,我们处于数据库0

Redis 管理的数据库如下图:

image-20250703161411794

  • 选择数据库:可以通过 select dbIndex (例如 select 1) 来切换数据库。
  • 清除数据库
    • flushdb:清除当前数据库中的所有数据。
    • flushall:清除 Redis 服务器中的所有数据

示例如下:

127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> SELECT 0
OK
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> keys *
(empty array)

🚫 警告:永远不要在线上环境 执行清除数据的操作,除非你想要体验“从删库到跑路”的操作。

💡 注意:Redis中虽然支持多数据库,但随着版本的升级,其实不是特别建议使用多数据库特性

  • 如果真的需要完全隔离的两套键值对,更好的做法是维护多个Redis实例,而不是在⼀个 Redis实例中维护多数据库

  • 这是因为本⾝Redis并没有为多数据库提供太多的特性,其次无论是否有多个数据库,Redis都是使用 单线程模型,所以彼此之间还是需要 排队等待命令的执行,同时多数据库还会让开发、调试和运维工作变得 复杂

实践中,始终使用 数据库0 其实是⼀个很好的选择

⚠️ 注意事项
  • Redis 的“数据库”是命名空间级别的隔离,并不是真正的物理隔离。
  • 所有数据库共享同一个内存池。
  • 不适合用作生产环境的多租户隔离,建议通过部署多个 Redis 实例实现。

2. 数据持久化机制(Persistence)

Redis 是内存数据库,为了防止数据丢失,它提供了两种持久化方式:

  1. RDB(Redis Database Backup):【快照持久化:将某一时刻的数据保存为 .rdb 文件】
  2. AOF(Append Only File):【日志持久化:记录所有写操作命令,重启时重放日志恢复数据】

🔁 混合模式(推荐):Redis 4.0+ 支持 AOF + RDB 混合持久化模式,兼顾性能与可靠性。

示例配置

# 启用 RDB 快照
save 900 1
save 300 10
save 60 10000# 启用 AOF
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec

3. 安全认证(Authentication)

Redis 提供了简单的密码认证机制,用于限制未授权访问。

  • 为什么需要设置密码:避免 redis 的 key 变成 backup([解决redis的key都变成了backup,redis所有缓存被清空_)

注意事项

  1. 开启Redis的远程访问前,请确保已经配置了合适的防火墙规则,只允许受信任的IP地址访问Redis端口
  2. 考虑设置一个密码来增强安全性。可以在 /etc/redis/redis.conf 中找到 requirepass 指令并设置一个强密码(需要重启 Redis)
  3. 如果你的Redis服务器暴露在互联网上,建议使用SSL/TLS加密通信,以防止数据泄露
密码设置两种方式

① 在 redis.conf 中添加:

requirepass yourpassword# 连接后进行认证
AUTH yourpassword # 使用方式

② 不需要重启redis服务的密码设置方式

127.0.0.1:6379> config set requirepass 123456 
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"# 退出重进
lighthouse@VM-8-10-ubuntu:~$ redis-cli
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> ping
PONG

注意:通过命令行修改了密码之后,配置文件的 requirepass 字段后面的密码是不会随之修改的。

三、redis-cli

前面学习的主要是各种 redis 的基本操作/命令,都是在 redis 命令行客户端 手动执行的。

  • 但是这种操作方式不是我们日常开发中主要的形式更多的时候,是使用 redis 的 api,来实现定制化的 redis 客户端程序,进一步操作 redis 服务器。
  • 相当于用程序来操作 redis

以前学习 MySQL 的时候~也会涉及到,关于使用程序来操作 MySQL 服务器

  1. C++:MySQL 原生 API
  2. Java:JDBC & MyBatis

redis 提供的命令行客户端 / 第三方的图形化客户端 …他们本质上都属于是“通用的客户端程序”

相比之下,在工作中更希望使用到的是 “专用的”“定制化”的客户端程序~

那么是不是表示前面学习的这些 redis 命令没有价值了呢? ==> 当然不是的!!【redis 命令相当于使用代码来执行】

使用:无论 C++还是 Java各自如何编程实现对应的 redis 客户端,其实要做的事情本质上是一样的~

  • 注意:网上关于 redis 这块的编程,基本都是 Java 为主,难道是,其他语言不配操作 redis 嘛? ==> 当然不是!!!redis 能支持很多很多种编程语言

img

博主这里主要是基于 C++ 来讲解

四、RESP 自定义协议

1. 前言

网络通信中的协议层次:应用层、传输层(TCP / UDP 协议)、网络层(IP 协议)、数据链路层(以太网)、物理层【五层协议】

虽然业界有很多 成熟的应用层协议,如 HTTP,但在某些情况下,会“自定义”应用层协议

Redis 使用的就是一种自定义的应用层协议。客户端和服务器之间的交互:

  1. 客户端按照应用层协议发送请求;
  2. 服务器按照该协议解析请求,并根据请求构造响应;
  3. 客户端再解析从服务器返回的响应。

关于我们能否 自己 编写自定义 Redis 客户端:

直接基于未公开的协议去编写是不可行的。然而,对于 Redis 来说,其使用的自定义协议(即 RESP)是公开的。

就像一些开源项目通过逆向工程实现了 QQ 客户端一样,实现过程依赖于 开发者对协议的理解和技术水平。

自主 开发 Redis 客户端的前提

  1. 需要 了解 Redis 的应用层协议(暗号),即 RESP 协议
  2. 由于 Redis 官方已经 公开 了这一协议规范,因此可以依据这些信息来 编写客户端

2. 基本概念

RESP(Redis Serialization Protocol) 是 Redis 客户端与服务端之间通信所使用的 应用层协议 ,它是一种 高效、简单、可读性强的二进制安全文本协议

Redis 使用 RESP 来封装客户端发送的命令和服务器返回的结果。

RESP 的设计目标🔍

  1. 简单易实现
  2. 高效性:支持二进制安全传输
  3. 可读性强:使用 ASCII 编码,方便调试
  4. 易解析:结构清晰,易于编写客户端/服务端解析器
  5. 多种数据类型支持:支持字符串、整数、数组、错误、批量字符串等

与 TCP 的关系

  • 基于 TCP 传输,但不强耦合于 TCP。

  • 请求-响应模型是 一问一答 的形式。

Redis 中的使用方式

  • 客户端发送命令到 Redis 服务器,服务器以 RESP 数组形式接收并处理。
  • 根据命令的不同,服务器将以不同的 RESP 类型进行响应,例如:直接返回 ok、返回整数、返回数组…

3. RESP 支持的数据类型(前缀标识)

每种数据类型都用一个特定字符开头表示:

类型前缀字符示例含义
简单字符串++OK\r\n不带空格的字符串(如状态响应)
错误信息--ERR invalid password\r\n错误信息,客户端应抛出异常
整数::1000\r\n返回一个整数值(如 INCR 操作结果)
批量字符串(Bulk String)$$6\r\nfoobar\r\n表示长度为 6 的字符串 “foobar”
数组**2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n表示包含两个元素的数组:[“foo”, “bar”]
  • Simple Strings:用于传输非二进制安全的文本字符串,开销最小。
  • Bulk Strings:用于传输二进制安全的数据。
  • 可以发现 resp 比 htttps 的设计简单不少~

开发提示:不需要手动解析或构造符合 RESP 规范的字符串,因为已有许多成熟的库支持这种操作。我们只需要 使用大佬们 提供的库,就可以比较简单方便的来完成和 redis 服务器通信的操作了

在这里插入图片描述


文章转载自:

http://BQdlEzTf.yrmpz.cn
http://yz5O2W40.yrmpz.cn
http://2zuqknFL.yrmpz.cn
http://E4NF3IHo.yrmpz.cn
http://eI8ERerL.yrmpz.cn
http://3E2HD7Yt.yrmpz.cn
http://hRaeOWcj.yrmpz.cn
http://a717dVCh.yrmpz.cn
http://k5Urjsdg.yrmpz.cn
http://a8gBdMeM.yrmpz.cn
http://z7iS58xs.yrmpz.cn
http://t2uKOL5X.yrmpz.cn
http://2dLnrMhb.yrmpz.cn
http://7RQJgONo.yrmpz.cn
http://09S60Twh.yrmpz.cn
http://WMsqOVyn.yrmpz.cn
http://CiS2AyYo.yrmpz.cn
http://ptbUZEue.yrmpz.cn
http://Y4XFfnHh.yrmpz.cn
http://gU0kTjp8.yrmpz.cn
http://gPzUY5uC.yrmpz.cn
http://QMwXZlQy.yrmpz.cn
http://I78RvDFl.yrmpz.cn
http://zHG5Shy5.yrmpz.cn
http://ezL5QqyN.yrmpz.cn
http://Ss1yodZl.yrmpz.cn
http://qG5NILRI.yrmpz.cn
http://PJr5LTNB.yrmpz.cn
http://OP6h5ixG.yrmpz.cn
http://vKfyZBWX.yrmpz.cn
http://www.dtcms.com/a/382298.html

相关文章:

  • Java面试问题记录(三)
  • 在Excel和WPS表格中批量删除数据区域的批注
  • 商品库存扣减方案
  • smartctl Current_Pending_Sector 硬盘待处理扇区
  • 并发和高并发
  • 科技信息差(9.13)
  • 文档长期不更新导致知识过时如何解决
  • Python学习-day9 字典Dictionary
  • Ubuntu22.04更换阿里镜像源,ubuntu更换源
  • 仓颉编程语言青少年基础教程:Struct(结构)类型
  • C语言数据结构实战:从零构建一个高性能的顺序栈
  • 数据链路层总结
  • Linux线程:基于环形队列的生产消费模型
  • 【Ambari监控】高版本 DataGrip 无法使用 Phoenix 驱动
  • 1.架构师——大纲
  • 粒子群算法模型深度解析与实战应用
  • JDK 新特性
  • 数据库可视化面板下载
  • 深入解析:preload与prefetch的区别及最佳实践
  • 【层面一】C#语言基础和核心语法-01(类型系统/面向对象/异常处理)
  • Python核心技术开发指南(061)——初始化方法__init__
  • 用 Go 采集服务器资源指标:从原理到实践
  • MySQL-day2_02
  • 基于springboot+vue开发的会议预约管理系统【50906】
  • 【Ubuntu】sudo apt update出现E :仓库***没有Release文件
  • JavaWeb--day3--AjaxElement路由打包部署
  • 阿里云国际代理:怎么保障数据库在凭据变更过程中的安全与稳定?
  • 关于子空间流形的认识
  • SQL注入漏洞手动测试详细过程
  • 【Linux】gcc/g++工具篇