[Redis#10] scan | db_0 | redis_cli | RESP | C++-redis启动教程
目录
1. 渐进式遍历
1.2 常见指令 - scan
2. 数据库管理
3.redis 客户端
是否前面学习的这些 redis 命令,没有价值了呢?
4.RESP 自定义协议
为什么能编写出一个自定义的 Redis 客户端?
RESP 协议
5.在 Ubuntu 下启用 C++ 操作 Redis
1. 前置依赖 - hiredis
2. 安装 redis-plus-plus
3. 示例:通用命令
1. 渐进式遍历
keys *
指令用于获取 Redis 中所有的 key,采用遍历的方式。但当 Redis 存储的 key 较多时,该操作可能会阻塞 Redis,影响其他指令执行。- 为避免上述问题,可以使用
scan
指令进行渐进式遍历。这种方式可以在不卡死服务器的情况下获取所有 key,并且 每次执行命令只获取其中的一小部分数据,如果 key 较多,则需要多次执行scan
命令。
1.2 常见指令 - 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 个特殊场景使用的类型。
在遍历过程中,不会在服务器中保存任何状态信息,如光标位置等。遍历可以随时终止,不会对服务器产生副作用。
- 就像我们去吃烧烤,如果有一部分烧烤还没有上来,我们就需要退款,这时候老板就到后厨看了一眼,说已经烤上了,退不了了。
- 再比如我们在遍历 Redis 服务器中的 key 的时候,遍历了一半,不想遍历了,但是这时候又取消不了,如果强行退出,这时候 服务器就会保存状态,这时候就会对服务造成一定的影响,这时候就相当于服务器保存了客户端的状态。
- 再比如我们去超市买东西,如果我们在结账的时候,发现钱没有带够,后面的东西不想要了,这时候 可以不扫后面的商品。
- 这时就像 Redis 中的渐进式遍历,没有保存之前遍历的状态,可以随时停止。
示例:
127.0.0.1:6379> mset k1 val1 k2 val2 k3 val3 k4 val4 k5 val5
OK
127.0.0.1:6379> scan 0 count 3
1) "1" // 返回下次开始遍历的光标
2) 1) "k3"
2) "k4"
3) "k5"
127.0.0.1:6379> scan 1 count 3
1) "0"
2) 1) "k1"
2) "k2" // 虽然输入的 count 是 3, 但是只遍历了 2 个
- 注意:尽管
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++ 里 前置
++
比后置++
性能更高(少了一次临时对象的构造)
- 在 C++ 里 前置
- Java 中
++
只是针对数字,数字本身无论是前置后置都是足够快
-
- C++ 中
++
可能是针对一个对象(C++ 有运算符重载)
- C++ 中
不像 C++ 里可能就崩溃了
- Redis 虽然不会给你崩溃,但是可能会出现 遗漏重复。
2. 数据库管理
- 类似于 MySQL 中的 database 概念,Redis 中也有 database 的概念,但用户不能随意创建或删除数据库。
- Redis提供了⼏个⾯向Redis数据库的操作,分别是
dbsize
、select
、flushdb
、flushall
说明:
- 许多关系型数据库,例如MySQL⽀持在⼀个实例下有多个数据库存在的,但是 与关系型数据库⽤字符来区分不同数据库名不同,Redis只是⽤数字作为多个数据库的实现
- Redis默认配置中是有16个数据库。
select 0
操作会切换到第⼀个数据库,select 15
会切换到最后⼀个数据库 - 0号数据库和15号数据库保存的数据是完全不冲突的,即各种有各⾃的键值对。默认情况下,我们处于数据库0
- 选择数据库:可以通过
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其实是⼀个很好的选择
3.redis 客户端
- 前面学习的主要是各种 redis 的基本操作/命令,都是在 redis 命令行客户端,手动执行的。
- 这种操作方式不是我们日常开发中主要的形式
- 更多的时候,是使用 redis 的 api,来实现定制化的 redis 客户端程序,进一步操作 redis 服务器。
- 用程序来操作 redis~
以前学习 MySQL 的时候~也会涉及到,关于使用程序来操作 MySQL 服务器
- C++: MySQL 原生 API
- Java: JDBC & MyBatis
redis 提供的命令行客户端 / 第三方的图形化客户端 ...
他们本质上都属于是“通用的客户端程序”
相比之下,在工作中更希望使用到的是 “专用的”“定制化”的客户端程序~
是否前面学习的这些 redis 命令,没有价值了呢?
- 当然不是的!!
- redis 命令相当于使用代码来执行
使用:
- 无论 C++还是 Java各自如何编程实现对应的 redis 客户端,其实要做的事情本质上是一样的~
- 网上关于 redis 这块的编程,基本都是 Java 为主,难道是,其他语言不配操作 redis 嘛?
- 当然不是!!!redis 能支持很多很多种编程语言
博主后面选择以 C++来进行讲解~
4.RESP 自定义协议
为什么能编写出一个自定义的 Redis 客户端?
- 网络通信中的协议层次:
-
- 应用层
- 传输层:TCP / UDP 协议
- 网络层:IP 协议
- 数据链路层:以太网
- 物理层
- 虽然业界有很多 成熟的应用层协议,如 HTTP,但在某些情况下,会“自定义”应用层协议。
-
- Redis 使用的就是一种自定义的应用层协议。
- 客户端和服务器之间的交互:
-
- 客户端按照应用层协议发送请求;
- 服务器按照该协议解析请求,并根据请求构造响应;
- 客户端再解析从服务器返回的响应。
- 关于我们能否 自己 编写自定义 Redis 客户端:
-
- 直接基于未公开的协议去编写是不可行的。然而,对于 Redis 来说,其使用的自定义协议(即 RESP)是公开的。
- 就像一些开源项目通过逆向工程实现了 QQ 客户端一样,实现过程依赖于 开发者对协议的理解和技术水平。
- 自主 开发 Redis 客户端的前提:
-
- 需要 了解 Redis 的应用层协议(暗号),即 RESP 协议。
- 由于 Redis 官方已经公开了这一协议规范,因此可以依据这些信息来编写客户端。
RESP 协议
- 名称:Redis Serialization Protocol (RESP)
- 特点:
-
- 简单易实现
- 快速解析
- 内容可读性强
- 与 TCP 的关系:
-
- 基于 TCP 传输,但不强耦合于 TCP。
- 请求-响应模型是 一问一答 的形式。
Redis 中的使用方式:
- 客户端发送命令到 Redis 服务器,服务器以 RESP 数组形式接收并处理。
- 根据命令的不同,服务器将以不同的 RESP 类型进行响应,例如:
-
- 直接返回
ok
- 返回整数
- 返回数组......
- 直接返回
常见 数据类型标识:
- Simple Strings: 开头为
+
- Errors: 开头为
-
- Integers: 开头为
:
- Bulk Strings: 开头为
$
- Arrays: 开头为
*
示例:
-
+OK\r\n
-Error message\r\n
:1000\r\n
$5\r\nhello\r\n
*2\r\n$5\r\nhello\r\n$6\r\nworld\r\n
- Simple Strings:用于传输非二进制安全的文本字符串,开销最小。
- Bulk Strings:用于传输二进制安全的数据。
- 可以发现 resp 比 htttps 的设计简单不少~
开发提示:
- 不需要手动解析或构造符合 RESP 规范的字符串,因为已有许多成熟的库支持这种操作。
- 我们只需要 使用大佬们 提供的库,就可以比较简单方便的来完成和 redis 服务器通信的操作了
5.在 Ubuntu 下启用 C++ 操作 Redis
1. 前置依赖 - hiredis
- hiredis 是一个用 C 语言实现的 Redis 客户端库,
redis-plus-plus
库基于hiredis
实现。 - 在开始之前,请确保已安装
libhiredis-dev
,可以通过以下命令安装:
sudo apt install libhiredis-dev
2. 安装 redis-plus-plus
redis-plus-plus
是一个功能强大且易于使用的 C++ Redis 客户端库。它具有统一的接口风格,使得使用起来非常方便。- 以下是安装步骤:
- 克隆
redis-plus-plus
的 GitHub 仓库到本地:
git clone https://github.com/sewenew/redis-plus-plus.git
进入克隆下来的项目目录,并创建构建目录:
cd redis-plus-plus
mkdir build && cd build
使用 CMake 配置并编译安装:
cmake .. -DCMAKE_INSTALL_PREFIX=/usr
make
sudo make install
- 说明:
-
redis-plus-plus
库支持多种方式传递参数,如 初始化列表或迭代器对。- 当函数需要返回多个数据时,通常会使用插入迭代器将结果添加到容器中。
- 对于可能返回无效值的情况,
redis-plus-plus
通常会使用std::optional
来表示。
3. 示例:通用命令
Quick Start创建一个简单的 C++ 程序来连接 Redis 并发送一个 ping
命令,以检查连接是否成功。
- main.cc:
#include <iostream>
#include <string>
#include <sw/redis++/redis++.h>
using namespace std;
int main()
{
// 构建Redis对象的时候,在构造函数中,指定Redis服务器的地址和端口
sw::redis::Redis redis("tcp://127.0.0.1:6379");
string ret = redis.ping();
cout << ret << endl;
return 0;
}
- Makefile:
main: main.cc
g++ -std=c++17 -o $@ $^ -l redis++ -l hiredis -l pthread
.PHONY: clean
clean:
rm main
- 编译和运行:
-
- 在终端中执行以下命令来编译程序:
make
-
- 运行生成的可执行文件:
./main
-
- 如果一切正常,可以 看到输出
PONG
,这表明已经成功连接到了 Redis 服务器啦
- 如果一切正常,可以 看到输出