目录
[二、value 常见的数据类型](#二、value 常见的数据类型)
[三、渐进式遍历 scan](#三、渐进式遍历 scan)
[五、通过 c++ 构造客户端使用 redis](#五、通过 c++ 构造客户端使用 redis)

前言
本文章使用的是 Redis5 版本且没设置密码,环境为Ubuntu20.4;
官方指令文档参考:Commands | Docs
一、常用命令
注意:redis 命令不区分大小写,大写小写都行
-- linux相关指令 --
0. 安装 redis(ubuntu)
- apt install redis -y
- 安装前使用 root 账号或者加上 sudo
1. 启动 / 重启 / 停止 redis服务
- 启动命令:service redis-server start
- 重启命令:service redis-server restart(ubuntu)、resid-server + 配置文件路径(centos),重启可以重新加载配置文件
- 停止命令:service redis-server stop
- 注意:使用 root 账户或者加上 sudo
2. 查看redis服务运行状态
- 命令:service redis-server status 或者 sudo netstat -anp | grep redis
3. redis-cli -h ++IP地址++ -p ++端口号++
- 功能:连接指定 redis 服务,如果 redis 服务跑在本地机器上,可以直接使用 redis-cli 连接本地 redis 服务;
- 补充:退出 redis-cli 可使用快捷键 ctrl+d
- 补充:redis-cli 后面带上 --raw 选项,可以使 redis 客户端自动把二进制数据进行翻译,比如 set 设置了一个中文 value,原本不带上 -- raw 进行 get 获取时得到的是一串十六进制数,带上 --raw 可以使 get 直接返回原本的中文汉字
-- 基础指令 --
4. ping ++"message"++
- 功能:ping 用于测试redis客户端与服务端的连通性,默认返回 PONG 表示连通;message 为可选发送消息,连通则会回显
5. set / get
- redis 是按照键值对的方式存储数据的,也就是 key : value
- set:存储数据,后跟 key value ,key 和 value 默认会当做字符串,可以加上单引号或者双引号;
- get:获取数据,后跟 key,数据存在返回 value,不存在返回 (nil)
- 补充:关于 key 与 value 的数据类型,其中 key 固定为字符串,而 value 则有 多种数据类型;value 常见5种类型包括:字符串、哈希表、列表、集合、有序集合;有关 value 详细的数据类型介绍参考下文。
- 另外还有补充说明参考下文 string
-- 基本全局命令 --
对于键 key 来说有⼀些通用的命令
6. keys pattern
- 作用:用来查询当前服务器上匹配的 key,返回所有满足样式(pattern)的key。
- 时间复杂度:O(n)
- 支持持如下示例样式:
- h?llo:匹配 hello,hallo,hxllo;也就是匹配 ? 号处可以是任意一个字符的key
- h*llo:匹配 hllo 和 heeeello;和 ? 号不同的是,* 号处可以是0个或多个同种字符
- haello:匹配 hello 和 hallo 但不匹配hillo;就是说只能匹配 内的一个字符进行组合的结果
- h \^ello:匹配 hallo,hbllo,...,但不匹配 hello;\^e 意思是匹配除了e以外的任意一个字符,如果^号后面有多个字符,则只要含有这些字符中的任意一个则不匹配;
- ha-bllo:匹配 hallo 和 hbllo;a-b 意思是这个闭区间内的字符都可匹配;
- 示例:
- 设置一些键值对:
- keys h?llo:
- keys h*llo:
- keys habello:
- keys h\^abllo:
- keys ha-ello:
- 注意事项:keys 命令的时间复杂度是 O(n);所以一般生产环境中,会禁止使用 keys 命令,特别是 keys *;因为对于单线程的 redis 服务,可能造成阻塞,导致无法给其他客户端提供服务。
- keys *:列出 redis 中所有 key;
7. exists key key ...
- 作用:判断某个 key 是否存在;
- 时间复杂度:O(1)
- 返回值:exists 后面跟上多个具体的 key 值,就会返回 key 存在的个数;exists 后面如果只有一个 key,那么返回值就是 1 或者 0;
8. del key key ...
- 作用:删除指定的一个或者多个 key
- 时间复杂度:O(1)
- 返回值:成功删除的 key 个数
9. expire key seconds / pexpire key milliseconds
- 作用:为指定的 key 添加过期时间,时间一到自动删除 key;
- expire key seconds:设置秒级过期时间
- pexpire key milliseconds:设置毫秒级过期时间
- 时间复杂度:O(1)
- 返回值:1 表示设置成功,0 表示设置失败
- redis 的过期策略:定期删除和惰性删除的结合;定期删除:每隔一段时间抽取一些设置过期的key进行检查是否过期;惰性删除:等用到的时候检查是否过期。
10. ttl key / pttl key
- 作用:获取 key 的过期时间,ttl 秒级,pttl 毫秒级;
- 时间复杂度:O(1)
- 返回值:剩余过期时间;如果是 -1 则表示没有关联过期时间;如果是 -2 则表示 key 不存在;
11. type key
- 作用:返回 key 对应的 value 的数据类型
- 时间复杂度:O(1)
- 返回值:none、string、list、set、zset、hash 和 stream
12. object encoding key
- 作用:查看 key 对应 value 的内部编码
- redis 为了优化内存空间占用同时保证效率,在不同场景下对每种数据结构 value 使用合适的编码;每个数据类型的编码如下:
|--------|------------|
| 数据类型 | 内部编码 |
| string | raw |
| string | int |
| string | embstr |
| hash | hashtable |
| hash | ziplist |
| list | linkedlist |
| list | ziplist |
| set | hashtable |
| set | intset |
| zset | skiplist |
| zset | ziplist |
13. flushall
- 作用:清除 redis 上所有数据;切记,生产环境禁用!
二、value 常见的数据类型
1. string
- 字符串类型是Redis最基础的数据类型
- Redis 中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的,例如列表和集合的元素类型是字符串类型。
- string 不仅可以存储文本数据,还有:整数、普通文本字符串、json、xml、二进制数据(图片、视频、音频...),不过一个字符串的最大值不能超过 512MB。
常见命令:
1.1 SET key value expiration EX seconds\|PX milliseconds NX\|XX
内属于可选选项,其中 '|' 表示'或者'的意思,也就是二选一
- 作用:set 的扩展选项,EX 和 PX 是可以直接顺带设置过期时间,NX | XX 则约束设置是否生效;
- EX seconds:使用秒作为单位设置key的过期时间。
- PX milliseconds:使用毫秒作为单位设置key的过期时间。
- NX:只在key不存在时才进行设置,即如果key之前已经存在,设置不执行。
- XX:只在key存在时才进行设置,即如果key之前不存在,设置不执行。
- 注意:set 设置 key 时,如果 key 存在,旧 value 会被新 value 覆盖,同时,原来 key 的 ttl (生存时间) 也会失效,也就是之前的过期设置作废;
- 时间复杂度:O(1)
1.2 get 命令补充说明
- 补充:get 获取 key 对应的value。如果key不存在,返回nil。如果value的数据类型不是string,会报错。
1.3 mset key value key value ...
- 作用:一次性设置多个 key 值
- 时间复杂度:O(n) ,n 代码命令中设置 key 的个数
1.4 mget key key ...
- 作用:一次性获取多个key的值。如果对应的key不存在或者对应的数据类型不是string,返回nil
- 时间复杂度:O(n),n 为key的数量
1.5 SETNX key value
- SETNX:只允许 key 不存在的情况下设置 key-vlaue
- 时间复杂度:O(1)
- 返回值:1 表示设置成功,0 表示失败
1.6 SETEX key seconds value / PSETEX key milliseconds value
- 作用:设置 key-value 的同时设置过期时间;setex 是秒级过期时间,psetex 是毫秒级
- 时间复杂度:O(1)
1.7 INCR key / DECR key
- INCR:将 key 对应的 string 表示的数字加 1。如果 key 不存在,则视为 key 对应的 value 是 0 ,创建 key 且 value = 1。如果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。
- 时间复杂度:O(1)
- 返回值:正常返回加完后的值
- DECR:将 key 对应的 string 表示的数字减一。如果 key 不存在,则视为 key 对应的value 是 0,创建 key 且初始value为 -1。如果 key 对应的 string 不是一个整型或者范围超过了64位有符号整型,则报错。
- 时间复杂度:O(1)
- 返回值:正常减完后的数值
1.8 INCRBY / DECRBY key decrement
- INCRBY:将 key 对应的 string 表示的数字加上对应的值。如果 key 不存在,则视为 key 对应的 value 是 0,v创建 key 且初始value为 decrement。如果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。
- 时间复杂度:O(1)
- 返回值:正常加上 decrement 后的返回值
- DECRBY:将 key 对应的 string 表示的数字减去对应的值。如果 key 不存在,则视为key 对应的 value 是 0 并创建。如果 key 对应的 string 不是一个整型或者范围超过了64位有符号整型,则报错。
- 时间复杂度:O(1)
- 返回值:正常减去 decrement 后的返回值
1.9 INCRBYFLOAT key increment
- 作用:将 key 对应的 string 表示的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果 key 不存在,则视为 key 对应的 value 是 0 并创建。如果 key 对应的不是string,或者不是一个浮点数,则报错。允许采⽤科学计数法表示浮点数。
- 时间复杂度:O(1)
- 返回值:加/减完后的数值。
1.10 append key value
- 作用:如果 key 已经存在并且对应值是一个 string,命令会将 value 追加到原有 string 的后边。如果 key 不存在,则效果等同于 SET 命令。
- 时间复杂度:O(1),追加的字符串一般长度较短,可以视为O(1)
- 返回值:追加完成之后 string 的长度。
1.11 getrange key start end
- 作用:返回 key 对应的 string 的子串,由 start 和 end 确定(左闭右闭)。0表示第一个字符,可以使用负数表示倒数。-1代表示倒数第一个字符,-2代表倒数第二个,其他的与此类似。超过范围的偏移量会根据 string 的长度调整成正确的值。
- 时间复杂度:O(N),N为start,end区间的长度.由于string通常比较短,可以视为是O(1)
- 返回值:string 类型的子串
1.12 setrange key offset value
- 作用:从指定的偏移位置开始,覆盖字符串为 value;如果 key 不存在则创建,offset 之前的每个位置会默认为 \x00
- 时间复杂度:O(N),N为value的长度,由于一般给的 value 比较短,通常视为O(1)
- 返回值:替换后的整体 string 的长度
1.13 strlen key
- 作用:获取 key 对应的 string 的长度(字节);当 key 存放的类似不是 string 时,报错。
- 时间复杂度:O(1)
- 返回值:替换后的 string 的长度。
- 补充:redis 数据存储默认是 utf8 编码,所以单个汉字占 3 字节
string 的内部编码
字符串类型的内部编码有3种:Redis 会根据当前值的类型和长度动态决定使用哪种内部编码实现。
- int:8个字节的长整型
- embstr:小于等于 39 个字节的字符串
- raw:大于 39 个字节的字符串
- 使用前面提到的 object encoding key 命令可以查看实际的内部编码实现
string 典型的使用场景
a. 缓存(Cache)功能
- 下图是比较典型的缓存使用场景,其中 Redis 作为缓冲层,MySQL 作为存储层,绝大部分请求的数据都是从 Redis 中获取。由于 Redis 具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
- Redis+MySQL组成的缓存存储架构:
- 补充:与MySQL等关系型数据库不同的是,Redis 没有表、字段这种命名空间,而且也没有对键名有强制要求(除了不能使用一些特殊字符)。但设计合理的键名,有利于防止键冲突和项目的可维护性,比较推荐的方式是使用 "业务名:对象名:唯⼀标识:属性" 作为键名。例如 MySQL 的数据库名为 vs,用户表名为 user_info,那么对应的键可以使用 "vs:user_info:6379"、"vs:user_info:6379:name" 来表示,如果当前 Redis 只会被一个业务使用,可以省略业务名 "vs:"。如果键名过长,则可以使用团队内部都认同的缩写替代,例如 "user:6379:friends:messages:5217" 可以被 "u:6379:fr:m:5217" 代替。毕竟键名过长,还是会导致Redis的性能明显下降的。
b. 计数(Counter)功能
- 许多应用都会使用 Redis 作为计数的基础⼯具,它可以实现快速计数、查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。如下图所示,例如视频网站的视频播放次数可以使用 Redis 来完成:用户每播放一次视频,相应的视频播放数就会自增1
- 记录视频播放次数架构:
- 补充:实际中要开发一个成熟、稳定的真实计数系统,要面临的挑战远不止如此简单:防作弊、按照不同维度计数、避免单点问题、数据持久化到底层数据源等。
c. 共享会话(Session)
- 问题:如下图所示,一个分布式Web服务将用户的 Session 信息(例如用户登录信息)保存在各自的服务器中,但这样会造成⼀个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同⼀台服务器上,这样当用户刷新⼀次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。
- Session分散存储架构:
- 解决方法:为了解决这个问题,可以使用 Redis 将用户的 Session 信息进行集中管理,如下图,在这种模式下,只要保证 Redis 是高可用和可扩展性的,无论用户被均衡到哪台 Web 服务器上,都集中从 Redis 中查询、更新Session信息。
- Redis集中管理Session 架构:
d. 手机验证码
- 很多应用出于安全考虑,会在每次进行登录时,让用户输⼊手机号并且配合给手机发送验证码, 然后让用户再次输入收到的验证码并进行验证,从而确定是否是用户本人。为了短信接口不会频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次。
- 此功能可以用以下伪代码说明基本实现思路:
bashString 发送验证码(phoneNumber) { key = "shortMsg:limit:" + phoneNumber; // 设置过期时间为 1 分钟(60 秒) // 使⽤ NX,只在不存在 key 时才能设置成功 bool r = Redis 执⾏命令:set key 1 ex 60 nx if (r == false) { // 说明之前设置过该⼿机的验证码了 long c = Redis 执⾏命令:incr key if (c > 5) { // 说明超过了⼀分钟 5 次的限制了 // 限制发送 return null; } } // 说明要么之前没有设置过⼿机的验证码;要么次数没有超过 5 次 String validationCode = ⽣成随机的 6 位数的验证码(); 验证码 5 分钟(300 秒)内有效 validationKey = "validation:" + phoneNumber; // Redis 执⾏命令:set validationKey validationCode ex 300; // 返回验证码,随后通过⼿机短信发送给⽤户 return validationCode ; } // 验证⽤⼾输⼊的验证码是否正确 bool 验证验证码(phoneNumber, validationCode) { validationKey = "validation:" + phoneNumber; String value = Redis 执⾏命令:get validationKey; if (value == null) { // 说明没有这个⼿机的验证码记录,验证失败 return false; } if (value == validationCode) { return true; } else { return false; } }
2. hash 哈希
- 在 Redis 中,哈希类型是指值 value 本身又是一个键值对结构,形如 key="key",value={{ field1, value1 }, ..., {fieldN, valueN } },Redis 键值对和哈希类型二者的关系可以用下图表示:
- 哈希类型中的映射关系通常称为 field-value,用于区分 Redis 整体的键值对(key-value), 注意这里的 value 是指 field 对应的值,不是键(key)对应的值,请注意value 在不同上下文的作用。
- field 不支持重复。
常见命令:
2.1 HSET key field value field value ...
- 设置 hash 中指定的字段(field)的值(value)。
- 时间复杂度:插⼊一组field为O(1),插入N组 field 为O(N)
- 返回值:添加的字段的个数
2.2 HGET key field
- 作用:获取 hash 中指定字段的值
- 时间复杂度:O(1)
- 返回值:字段对应的值或者nil。
2.3 HEXISTS key field
- 作用:判断 hash 中是否有指定的字段。
- 时间复杂度:O(1)
- 返回值:1表示存在,0表示不存在。
2.4 HDEL key field field ...
- 作用:删除hash中指定的字段。
- 时间复杂度:删除一个元素为O(1),删除N个元素为O(N)
- 返回值:本次操作删除的字段个数。
2.5 HKEYS key
- 作用:获取 hash 中的所有字段。
- 时间复杂度:O(N),N为field的个数
- 返回值:字段列表
2.6 HVALS key
- 作用:获取 hash 中的所有的值
- 时间复杂度:O(N),N为 field 的个数
- 返回值:所有的值
2.7 HGETALL key
- 作用:获取 hash 中的所有字段以及对应的值
- 时间复杂度:O(N),N 为 field 的个数
- 返回值:字段和对应的值
2.8 HMGET key field field ...
- 作用:一次获取 hash 中多个字段的值
- 时间复杂度:只查询⼀个元素为O(1),查询多个元素为O(N),N为查询元素个数
- 返回值:字段对应的值或者nil
2.9 HLEN key
- 作用:获取 hash 中的所有字段的个数
- 时间复杂度:O(1)
- 返回值:字段个数
2.10 HSETNX key field value
- 作用:在字段不存在的情况下,设置hash中的字段和值
- 时间复杂度:O(1)
- 返回值:1表示设置成功,0表示失败。
2.11 HINCRBY key field increment
- 作用:将 hash 中字段对应的数值添加指定的值,key 或 field 不存在会进行创建
- 时间复杂度:O(1)
- 返回值:该字段变化之后的值
2.12 HINCRBYFLOAT key field increment
- 作用:HINCRBY 的浮点数版本
- 时间复杂度:O(1)
- 返回值:该字段变化之后的值
2.13 HSTRLEN key field
- 作用:计算 value 的字符串长度
- 时间复杂度:O(1)
- 返回值:计算的字节长度
Hash 的内部编码
哈希的内部编码有两种:
- ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于 hash-max-ziplist-value 配置(默认64字节)时,Redis会使用ziplist 作为哈希的内部实现,ziplist 使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比 hashtable 更加优秀。
- hashtable(哈希表):当哈希类型无法满足 ziplist 的条件时,Redis 会使用 hashtable 作为哈希的内部实现,因为此时 ziplist 的读写效率会下降,而 hashtable 的读写时间复杂度为O(1)。
- 使用前面提到的 object encoding key 命令可以查看实际的内部编码实现:
Hash使用场景之一:结构化对象缓存
缓存方式对比:
- 原生字符串类型⸺使用字符串类型,每个属性⼀个键。
bashset user:1:name James set user:1:age 23 set user:1:city Beijing
- 优点:实现简单,针对个别属性变更也很灵活。
- 缺点:占用过多的键,内存占用量较大,同时用户信息在 Redis 中比较分散,缺少内聚性,所以这种方案基本没有实用性。
- 序列化字符串类型,例如JSON格式
bashset user:1 经过序列化后的用户对象字符串
- 优点:针对总是以整体作为操作的信息比较合适,编程也简单。同时,如果序列化方案选择合适,内存的使用效率很⾼。
- 缺点:本身序列化和反序列需要⼀定开销,同时如果总是操作个别属性则非常不灵活。
- 哈希类型
bashhmset user:1 name James age 23 city Beijing
- 优点:简单、直观、灵活。尤其是针对信息的局部变更或者获取操作。
- 缺点:需要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,可能会造成内存的较大消耗。
3. list 列表
- 列表类型是用来存储多个有序的字符串,列表中的每个字符串称为元素(element),⼀个列表最多可以存储 32² − 1 个元素。
- 列表支持负数下标,如上图,l 开头的 push 和 pop 对应着列表的头插和头删,r 开头的 push 和 pop 则对应着尾插和尾删,时间效率上都是O(1)。
- 因为列表头尾插入删除比较高效,所以也能充当栈和队列的角色。
- 列表中允许有重复元素。
常见命令
3.1 LPUSH key element element ...
- 作用:将一个或者多个元素从左侧放入(头插)到list中,key 不存在则创建。
- 时间复杂度:只插入一个元素为O(1),插入多个元素为O(N),N为插入元素个数。
- 返回值:插入后 list 的长度。
- 注意 key 如果存则 value 必须是列表,否则会失败
3.2 LRANGE key start stop
- 作用:获取从 start 到 end 区间的所有元素,左闭右闭。
- 时间复杂度:O(N)
- 返回值:指定区间的元素。
- stop 可以用负数下标。另外,如果 stop 超出范围,也不会报错,只会返回范围中存在的值:
注意返回值前面的序号只是结果集的序号,不是原始list的下标
另外 lrange 中的 l 表示 list 而不是 left
3.3 LPUSHX key element element ...
- 作用:在 key 存在时,将一个或者多个元素从左侧放入(头插)到 list 中。不存在,直接返回。
- 时间复杂度:只插入一个元素为O(1),插⼊多个元素为O(N),N为插⼊元素个数。
- 返回值:插入后 list 的长度。
3.4 RPUSH key element element ...
- 作用:将一个或者多个元素从右侧放入(尾插)到 list 中。
- 时间复杂度:只插入一个元素为O(1),插入多个元素为O(N),N为插入元素个数。
- 返回值:插入后 list 的长度。
3.5 RPUSHX key element element ...
- 作用:只在 key 存在时,将一个或者多个元素从右侧放入(尾插)到 list 中。
- 时间复杂度:只插入一个元素为O(1),插入多个元素为O(N),N为插入元素个数。
- 返回值:插⼊后 list 的长度。
3.6 LPOP key
- 作用:从list 左侧取出元素(即头删)。主要 6.2 后的高版本中,key 后面有 count 参数,可以设置执行几次头删。
- 时间复杂度:O(1)
- 返回值:取出的元素或者nil。
3.7 RPOP key
- 作用:从 list 右侧取出元素(即尾删)。同样 6.2 及高版本中,存在 count 参数可以设置执行多少次尾删。
- 时间复杂度:O(1)
- 返回值:取出的元素或者nil。
3.8 LINDEX key index
- 作用:获取 index 下标位置的元素。
- 时间复杂度:O(N)
- 返回值:取出的元素或者nil。
3.9 LINSERT key <BEFORE | AFTER> pivot element
- 作用:在指定元素 pivot 前或者后插入元素。
- 时间复杂度:O(N)
- 返回值:插入后的 list 长度,插入失败返回 -1
- 如果存在多个 pivot ,则按照从左往右第一个元素进行插入,如果 pivot 不存在则插入失败并返回 -1
3.10 LLEN key
- 作用:获取 list 长度。
- 时间复杂度:O(1)
- 返回值:list 的长度,不存在则为 0
3.11 LREM key count value
- 作用:删除指定元素 value
- count 有三种情况:
- count > 0:则从左往右删除 count 个 value
- count < 0:则从右往左删除 count 个 value
- count = 0:则删除全部 value
- 时间复杂度:O(N)
- 返回值:删除元素的个数
3.12 LTRIM key start stop
- 作用:删除区间以外的所有元素
- 时间复杂度:O(N)
- 返回值:OK
3.13 LSET key index value
- 作用:修改下标 index 处的元素为 value
- 时间复杂度为:O(N)
- 返回值:一般为 OK,下标越界则报错
3.14 阻塞版本命令 blpop brpop
blpop 和brpop 是 lpop 和 rpop 的阻塞版本,和对应非阻塞版本的作用基本一致,除了:
- 在列表中有元素的情况下,阻塞和非阻塞表现是一致的。但如果列表中没有元素,非阻塞版本会立即返回nil,但阻塞版本会根据 timeout,阻塞一段时间,期间 Redis 可以执行其他命令,但要求执行该命令的客户端会表现为阻塞状态。
- 命令中如果设置了多个键,那么会从左向右进行遍历键,一旦有一个键对应的列表中可以弹出元素,命令立即返回。
- 如果多个客户端针对同一个键同时执行 pop,则最先执行命令的客户端会得到弹出的元素。因为 redis 服务端的单线程模型,所以天然线程安全。
BLPOP key key ... timeout
- LPOP 的阻塞版本,可以指定多个 key。
- 如果这些 list 中有任何一个非空,则 blpop 可以立即获取到这里的元素并立即返回。如果这些 list 都为空,则需要阻塞等待,等待其他客户端往这些 list 中插入元素了。
- timeout 超时时间单位是秒,redis5中只能是整数,redis6中可以是小数。
- 时间复杂度:O(1)
- 返回值:取出的元素或者 nil。
BRPOP key key ... timeout
- RPOP的阻塞版本。其他说明参数上面的 brpop
- 时间复杂度:O(1)
- 返回值:取出的元素或者 nil。
list 的内部编码
以下是老版本编码,很早就已经没有使用了,仅参考:
- ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时 列表中每个元素的长度都⼩于list-max-ziplist-value配置(默认64字节)时,Redis会选用 ziplist 来作为列表的内部编码实现来减少内存消耗。
- linkedlist(链表):当列表类型无法满足 ziplist 的条件时,Redis会使用 linkedlist 作为列表的内部实现。
大概3.x时的redis,list 就已经采用的是 Quicklist 编码:
- quicklist:相当于是 linkedlist链表 和 ziplist压缩列表 的结合;整体还是一个链表,但是链表的每一个节点是压缩列表。
- /etc/redis/redis.conf 配置文件中的 list-max-ziplist-size 参数可以设置每个节点也就是压缩列表的大小,默认-2就是一个节点为8kb大小
list 的使用场景
a. 消息队列
- 如下图所示,Redis 可以使用 lpush+brpop 命令组合实现经典的阻塞式生产者-消费者模型队列,生产者客户端使用 lpush 从列表左侧插入元素,多个消费者客户端使用 brpop命令阻塞式地从队列中 "争抢"队首元素。通过多个客户端来保证消费的负载均衡和高可用性。
b. 分频道的消息队列
- 如下图所示,Redis 同样使用 lpush+brpop 命令,但通过不同的键模拟频道的概念,不同的消费者可以通过 brpop 不同的键值,实现订阅不同频道的理念。
c. 微博 Timeline
- 每个用户都有属于自己的 Timeline(微博列表),现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。
bash1.每篇微博使⽤哈希结构存储,例如微博中3个属性:title、timestamp、content: hmset mblog:1 title xx timestamp 1476536196 content xxxxx ... hmset mblog:n title xx timestamp 1476536196 content xxxxx 2.向用户Timeline添加微博,user:<uid>:mblogs作为微博的键: lpush user:1:mblogs mblog:1 mblog:3 ... lpush user:k:mblogs mblog:9 3.分⻚获取用户的Timeline,例如获取⽤⼾1的前10篇微博: keylist = lrange user:1:mblogs 0 9 for key in keylist { hgetall key }
- 此方案在实际中可能存在两个问题:
- 1+n 问题。即如果每次分页获取的微博个数较多,需要执行多次 hgetall 操作,此时可以考虑使用 pipeline(流水线)模式批量提交命令,或者微博不采用哈希类型,而是使用序列化的字符串类 型,使⽤mget获取。
- 分裂获取文章时,lrange 在列表两端表现较好,获取列表中间的元素表现较差,此时可以考虑将列表做拆分。
选择列表类型时,请参考:
- 同侧存取(lpush+lpop 或者 rpush+rpop)为栈
- 异侧存取(lpush+rpop 或者 rpush+lpop)为队列
4. set 集合
- 首先集合是无序的,也就是 set 2, 1, 3 和 1, 2, 3 是相同的
- 然后集合中的元素是不能重复的
- 集合中的元素类型都是 string
- 上图展示一个集合的结构;一个集合中最多可以存储 32² − 1 个元素。Redis 除了支持 集合内的增删查改操作,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多问题。
常见命令
集合相关的指令中,member 通常指元素,且命令一般以 S 开头
4.1 SADD key member member ...
- 作用:将一个或者多个元素添加到 set 中。注意,重复的元素只会添加一个到 set 中
- 时间复杂度:O(1)
- 返回值:本次添加成功的元素个数
4.2 SMEMBERS key
- 作用:获取一个 set 中的所有元素,注意,元素间的顺序是无序的
- 时间复杂度:O(N)
- 返回值:所有元素的列表
4.3 SISMEMBER key member
- 作用:判断一个元素在不在 set 中
- 时间复杂度:O(1)
- 返回值:1表示元素在 set 中;0表示元素不在 set 中或者 key 不存在。
4.4 SPOP key count
- 作用:从 set 中随机删除并返回一个或多个元素,count 指要删掉的个数;因为 set 是无序的,并且 spop 底层代码也采用了随机数,所以即使构造两个相同 set,删掉的元素也大概率不相同。
- 时间复杂度:O(N),n是count
- 返回值:删掉的元素
4.5 SRANDMEMBER key count
- 作用:随机的返回 key 集合中一个或多个元素
- 时间复杂度:O(N),n为count
- 返回值:随机返回的元素
4.6 SMOVE source destination member
- 作用:将一个元素从源 set 取出并放入目标 set 中。
- 注意,如果 member 在 destination 中已经存在,此操作不会失败,source 中的 menber 将会被删除,destination 不会新增元素;如果 member 在 source 中不存在,则会直接失败。
- 时间复杂度:O(1)
- 返回值:1表示移动成功,0表示失败。
4.7 SREM key member member ...
- 作用:将指定的元素从 set 中删除
- 时间复杂度:O(N),N是要删除的元素个数
- 返回值:本次操作删除的元素个数
4.8 SINTER key key ...
- 作用:获取给定 set 的交集中的元素;不会对给定的 key 集合造成影响。
- 时间复杂度:O(N*M),N是最小的集合元素个数,M是最大的集合元素个数
- 返回值:交集的元素
4.9 SINTERSTORE destination key key ...
- 作用:获取给定 set 的交集中的元素并保存到目标 destination 集合中;destination 不存在则会新建,存在则会重新赋值为交集
- 时间复杂度:O(N*M),N是最小的集合元素个数,M是最大的集合元素个数
- 返回值:交集的元素个数
4.10 SUNION key key ...
- 作用:获取给定 set 的并集中的元素;取并集,不影响指定的 key
- 时间复杂度:O(N),N给定的所有集合的总的元素个数
- 返回值:并集的元素
4.11 SUNIONSTORE destination key key ...
- 作用:获取给定 key 集合的并集中的元素,并保存到目标 destination 中;destination 不存在则创建,存在则重新赋值。
- 时间复杂度:O(N),N给定的所有集合的总的元素个数
- 返回值:并集的元素个数
4.12 SDIFF key key ...
- 作用:获取给定 key 的差集中的元素;如 sdiff A B,那么差集就是 A 中存在,B 中不存在的元素,交换 A B 结果不一样。此操作不会影响指定的 key。
- 时间复杂度:O(N),N给定的所有集合的总的元素个数
- 返回值:差集的元素
4.13 SDIFFSTORE destination key key ...
- 作用:获取给定 key 集合的差集中的元素,并保存到目标 destination 中;destination 不存在则创建,存在则重新赋值。
- 时间复杂度:O(N),N给定的所有集合的总的元素个数
- 返回值:差集的元素个数
set 的内部编码
集合类型的内部编码有两种:
- intset(整数集合):当集合中的元素都是整数并且元素的个数小于 set-max-intset-entries 配置 (默认512个)时,Redis 会选用 intset 来作为集合的内部实现,从而减少内存的使用。
- hashtable(哈希表):当集合类型无法满足 intset 的条件时,Redis 会使用 hashtable 作为集合的内部实现。
set 的使用场景
集合类型比较典型的使用场景是标签(tag),用户画像相关的。
- 例如A用户对娱乐、体育板块比较感兴趣,B用户对历史、新闻比较感兴趣,这些兴趣点可以被抽象为标签。有了这些数据就可以得到喜欢同⼀个标签的人,以及用户的共同喜好的标签,这些数据对于增强用户体验和用户黏度都非常有帮助。例如一个电子商务网站会对不同标签的用户做不同的产品推荐。
5. zset 有序集合
- zset 保留了 set 集合不能有重复成员 的特点,但与集合不同的是,有序集合中的每个元素都有一个**的浮点类型的分数(score)**与之关联,使得有序集合中的元素是可以维护有序性的,但这个有序不是用下标作为排序依据而是用这个分数。
- 这里的有序就是依据分数的升序排列
- 分数相同的元素,按照元素自身字符串的字典序进行排列
常见命令:
5.1 ZADD key NX \| XX GT \| LT CH INCR score member score member ....
- 作用:添加或者更新指定的元素以及关联的分数到 zset 中,分数应符合double类型
- 时间复杂度:O(log(N))
- 返回值:默认情况下返回成功添加的元素个数
- XX 和 NX选项:
- XX:仅仅用于更新已经存在的元素,不会添加新元素。
- NX:仅用于添加新元素,不会更新已经存在的元素。
- CH 选项:
- CH:默认情况下,ZADD 返回的是本次添加的元素个数,但指定这个选项之后,就会还包含本次更新的元素的个数。
- INCR 选项:
- INCR:此命令类似 ZINCRBY 的效果,将元素的分数加上指定的分数。此时只能指定一个元素和分数。且返回值会变为更新后的分数。
- 不带选项:
- XX 和 CH:
- INCR:
- 很明显,此时 ch 和 incr 一起用时返回值是 incr 更新后的分数
5.2 ZRANGE key start stop WITHSCORES
- 作用:按照分数升序返回指定区间的元素。带上WITHSCORES可以把分数也返回。支持负数下标。
- 时间复杂度:O(log(N)+M)
- 返回值:区间内的元素列表
5.3 ZCARD key
- 作用:获取一个 zset 的基数(cardinality),即 zset 中的元素个数。
- 时间复杂度:O(1)
- 返回值:zset内的元素个数
5.4 ZCOUNT key min max
- 作用:返回分数在 min 和 max 之间的元素个数,默认情况下,min和max都是包含的,可以通过 ( 排除。
- 时间复杂度:O(logN)
- 返回值:满足条件的元素列表个数
- 可以用浮点数中的负无穷大 -inf 和正无穷大 inf 计算整个 zset 元素个数
5.5 ZREVRANGE key start stop WITHSCORES
- 作用:与 zrange 相反,以降序返回区间内的元素,带上 withsores 可显示对应元素分数
- 备注:这个命令可能在6.2.0之后废弃,并且功能合并到ZRANGE中
- 时间复杂度:O(log(N)+M)
- 返回值:区间内的元素列表
5.6 ZRANGEBYSCORE key min max WITHSCORES
- 作用:返回分数在 min 和 max 之间的元素,默认情况下,min 和 max 都是包含的,可以通过(排除。命令后面其实还有一个 limit offset count 参数可以控制一次返回几个数。
- 备注:这个命令可能在6.2.0之后废弃,并且功能合并到ZRANGE中
- 时间复杂度:O(log(N)+M
- 返回值:区间内的元素列表
5.7 ZPOPMAX key count
- 作用:删除并返回 key 有序集合中分数最高的 count 个元素。如果最大的分数存在多个元素,则按字典序进行删除。
- 时间复杂度:O(log(N)*M)
- 返回值:删除的分数和元素列表
5.8 BZPOPMAX key key ... timeout
- 作用:ZPOPMAX的阻塞版本,有元素会立即返回,否则阻塞。timeout 设置阻塞超时时间,单位秒。
- 时间复杂度:O(logN)
- 返回值:元素列表
5.9 ZPOPMIN key count
- 作用:删除并返回分数最低的count个元素。
- 时间复杂度:O(log(N)*M)
- 返回值:分数和元素列表
5.10 BZPOPMIN key key ... timeout
- 作用:ZPOPMIN的阻塞版本。
- 时间复杂度:O(log(N))
- 返回值:元素列表。
5.11 ZRANK key member
- 作用:按升序,返回指定元素的排名,即下标
- 时间复杂度:O(log(N))
- 返回值:排名,如果元素不存在则为空nil
5.12 ZREVRANK key member
- 作用:按降序,返回指定元素的排名。
- 时间复杂度:O(log(N))
- 返回值:排名,元素不存在则为nil
5.13 ZSCORE key member
- 作用:返回指定元素的分数。
- 时间复杂度:O(1)
- 返回值:分数,元素不存在则返回nil
5.14 ZREM key member member ...
- 作用:删除指定的元素
- 时间复杂度:O(M*log(N))
- 返回值:本次操作删除的元素个数
5.15 ZREMRANGEBYRANK key start stop
- 作用:按照升序,删除指定下标范围内的元素,左闭右闭。
- 时间复杂度:O(log(N)+M)
- 返回值:本次操作删除的元素个数
5.16 ZREMRANGEBYSCORE key min max
- 作用:按照分数升序,删除指定分数范围内的元素,左闭右闭
- 时间复杂度:O(log(N)+M)
- 返回值:本次操作删除的元素个数
5.17 ZINCRBY key increment member
- 作用:给指定元素 member 的分数加上 increment,increment 可以是浮点数,可以是负数
- 时间复杂度:O(log(N))
- 返回值:增加后元素的分数
5.18 ZINTERSTORE destination numkeys key key ... WEIGHTS weight \[weight ...] AGGREGATE \
- 作用:求多个 key 的交集, 结果放在 destination 有序集合中,destination 不存在则创建,存在则重新赋值;
- 时间复杂度:O(N*K)+O(M*log(M)) ,N是输入的有序集合中,最小的有序集合的元素个数;K是输入了 几个有序集合;M是最终结果的有序集合的元素个数。
- 返回值:目标集合中的元素个数
- numkeys 参数:需要指定有多少个 key求交集,因为 key 后面可能还有其他参数,所以为了避免 "黏包",需要告诉 redis 有多少个 key。
- weights 选项:权重,如果需要分配权重,则有多少 key,后面就按顺序设置权重,可以是整数可以是小数。最终求交集时,会先分别根据自己的权重先计算各元素的分数再计算交集后的分数。
- aggregate 选项:ZINTERSTORE 默认求交集时,遇到相同元素不同分数时,是按照求和计算该元素交集的分数的,如果想改变,则可以指定该参数,可以换成取分数的较小值或者较大值。
- 默认不使用选项:
- 使用 weights 选项设置权重:例 a = 10*0.4 + 50*0.5 = 29
- 使用 aggregate 选项设置最终分数取较小值:
5.19 ZUNIONSTORE destination numkeys key key ... WEIGHTS weight \[weight ...] AGGREGATE \
- 作用:求多个 key 有序集合的并集,其参数选项参考上一个指令 ZINTERSTORE
- 时间复杂度:O(N)+O(M*log(M)),N是输入的有序集合总的元素个数;M是最终结果的有序集合的元素个数。
- 返回值:目标集合中的元素个数
zset 的内部编码
有序集合类型的内部编码有两种:
- ziplist(压缩列表):当有序集合的元素个数小于 zset-max-ziplist-entries 配置(默认128个),同时每个元素的值都小于 zset-max-ziplist-value 配置(默认64字节)时,Redis会用 ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使⽤。
- skiplist(跳表):当ziplist条件不满足时,有序集合会使用 skiplist 作为内部实现,因为此时 ziplist 的操作效率会下降。
zset 的使用场景
- 有序集合比较典型的使用场景就是排行榜系统。例如常见的网站上的热榜信息,榜单的维度可能是多方面的:按照时间、按照阅读量、按照点赞量。
- 本例中我们使用点赞数这个维度,维护每天的热榜:
bash1.添加用户赞数 例如用户james发布了⼀篇⽂章,并获得3个赞,可以使⽤有序集合的zadd和zincrby功能: zadd user:ranking:2022-03-15 3 james 之后如果再获得赞,可以使⽤zincrby: zincrby user:ranking:2022-03-15 1 james 2.取消用户赞数 由于各种原因(例如用户注销、用户作弊等)需要将用户删除,此时需要将用户从榜单中删除掉,可 以使⽤zrem。例如删除成员tom: zrem user:ranking:2022-03-15 tom 3.展⽰获取赞数最多的10个用户 此功能使⽤zrevrange命令实现: zrevrangebyrank user:ranking:2022-03-15 0 9 4.展⽰用户信息以及用户分数 此功能将用户名作为键后缀,将用户信息保存在哈希类型中,⾄于用户的分数和排名可以使⽤ zscore 和 zrank 来实现: hgetall user:info:tom zscore user:ranking:2022-03-15 mike zrank user:ranking:2022-03-15 mike
6. 其他数据类型简介
- stream
- stream 就是一个队列(阻塞队列),是 redis 用作消息队列的一个重要支撑,属于是 List 的 brpop / blpop 的升级版本。
- geospatial
- 用来存储坐标,存储一些点之后,就可以让用户给定一个坐标,取从刚才存储的点里进行查找(按照半径、矩形区域...),这个功能常见于"地图"应用中。
- hyperloglog
- 核心作用是用来估算一个集合中有多少个元素,相比 set 如果使用了800mb记录一些数据时,hyperloglog 只需要12kb左右就能表示这些数据的个数。hyperloglog 主要是记录元素的特征,能够判断添加的元素是否是新元素,但不存储元素内容。所以常用于计数,但是有一定误差。
- bitmaps
- 位图,属于 set 针对整数元素类型的一个特化版本,使用位图保存整数,节省空间。如果需要用到集合并且数据都是整数就可以使用该类型。
- bitfield
- 位域,使用上类似 c语言 中的位段。核心目的都是将多个"小整数"紧凑地塞进一个大的二进制连续内存块中,从而极致节约内存。
三、渐进式遍历 scan
- Redis 使用 scan 命令进行渐进式遍历键,进而解决直接使用 keys 获取键时可能出现的阻塞问题。每次scan命令的时间复杂度是O(1),但是要完整地完成所有键的遍历,需要执行多次scan
- 语法:
- SCAN cursor MATCH pattern COUNT count TYPE type
- cursor:游标,一个字符串,决定下次 scan 从哪里继续遍历,但是 cursor 并不是严格递增的,所以一般从 0 开始,后续需要根据 scan 的返回值在进行设置。
- MATCH pattern:符号匹配,参考上文基础全局命令中 keys pattern 的五种示例样式。
- COUNT count:一次遍历返回多少个,注意这只是建议值,实际一次返回多少个以实际为准。
- TYPE type:设置只遍历特定数据类型的key
- 时间复杂度:O(1)
- 返回值:下⼀次scan的游标(cursor)以及本次得到的键
- 示例:一次性遍历完
- 示例:一次性遍历个数设置为2个并进行多次遍历
- 这里就能看到 scan 的第一个返回值,返回的是下一次继续遍历的 cursor 值,但这个值不是严格递增的。另外,当返回 cursor 是0时一般是遍历结束了。
注意:
- 除了scan以外,Redis 面向哈希类型、集合类型、有序集合类型分别提供了hscan、sscan、zscan命 令,它们的用法和 scan 基本类似。
- 渐进性遍历 scan 虽然解决了阻塞的问题,但如果在遍历期间键有所变化(增加、修改、删 除),可能导致遍历时键的重复遍历或者遗漏,这点务必在实际开发中考虑。
四、数据库管理操作
- 在 redis 中,是存在多个数据库的,但是不能像 mysql 那样自己创建和删除数据库。
- redis 中,默认一共是存在 16 个数据库的,从 0-15 编号,我们默认使用的是 0 号数据库。每个数据库之间数据相互隔离。
- select dbIndex
- 作用:切换数据库,dbIndex 设置为 0 - 15 就能切换到对应数据库
- dbsize
- 作用:查看当前数据库存在多少个 key
- flushdb / flushall
- 作用:用于清除数据库,区别在于 flushdb 只清除当前数据库,flushall会清楚所有数据库。
- 生产环境慎用
注意:
- Redis 中虽然支持多数据库,但随着版本的升级,其实不是特别建议使用多数据库特性。如果真的需要完全隔离的两套键值对,更好的做法是维护多个 Redis 实例,而不是在一个 Redis 实例中维护多数据库。这是因为本身 Redis 并没有为多数据库提供太多的特性,其次无论是否有多个数据库,Redis 都是使用单线程模型,所以彼此之间还是需要排队等待命令 的执行。同时多数据库还会让开发、调试和运维⼯作变得复杂。所以实践中,始终使用数据库0其实是⼀个很好的选择。
五、通过 c++ 构造客户端使用 redis
1. 前置安装
- redis 服务端使用的是自定义的 RESP 协议,基于 tcp 的应用层协议,该协议详细内容可以去官网查看。感兴趣可以自己手搓 sdk 构造和解析该协议。我们当前就直接使用第三方已经封装好的 sdk 即可,也就是:redis-plus-plus
- github 连接:GitHub - sewenew/redis-plus-plus: Redis client written in C++ · GitHub
安装过程:
- redis-plus-plus 是基于 hiredis 实现的,hiredis 是⼀个C语言实现的redis客户端,因此需要先安装 hiredis,直接使用包管理器安装即可:
- 安装 hiredis:sudo apt install -y libhiredis-dev (ubunru)、sudo yum install hiredis-devel.x86_64(centos)
- 然后,我们需要通过编译源码安装 redis-plus-plus:
- 下载 redis-plus-plus 源码:git clone https://github.com/sewenew/redis-plus-plus.git
- 剩下的就是 cmake 的编译安装流程了:
bashcd redis-plus-plus mkdir build cd build cmake .. make sudo make install
2. 简单链接测试
cpp#include <iostream> #include <sw/redis++/redis++.h> using namespace std; int main() { // 创建 redis 连接对象。需传入 redis 服务器的地址和端口号 sw::redis::Redis redis("tcp://127.0.0.1:6379"); // 简单的 Ping 测试,redis 的这些操作方法和 redis-cli 的命令是对应的 string ping_result = redis.ping(); cout << "Ping result: " << ping_result << endl; return 0; }makefile:
bashhello:main.cc g++ $^ -o $@ -std=c++17 /usr/local/lib/libredis++.a /usr/lib/x86_64-linux-gnu/libhiredis.a -pthread .PHONY:clean clean: rm -f hello运行结果:
3. string 演示示例
cpp#include <iostream> #include <string> #include <vector> #include <sw/redis++/redis++.h> using namespace sw::redis; int main() { try { // 1. 连接到 Redis 服务器 // 地址格式:tcp://[[username:]password@]host:port[/db] // 这里使用默认无密码,数据库 0 auto redis = Redis("tcp://127.0.0.1:6379"); std::cout << "=== 清空当前数据库(flushall) ===" << std::endl; // flushall 会删除所有数据库中的所有 key,生产环境慎用! redis.flushall(); std::cout << "数据库已清空\n" << std::endl; // 2. String 基本操作:set / get std::cout << "=== set / get 演示 ===" << std::endl; // set 设置 key-value,成功返回 true(实际返回 OK 状态) redis.set("user:name", "Alice"); // get 返回 OptionalString,若 key 不存在则返回空 Optional auto name = redis.get("user:name"); if (name) { std::cout << "user:name = " << *name << std::endl; } else { std::cout << "user:name 不存在" << std::endl; } // 演示获取不存在的 key auto missing = redis.get("user:not_exist"); if (!missing) { std::cout << "user:not_exist 不存在(get 返回空 Optional)" << std::endl; } std::cout << std::endl; // 3. incr / decr 操作(针对整数) std::cout << "=== incr / decr 演示 ===" << std::endl; // 设置一个计数器 redis.set("counter", "10"); // incr 将 key 的值加 1,并返回新值;若 key 不存在则先设为 0 再加 1 long long val = redis.incr("counter"); std::cout << "counter 自增后 = " << val << std::endl; // decr 将 key 的值减 1,并返回新值 val = redis.decr("counter"); std::cout << "counter 自减后 = " << val << std::endl; // 也可以一次增减指定数值 val = redis.incrby("counter", 5); std::cout << "counter 增加 5 后 = " << val << std::endl; val = redis.decrby("counter", 3); std::cout << "counter 减少 3 后 = " << val << std::endl; std::cout << std::endl; // 4. getrange / setrange 操作(子字符串) std::cout << "=== getrange / setrange 演示 ===" << std::endl; // 准备一个较长字符串 redis.set("greeting", "Hello, Redis!"); // getrange 返回 [start, end] 范围的子字符串,索引从 0 开始,支持负数(-1 表示最后一个字符) auto sub = redis.getrange("greeting", 0, 4); std::cout << "greeting[0,4] = " << sub << std::endl; // setrange 从指定偏移量开始覆盖字符串,返回修改后字符串的总长度 // 将 "Redis" 替换为 "World"(偏移量 7 开始) redis.setrange("greeting", 7, "World"); auto new_greeting = redis.get("greeting"); std::cout << "修改后的 greeting = " << *new_greeting << std::endl; // 如果偏移量大于原字符串长度,中间会补零字节('\0') redis.setrange("greeting", 20, "!"); new_greeting = redis.get("greeting"); std::cout << "偏移超过原长度后的 greeting(注意可能包含空字符): "; for (char c : *new_greeting) { if (c == '\0') std::cout << "\\0"; else std::cout << c; } std::cout << std::endl << std::endl; // 5. 设置过期时间 expire 和查询 TTL std::cout << "=== expire / ttl 演示 ===" << std::endl; // 设置一个临时 key redis.set("temp:key", "temp value"); // expire 设置 key 的生存时间(秒),成功返回 true bool ok = redis.expire("temp:key", 5); std::cout << "设置过期时间 5 秒: " << (ok ? "成功" : "失败") << std::endl; // ttl 返回剩余生存时间(秒),-1 表示无过期时间,-2 表示 key 不存在 long long ttl = redis.ttl("temp:key"); std::cout << "temp:key 剩余 TTL = " << ttl << " 秒" << std::endl; // 等待 6 秒后 key 应已过期 std::cout << "等待 6 秒..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(6)); ttl = redis.ttl("temp:key"); std::cout << "过期后 temp:key 剩余 TTL = " << ttl << " (-2 表示不存在)" << std::endl; if (!redis.get("temp:key")) { std::cout << "temp:key 已自动删除" << std::endl; } std::cout << std::endl; // 6. keys * 演示 std::cout << "=== keys * 演示 ===" << std::endl; std::vector<std::string> result; auto it = std::back_inserter(result); // 返回一个插入迭代器 redis.keys("*", it); // 将所有 key 通过插入迭代器 it 插入 result std::cout << "当前数据库中的所有 key(共 " << result.size() << " 个):" << std::endl; for (const auto& k : result) { std::cout << " " << k << std::endl; } std::cout << std::endl; // 7. 补充:exists / type 命令 std::cout << "=== exists / type 补充演示 ===" << std::endl; // exists 判断 key 是否存在,可以同时判断多个,返回存在的个数 long long exists_count = redis.exists({"user:name", "counter", "not_exist"}); std::cout << "存在 key 的数量((\"user:name\", \"counter\", \"not_exist\")) = " << exists_count << std::endl; // type 返回 key 的类型,返回 std::string,如 "string", "list", "set" 等 std::string key_type = redis.type("counter"); std::cout << "counter 的类型 = " << key_type << std::endl; std::cout << std::endl; // 8. 最终清理:删除演示用 key(可选) std::cout << "=== 清理演示数据 ===" << std::endl; // del 删除一个或多个 key,返回删除的数量 long long deleted = redis.del({"user:name", "counter", "greeting"}); std::cout << R"(删除了"user:name", "counter", "greeting")" << deleted << " 个 key" << std::endl; // 再次查看所有 key std::vector<std::string> result2; auto it2 = std::back_inserter(result2); redis.keys("*", it2); std::cout << "清理后剩余 key 数量 = " << result2.size() << std::endl; } catch (const Error& e) { std::cerr << "Redis 错误: " << e.what() << std::endl; return 1; } return 0; }makefile 同上,运行结果:
- 这里关于 keys * 的接口,需要用到插入迭代器,就是 std::back_inserter 的返回值,用来接收 keys * 的查询结果。std::back_inserter 的参数就是一个容器的引用,该容器需要支持尾插。
- 那么为什么 keys 接口不能直接对这里的 vector 容器本身进行操作?答案是为了解耦合。
总结
以上就是本文的全部内容,感谢支持!!!
























































































































