Redis基础
一、Redis 介绍
Redis (Remote Dictionary Server) 是一个开源的、基于 内存 的 键值对存储系统 ,支持多种数据结构,可用作数据库、缓存和消息中间件。Redis是一种 客户端服务器 架构。
Redis是单线程为什么快?
首先快是相对的,要看和谁比,通常情况下,Redis是与关系型数据库如MySql等比比较。
-
纯内存操作:所有数据在内存中,避免磁盘 I/O 瓶颈。数据库则是访问好硬盘。
-
Redis 核心功能都是操作内存的数据结构,这些核心功能比数据库更简单(数据库对于数据的插入删除查询,都有更复杂的功能支持,这样的功能势必要花费更多的开销,比如,数据库中的各种约束)。
-
单线程模型:避免线程切换开销与锁竞争(单线程快还是多线程快应取决于不同的应用场景,redis每个操作都是 短平快,就算搞一个多线程提升也不大)。
-
I/O多路复用:单线程处理多个文件描述符,使用 epoll 高效管理网络I/O。
-
高效编码:针对小数据优化存储格式,合理数据结构选择,每种数据结构针对特定场景优化。
虽然单线程给 Redis 带来很多好处,但还是有一个致命的问题:对于单个命令的执行时间都是有要求的。如果某个命令执行过长,会导致其他客户端的命令全部处于阻塞中,迟迟得不到相应,对于 Redis 这种高性能的服务来说是非常严重的。
Redis的特性
Redis 是一个内存中存储数据的中间件,用于作为数据库或数据缓存,在分布式系统中能够大展拳脚。
-
在内存中存储数据 :
key都是string,而value则可以是一些数据结构(strings、hashes、lists、sets、sorted sets and more)。 -
可编程性: 针对 Redis 的操作,可以直接通过简单的交互式命令进行操作。
-
持久化: Redis 把数据存储在内存上,内存数据是易失的,而 Redis 会把数据存储在硬盘上,内存为主,硬盘为辅(如果 Redis 重启了,会在重启时加载硬盘中的备份数据,使 Redis 的内存恢复到重启前的状态)。
-
支持集群: 一个 Redis 能存储的数据是有限的,引入多个主机后,部署多个 Redis 节点,每个 Redis 存储数据的一部分。
-
高可用: Redis 自身也支持主从结构的,从节点就相当于主节点的备份。
Redis 被当作缓存应用广泛,对数据进行冷热分离,在计算机领域中有一条这样的定律,20%的数据可以满足80%的需求。
Redis的安装
ubuntu:
bash
apt install -y redis
# 查看 redis 是否启动
sudo netstat -anp | grep redis
redis 的配置文件:
bash
/etc/redis/redis.conf
修改访问 ip 和 port:
bash
bind 0.0.0.0 ::1
protected-mode no
port 6379
修改配置后,重新启动 redis:service redis-server restart。
Redis命令行客户端
第一种是交互式方式:通过 redis-cli -h { host } -p { port } 的⽅式连接到 Redis 服务,后续所有的操作都是通过交互式的⽅式实现,不需要再执⾏ redis-cli。
bash
redis-cli [-h 127.0.0.1] [-p 6379]
第二种是命令方式: 通过 redis-cli -h { host } -p { port } { command } 就可以直接得到命令的返回结果。
bash
redis-cli -h 127.0.0.1 -p 6379 ping
-
redis客户端默认连接本地
127.0.0.1的 redis 服务器。 -
端口也使用的是默认的 6379 端口。
redis-cli --raw 是Redis 命令行客户端的输出格式化参数,用于强制以原始格式显示响应数据,避免 Redis 默认的转义行为。默认情况下,Redis 命令行工具会对非打印字符(如二进制数据、中文等)进行转义显示(例如 "\xe4\xb8\xad\xe6\x96\x87"),加上 --raw 后会直接输出原始字节内容,使中文正常显示。
二、Redis全局命令
2.1 set和get
设置键值对。
redis
set key value [NX|XX] [EX seconds] [PX milliseconds]
-
NX: 键不存在时才设置(Not eXists) -
XX: 键存在时才设置 -
EX seconds: 过期时间(秒) -
PX milliseconds: 过期时间(毫秒)
获取键值对。
redis
get key
- 如果当前 key 不存在,会返回
nil。
批量操作
redis
mset key1 value1 [key2 value2 ...]
mget key1 [key2 key3 ...]
注意:redis 是一个客户端/服务器结构的程序,客户端和服务器之间通过网络来进行通信,分开的写法,会产生更多轮次的网络通信。
2.2 keys
返回所有满⾜样式(pattern)的 key。⽀持如下统配样式。
redis
keys pattern
pattern:
-
h?llo匹配hello,hallo和hxllo。 -
h*llo匹配hllo和heeeello。 -
h[ae]llo匹配hello和hallo但不匹配hillo。 -
h[^e]llo匹配hallo,hbllo, ... 但不匹配hello -
h[a-b]llo匹配hallo和hbllo。 -
显示 Redis 所有的 key
keys *。
返回值:匹配 pattern 的所有的 key。
2.3 exists
判断某个 key 是否存在。
redis
exists key [key ...]
返回值:key存在的个数。
2.4 del
删除指定的 key。
redis
del key [key ...]
返回值:删除掉的 key 的个数。
2.5 expire
为指定的 key 添加秒级的过期时间(Time To Live TTL)。超出这个时间,key 就会被自动删除。
redis
expire key seconds
返回值:1表示设置成功;0表示设置失败。
2.6 ttl
获取指定 key 的过期时间,秒级。
redis
ttl key
返回值:剩余过期时间。-1 表示没有关联过期时间,-2 表示 key 不存在。
expire 和 ttl 命令都有对应的支持毫秒为单位的版本:pexpire 和 pttl。
2.7 redis 的 key 的过期策略
redis 整体的策略是:
-
定期删除:每次抽取一部分,进行验证过期时间。如果直接遍历所有的 key,效率是很低的,因为 redis 主要是单线程程序,如果扫描过期 key 消耗的时间过长,就可能导致正常的命令被阻塞。
-
惰性删除:假设这个 key 已经到达了过期时间,但是暂时还没删除,key 还存在,当后面访问该 key 时,此时的访问就会让 redis 服务器触发删除 key 的操作,同时返回
nil。
redis 为了对上述策略进行补充,还提供了一系列的内存淘汰策略。
当过期键还没来得及删,内存又满了,Redis 触发 maxmemory-policy:
| 策略 | 说明 |
|---|---|
| noeviction | 内存满直接报错(默认) |
| allkeys-lru | 所有键,删除最近最少用的 |
| volatile-lru | 设过期时间的键,删除最近最少用的 |
| allkeys-random | 所有键,随机删 |
| volatile-random | 设过期时间的键,随机删 |
| volatile-ttl | 设过期时间的键,删除剩余寿命最短的 |
| allkeys-lfu | 所有键,删除不经常用的(4.0+) |
| volatile-lfu | 设过期时间的键,删除不经常用的(4.0+) |
配置
bash
# /etc/redis/redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru
2.8 type
返回 key 对应的数据类型。
redis
type key
返回值:none,string,list,set,zset,hash,stream 等。
2.9 flushall
删除当前 redis 中所有数据库的所有键。
redis
flushall
三、Redis数据类型和内部编码
Redis 常用的五种数据类型:
-
String(字符串)
-
Hash(哈希)
-
List(列表)
-
Set(集合)
-
Zset(有序集合)
每种类型对外暴露的数据结构是固定的,但内部实际存储编码会根据数据长度、元素数量等自动切换(为了节省内存/提高性能)。
| 数据结构 | 内部编码 |
|---|---|
| string | raw |
| int | |
| embstr | |
| hash | hashtable |
| ziplist | |
| list | quicklist |
| set | intset |
| hashtable | |
| zset | skiplist |
| ziplist |
查看当前数据类型内部编码:
redis
object encoding key
当元素少时,Redis 选择节省内存但性能稍低的编码方式;当元素达到一定阈值后,自动切换到性能更高但内存开销更大的常规数据结构。
四、Redis常用的数据类型
注意:首先 Redis 中所有的键(key)类型都是字符串类型,这里说的数据类型指的是值(value)的类型。
4.1 string
| 命令 | 功能解释 | 时间复杂度 | 注意事项 |
|---|---|---|---|
| set key value [key value...] | 设置一个或多个键的值,键已存在则覆盖; 成功返回 OK , 如果设置了指定的 NX 或 XX 但条件不满足,set 不会执行,并返回 nil | O(k), k 是键个数 | EX seconds: 使用秒作为单位设置 key 的过期时间 PX milliseconds: 使用毫秒作为单位设置 key 的过期时间 NX: 只有在 key 不存在时才进行设置,如果 key 已经存在,设置不执行 XX: 只有在 key 存在时才进行设置,如果 key 不存在,设置不执行 |
| get key | 返回指定键的值,键不存在返回 nil。如果 value 的数据类型不是 string,则会报错 | O(1) | 仅支持字符串类型,复杂类型需用特定命令 |
| del key [key ...] | 删除一个或多个键,返回被删除键的数量 | O(k), k 是键个数 | 不存在的键会被忽略 |
| mset key value [key value ...] | 原子性批量设置多对键值对,覆盖已存在的键;返回 OK | O(k), k 是键个数 | 要么全部成功,要么全部失败 |
| mget key [key ...] | 批量获取多个键的值,按请求顺序返回,不存在的返回 nil | O(k), k 是键个数 | 不会因为某个键不存在而报错 |
| incr key | 将键存储的数字值加1,返回增加后的值;键不存在则初始化为0再执行 | O(1) | 值必须是整数,否则报错 |
| decr key | 将键存储的数字值减1,返回减少后的值;键不存在则初始化为0再执行 | O(1) | 值必须是整数,否则报错 |
| incrby key n | 将键存储的数字值增加整数 n,返回增加后的值;键不存在则初始化为0再执行 | O(1) | n 必须是整数,值必须是整数 |
| decrby key n | 将键存储的数字值减少整数 n,返回减少后的值;键不存在则初始化为0再执行 | O(1) | n 必须是整数,值必须是整数 |
| incrbyfloat key n | 将键存储的数字值增加浮点数 n,返回增加后的值;键不存在则初始化为0再执行 | O(1) | n 可以是整数或浮点数,支持高精度 |
| append key value | 将 value 追加到原字符串末尾,返回追加后的字符串长度;键不存在则创建 | O(1) | 键已存在且非字符串类型会报错 |
| strlen key | 返回键存储的字符串长度,键不存在返回 0 | O(1) | 键存在但非字符串类型会报错 |
| setrange key offset value | 从偏移量开始覆盖字符串内容,返回修改后的字符串长度;键不存在则填充空字符串 | O(n), n 是字符串长度, 通常视为 O(1) | offset 不能为负数,超出长度会填充空字符 |
| getrange key start end | 返回字符串从 start 到 end 的子串,包含两端(左闭右闭),可以使用负数,-1代表倒数第一个字符,-2代表倒数第二个,以此类推;键不存在返回空字符串 | O(n), n 是字符串长度, 通常视为 O(1) | start 和 end 支持负数偏移,超出范围自动截断 |
内部编码
字符串类型的内部编码有 3 种:
-
int: 8个字节的长整形。
-
embstr: 小于等于 39 个字节的字符串。
-
raw: 大于 39 个字节的字符串
Redis 会根据当前值的类型和长度动态使用哪种内部编码实现。
典型使用场景
-
缓存功能:Redis 作为缓存层,MySql 作为存储层,绝大部分请求的数据都是从 Redis 中获取。由于 Redis 具有支撑高并发的特性,所以缓存通常能起到加速读写和降低数据库压力的作用。注意:与关系型数据不同的是,Redis 没有表、字段这种命名空间,所以设计合理的键名,有利于防止键冲突和项目的可维护性,比较推荐的方式是使用 "业务名:对象名:唯一标识:属性"作为键名,业务名过长也可以使用缩写。
-
计数功能:许多应⽤都会使⽤ Redis 作为计数的基础⼯具,它可以实现快速计数、查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。
-
共享会话:⼀个分布式 Web 服务将⽤⼾的 Session 信息(例如⽤⼾登录信息)保存在各⾃的服务器中,但这样会造成⼀个问题:出于负载均衡的考虑,分布式服务会将⽤⼾的访问请求均衡到不同的服务器上,并且通常⽆法保证⽤⼾每次请求都会被均衡到同⼀台服务器上,这样当⽤⼾刷新⼀次访问是可能会发现需要重新登录,为了解决这个问题,可以使用 Redis 将用户的 Session 信息都存储到 Redis 中,无论用户被负载均衡到哪台 Web 服务器上,都集中从 Redis 中查询,更新 Session 信息。
-
手机验证码:很多应⽤出于安全考虑,会在每次进⾏登录时,让⽤⼾输⼊⼿机号并且配合给⼿机发送验证码,然后让⽤⼾再次输⼊收到的验证码并进⾏验证,从⽽确定是否是⽤⼾本⼈。为了短信接⼝不会频繁访问,会限制⽤⼾每分钟获取验证码的频率,例如⼀分钟不能超过 5 次。
4.2 hash
哈希类型中的映射关系通常称为 field-value,⽤于区分 Redis 整体的键值对(key-value),注意这⾥的 value 是指 field 对应的值,不是键(key)对应的值,请注意 value 在不同上下文的作用。
| 命令 | 功能解释 | 时间复杂度 | 注意事项 |
|---|---|---|---|
| hset key field value [field value ...] | 设置 Hash 中指定字段的值,返回新增字段数量(修改不计数) | O(k),k为插入的数量 | 字段已存在则修改,不增加计数 |
| hget key field | 返回 Hash 中指定字段的值,字段不存在返回 nil | O(1) | 键不存在或字段不存在均返回 nil |
| hdel key field [field ...] | 删除 Hash 中的一个或多个字段,返回成功删除的字段数量 | O(k), k 是 field 个数 | 不存在的字段会被忽略 |
| hlen key | 返回 Hash 中字段的数量,键不存在返回 0 | O(1) | 键存在但非 Hash 类型会报错 |
| hmget key field [field ...] | 批量获取 Hash 中指定字段的值,按请求顺序返回,缺失返回 nil | O(k), k 是 field 个数 | 键不存在时所有字段返回 nil |
| hmset key field value [field value ...] | 批量设置 Hash 中多个字段的值,返回 OK | O(k), k 是 field 个数 | 已存在的字段会被覆盖 |
| hexists key field | 判断 Hash 中指定字段是否存在,存在返回 1,否则返回 0 | O(1) | 键不存在返回 0 |
| hkeys key | 返回 Hash 中所有字段名,键不存在返回空列表 | O(k), k 是 field 个数 | 字段较多时建议用 hscan 替代 |
| hvals key | 返回 Hash 中所有值,键不存在返回空列表 | O(k), k 是 field 个数 | 顺序与 hkeys 一致 |
| hgetall key | 返回 Hash 中所有字段和值,交替排列;键不存在返回空列表 | O(k), k 是 field 个数 | 字段较多时占用内存,建议用 hscan 替代 |
| hsetnx key field value | 仅当字段不存在时设置值,设置成功返回 1,失败返回 0 | O(1) | 键不存在会自动创建 |
| hincrby key field n | 将 Hash 中指定字段的整数值增加 n,返回增加后的值 | O(1) | 字段值必须是整数,否则报错 |
| hincrbyfloat key field n | 将 Hash 中指定字段的浮点数值增加 n,返回增加后的值 | O(1) | n 可以是整数或浮点数 |
| hstrlen key field | 返回 Hash 中指定字段值(value)的字符串长度,字段不存在返回 0 | O(1) | 键不存在或字段不存在均返回 0 |
内部编码
哈希的内部编码有 2 种:
-
ziplist(压缩列表):当哈希类型元素个数⼩于
hash-max-ziplist-entries配置(默认 512 个)、同时所有值都⼩于hash-max-ziplist-value配置(默认 64 字节)时,Redis 会使⽤ ziplist 作为哈希的内部实现,ziplist 使⽤更加紧凑的结构实现多个元素的连续存储,所以在节省内存⽅⾯⽐hashtable 更加优秀。 -
hashtable(哈希表):当哈希类型无法满足 ziplist 的条件时,Redis 会使用 hashtable 作为哈希的内部实现,因为此时 zpilist 的读写效率会下降,而 hashtable 的读写事件复杂度为 O(1)。
典型使用场景
-
关系型数据表保存用户信息: 相比于使用 JSON 格式的字符串缓存用户信息,哈希类型变得更加直观,并且在更新操作上变得更加灵活。可以将每个用户的 id 定义为键后缀,多个 field - value 对应用户的各个属性。需要注意关系型数据库表的稀疏性。
- key:
user1- uid: 1
- name: james
- age: 28
- city: beijing
- key:
user2- uid: 2
- name: johnathan
- age: 30
- city: xian
- key:
4.3 list
列表类型是用来存储多个有序的字符串,在 Redis 中,可以对列表两端插⼊(push)和弹出(pop),还可以获取指定范围的元素列表、
获取指定索引下标的元素等,列表是⼀种⽐较灵活的数据结构,它可以充当栈和队列的⻆⾊。内部结构类似 C++ 中的 dequeue(双端队列)。
| 命令 | 功能解释 | 时间复杂度 | 注意事项 |
|---|---|---|---|
| rpush key value [value ...] | 从列表右侧(尾部)插入一个或多个元素,返回插入后的列表长度 | O(k), k 是元素个数 | 键不存在则创建空列表再插入 rpushx:在 key 存在时,将一个或者多个元素从右侧放入到 list 中 |
| lpush key value [value ...] | 从列表左侧(头部)插入一个或多个元素,返回插入后的列表长度 | O(k), k 是元素个数 | 键不存在则创建空列表再插入 lpushx: 在 key 存在时,将一个或多个元素从左侧放入到 list 中 |
| linsert key before|after pivot value | 在基准元素 pivot 前或后插入新元素,返回插入后列表长度;pivot 不存在返回 -1 | O(n), n 是 pivot 距离头尾的距离 | 列表存在重复元素时仅操作第一个匹配项 |
| lrange key start end | 返回列表中指定范围内的元素,包含 start 和 end(左闭右闭);支持负数偏移 | O(s+n), s 是 start 偏移量, n 是 start 到 end 的范围 | start 大于 end 或超出范围返回空列表或自动截断(只展示合法区间的元素) |
| lindex key index | 返回列表中指定索引位置的元素,索引不存在返回 nil | O(n), n 是索引的偏移量 | 支持负数索引(-1 表示最后一个元素) |
| llen key | 返回列表的长度,键不存在返回 0 | O(1) | 键存在但非列表类型会报错 |
| lpop key | 移除并返回列表左侧(头部)的第一个元素,列表为空或不存在返回 nil | O(1) | Redis 6.2+ 后支持 count 参数批量弹出 |
| rpop key | 移除并返回列表右侧(尾部)的第一个元素,列表为空或不存在返回 nil | O(1) | Redis 6.2+ 后支持 count 参数批量弹出 |
| lrem key count value | 移除列表中值为 value 的元素;count > 0 从头部删,count < 0 从尾部删,count = 0 删全部;返回移除数量 | O(k), k 是元素个数 | 只删除精确匹配的元素,不是按索引删除 |
| ltrim key start end | 只保留列表中指定范围内的元素,其余删除;返回 OK | O(k), k 是元素个数 | start 大于 end 或超出范围会清空列表 |
| lset key index value | 将列表中指定索引位置的元素值修改为新值,成功返回 OK | O(n), n 是索引的偏移量 | 索引必须存在,否则报错 |
| blpop key [key ...] timeout | 从多个列表左侧阻塞弹出元素,超时返回 nil;有元素时立即返回 | O(1) | timeout 单位秒,0 表示永久阻塞 |
| brpop key [key ...] timeout | 从多个列表右侧阻塞弹出元素,超时返回 nil;有元素时立即返回 | O(1) | 多个 key 时按顺序检查,建议配合 rpush 使用 |
阻塞版本命令 :blpop和brpop
-
在列表中有元素的情况下,阻塞和⾮阻塞表现是⼀致的。但如果列表中没有元素,⾮阻塞版本会理解返回 nil,但阻塞版本会根据 timeout,阻塞⼀段时间,期间 Redis 可以执⾏其他命令,但要求执⾏该命令的客⼾端会表现为阻塞状态。
-
命令中如果设置了多个键,那么会从左向右进⾏遍历键,⼀旦有⼀个键对应的列表中可以弹出元素,命令⽴即返回。
-
如果多个客⼾端同时执行 blpop,则最先执⾏命令的客⼾端会得到弹出的元素。
内部编码
列表的内部编码只有 1 种:
- quicklist: 是 ziplist(压缩列表) 和 linkedlist(链表) 的结合体。每一个列表节点都是一个压缩列表,默认情况下,每个链表节点下存储 2 个压缩列表,可以根据配置文件调整。
典型实用场景
-
栈: 同侧存取(lpush + lpop 或者 rpush + rpop)为栈。
-
队列: 异侧存取(lpush + rpop 或者 rpush + lpop)为队列。
4.4 set
集合类型也是保存多个字符串类型的元素的,但和列表类型不同的是,集合中 1)元素之间是⽆序的 2)元素不允许重复。Redis 除了⽀持
集合内的增删查改操作,同时还⽀持多个集合取交集、并集、差集。
| 命令 | 功能解释 | 时间复杂度 | 注意事项 |
|---|---|---|---|
| sadd key element [element ...] | 向集合中添加一个或多个元素,返回成功添加的元素数量(重复元素无法添加到 set 中) | O(k), k 是元素个数 | 集合不存在会自动创建;元素已存在则忽略 |
| srem key element [element ...] | 从集合中移除一个或多个元素,返回成功移除的元素数量 | O(k), k 是元素个数 | 不存在的元素会被忽略 |
| scard key | 返回集合中元素的数量,键不存在返回 0 | O(1) | 键存在但非集合类型会报错 |
| sismember key element | 判断元素是否在集合中,存在返回 1,否则返回 0 | O(1) | 键不存在返回 0 |
| srandmember key [count] | 随机返回集合中 count 个元素(不删除);count 为正时不重复,为负时可重复 | O(n), n 是 count | count 绝对值大于集合大小时,正数返回全部,负数按 count 绝对值重复 |
| spop key [count] | 随机移除并返回集合中 count 个元素,默认 count=1 | O(n), n 是 count | 返回被移除的元素;集合为空返回 nil |
| smove source destination member | 将一个元素从源 set 取出并放入目标 set 中;移动成功返回1,0表示失败 | 时间复杂度 O(1) | |
| smembers key | 返回集合中所有元素,顺序不保证 | O(k), k 是元素个数 | 元素较多时占用内存,建议用 sscan 替代 |
| sinter key [key ...] | 返回多个集合的交集,结果不存储 | O(m * k), k 是元素最小的集合大小,m 是键个数 | 任一键不存在视为空集,交集结果为空 |
| sinterstore destination key [key ...] | 计算多个集合的交集并存储到 destination,返回结果元素数量 | O(m * k), k 是元素最小的集合大小,m 是键个数 | destination 已存在会被覆盖 |
| sunion key [key ...] | 返回多个集合的并集,结果不存储 | O(k), k 是多个集合的元素个数总和 | 键不存在视为空集 |
| sunionstore destination key [key ...] | 计算多个集合的并集并存储到 destination,返回结果元素数量 | O(k), k 是多个集合的元素个数总和 | destination 已存在会被覆盖 |
| sdiff key [key ...] | 返回第一个集合与其他集合的差集,结果不存储 | O(k), k 是多个集合的元素个数总和 | 键不存在视为空集 |
| sdiffstore destination key [key ...] | 计算第一个集合与其他集合的差集并存储到 destination,返回结果元素数量 | O(k), k 是多个集合的元素个数总和 | destination 已存在会被覆盖 |
内部编码
集合类型的内部编码有 2 种:
-
intset(整数集合):当集合中的元素都是整数并且元素的个数小于
set-max-intset-entries(默认512个)时,Redis会选用 intset 作为集合的内部实现,从而减少内存的使用。 -
hashtable(哈希表):当集合类型无法满足 intset 的条件时,Redis 会使用 hashtable 作为集合的内部实现。
典型使用场景
- 一些需要去重或者求交集并集的场景。
4.5 zset
有序集合 相对于字符串、列表、哈希、集合来说会有⼀些陌⽣。它保留了集合不能有重复成员的特点,但与集合不同的是,有序集合中的每个元素都有⼀个唯⼀的浮点类型的分数(score)与之关联 ,着使得有序集合中的元素是可以维护有序性的,但这个有序不是⽤下标作为排序依据⽽是⽤这个分数。分数可以使用 +inf / -inf。
有序集合提供了获取指定分数和元素范围查找、计算成员排名等功能。
| 命令 | 功能解释 | 时间复杂度 | 注意事项 |
|---|---|---|---|
| zadd key [NX|XX] [CH] [INCR] score member [score member ...] | 向有序集合添加一个或多个成员,返回成功添加的成员数量 | O(k * log(n)), k 是添加成员的个数, n 是当前有序集合的元素个数 | 支持 NX(只添加)/XX(只更新)/CH(返回变更总数)/INCR(增加分数)等选项 |
| zcard key | 返回有序集合的成员数量,键不存在返回 0 | O(1) | 键存在但非有序集合类型会报错 |
| zscore key member | 返回指定成员的分数值,成员不存在返回 nil | O(1) | 分数以字符串形式返回,保留精度 |
| zrank key member | 返回指定成员的排名(从小到大,0 开始),成员不存在返回 nil | O(log(n)), n 是当前有序集合的元素个数 | 可用于判断成员是否存在及获取位置 |
| zrevrank key member | 返回指定成员的排名(从大到小,0 开始),成员不存在返回 nil | O(log(n)), n 是当前有序集合的元素个数 | 与 zrank 相反顺序 |
| zrem key member [member ...] | 移除一个或多个成员,返回成功移除的成员数量 | O(k * log(n)), k 是删除成员的个数, n 是当前有序集合的元素个数 | 不存在的成员会被忽略 |
| zpopmax key [count] | 移除并返回有序集合中分数最高的 count 个成员(默认 1),返回分数和元素列表 | O(log(n) * k), k 是移除的成员个数, n 是当前有序集合的元素个数 | Redis 5.0+ 支持;成员较多时性能较好 |
| zpopmin key [count] | 移除并返回有序集合中分数最低的 count 个成员(默认 1),返回分数和元素列表 | O(log(n) * k), k 是移除的成员个数, n 是当前有序集合的元素个数 | Redis 5.0+ 支持;成员较多时性能较好 |
| zincrby key increment member | 为指定成员的分数增加 increment,返回增加后的分数;成员不存在则添加 | O(log(n)), n 是当前有序集合的元素个数 | increment 可为整数或浮点数,支持负数 |
| zrange key start end [WITHSCORES] | 按排名从小到大返回指定范围内的成员,可同时返回分数 | O(k + log(n)), k 是获取成员的个数, n 是当前有序集合的元素个数 | start 和 end 支持负数索引 |
| zrevrange key start end [WITHSCORES] | 按排名从大到小返回指定范围内的成员,可同时返回分数 | O(k + log(n)), k 是获取成员的个数, n 是当前有序集合的元素个数 | 与 zrange 相反顺序,这个命令可能在 6.2 之后废弃,并且功能合并到 zrange 上 |
| zrangebyscore key min max [WITHSCORES] [LIMIT offset count] | 按分数从小到大返回分数在 min 和 max 之间的成员(包含 min 和 max),可以通过 ( 排除 |
O(k + log(n)), k 是获取成员的个数, n 是当前有序集合的元素个数 | min 和 max 支持 -inf、+inf 及开区间语法 |
| zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count] | 按分数从大到小返回分数在 max 和 min 之间的成员 | O(k + log(n)), k 是获取成员的个数, n 是当前有序集合的元素个数 | 参数顺序与 zrangebyscore 相反 |
| zcount key min max | 返回分数在 min 和 max 范围内的成员数量(闭区间),可以通过 ( 排除 |
O(log(n)), n 是当前有序集合的元素个数 | min 和 max 支持 -inf、+inf |
| zremrangebyrank key start end | 移除排名在指定范围内的所有成员(闭区间),返回移除的成员数量 | O(k + log(n)), k 是获取成员的个数, n 是当前有序集合的元素个数 | 按排名(从小到大)删除 |
| zremrangebyscore key min max | 移除分数在指定范围内的所有成员(闭区间),返回移除的成员数量 | O(k + log(n)), k 是获取成员的个数, n 是当前有序集合的元素个数 | 按分数范围删除 |
| zinterstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX] | 计算多个有序集合的交集并存储到 destination,返回结果成员数量 | O(n * k) + O(m * log(m)), n 是输入的集合最小的元素个数, k 是集合个数, m 是目标集合元素个数 | 支持权重和聚合方式(权重就会给当前集合中所有的的分数乘上该权重值,聚合方式默认求出交集分数求和),目标集合已存在会被覆盖 |
| zunionstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX] | 计算多个有序集合的并集并存储到 destination,返回结果成员数量 | O(n) + O(m * log(m)), n 是输入集合总元素个数, m 是目标集合元素个数 | 支持权重和聚合方式(权重就会给当前集合中所有的的分数乘上该权重值,聚合方式默认求出交集分数求和),目标集合已存在会被覆盖 |
内部编码
有序集合类型的内部编码有 2 种:
-
ziplist(压缩列表):当有序集合的元素个数⼩于
zset-max-ziplist-entries配置(默认 128 个),同时每个元素的值都小于zset-max-ziplist-value配置(默认 64 字节)时,Redis 会⽤ ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使⽤。 -
skiplist(跳表):当 ziplist 条件不满足时,有序集合会使⽤ skiplist 作为内部实现,因为此时ziplist 的操作效率会下降。
使用场景
- 有序集合⽐较典型的使⽤场景就是排⾏榜系统。例如常⻅的⽹站上的热榜信息,榜单的维度可能是多⽅⾯的:按照时间、按照阅读量、按照点赞量。
4.6 scan
在 Redis 中,渐进式遍历 主要指通过 SCAN 命令及其相关命令(SSCAN、HSCAN、ZSCAN)来遍历键空间或数据结构内部元素的方式。
它与传统的 KEYS 或 SMEMBERS 等命令不同,不会阻塞服务器长时间,而是分多次、逐步地遍历数据。
以下是关于 Redis 渐进式遍历的详细解析:
为什么需要渐进式遍历?
在 Redis 中,如果我们使用 KEYS * 这样的命令来查找键,当数据库中键的数量非常大(例如数百万个)时,Redis 需要阻塞单线程去匹配所有键。在阻塞期间,Redis 无法处理任何其他命令,会导致服务卡顿。
为了解决这个问题,Redis 提供了 SCAN 系列命令。它每次只返回一小部分数据,并允许用户通过多次调用来完成全量遍历。
游标与状态
渐进式遍历的核心是一个游标的概念。
-
第一次调用:用户传入游标 0。
-
服务器返回:服务器返回两个值:
-
新的游标:用于下一次遍历的起点。
-
数据集合:本次遍历到的一批元素。
-
-
继续遍历:用户拿着返回的新游标,继续调用命令。
-
遍历结束:当服务器返回的游标为
0时,表示整个遍历过程结束。
这个过程维护了遍历的状态在服务器端,但状态信息通过游标传递给客户端,所以服务器不需要为每个客户端维护庞大的遍历状态。
主要的命令
渐进式遍历不仅仅针对整个数据库的键,也针对各种数据结构的内部元素:
-
SCAN:遍历当前数据库中的所有 Key。 -
SSCAN:遍历 Set 集合中的元素。 -
HSCAN:遍历 Hash 结构中的字段和值。 -
ZSCAN:遍历 Sorted Set 中的成员和分数。
基本语法
redis
SCAN cursor [MATCH pattern] [COUNT count]
SSCAN key cursor [MATCH pattern] [COUNT count]
HSCAN key cursor [MATCH pattern] [COUNT count]
ZSCAN key cursor [MATCH pattern] [COUNT count]
-
cursor:本次遍历的游标。 -
MATCH:可选,提供模式匹配(类似于 KEYS 的匹配,当没有显示的设置 match,默认为*)。 -
COUNT:可选,向 Redis 提示期望返回的条数(注意 :只是提示,实际返回可能多于或少于这个数,默认为10)。
渐进式遍历的重要特点
-
不会阻塞服务器。
-
重复与遗漏的可能性:
由于 Redis 的遍历是基于哈希槽(hash slot)的,且在遍历过程中如果有数据变化(增删改),可能会发生:
-
重复返回:同一个元素可能在多次遍历中被返回(尤其是当哈希表在扩容或缩容时,元素可能会从旧槽移动到新槽,导致被扫描两次)。
-
遗漏:理论上在特定条件下,如果元素在扫描到该槽位后被移动到未扫描的槽位,可能会遗漏。
结论:SCAN 命令提供的是概率保证,它能保证遍历的完整性,但不能保证在遍历过程中不变的数据集合不出现重复或少量的遗漏。
- count 参数知识一个提示。
COUNT默认是 10。但这并不意味着每次一定返回 10 条。
- 状态由游标维持。
-
服务器的内存中并不保存客户端的遍历状态,状态信息被打包在游标中由客户端持有。因此,即使客户端断开重连,只要知道游标值,就可以继续遍历。
-
游标并非是一个单调递增的整数,它只是一个用于标记位置的点。
使用方式
bash
# 第一次遍历,游标为 0
127.0.0.1:6379> SCAN 0 MATCH user:* COUNT 20
1) "17" # 下一次要使用的游标
2) 1) "user:1001"
2) "user:1003"
3) "user:2002"
# 拿着返回的游标继续
127.0.0.1:6379> SCAN 17 MATCH user:* COUNT 20
1) "0" # 返回 0,表示遍历完成
2) 1) "user:3005"
2) "user:4011"
五、Redis的数据库管理
许多关系型数据库,例如 MySQL ⽀持在⼀个实例下有多个数据库存在的,但是与关系型数据库⽤字符来区分不同数据库名不同,Redis 只是⽤数字作为多个数据库的实现。Redis 默认配置中是有 16 个数据库。select 0 操作会切换到第⼀个数据库,select 15 会切换到最后⼀个数据库。0 号数据库和 15 号数据库保存的数据是完全不冲突的,即各种有各⾃的键值对。默认情况下,我
们处于数据库 0。
切换数据库
bash
select dbindex
❗Redis 中虽然⽀持多数据库,但随着版本的升级,其实不是特别建议使⽤多数据库特性。如果真的需要完全隔离的两套键值对,更好的做法是维护多个 Redis 实例,⽽不是在⼀个Redis 实例中维护多数据库。
查看当前数据库有多少个key
bash
dbsize
❗️
flushdb/flushall命令⽤于清除数据库,区别在于 flushdb 只清除当前数据库,flushall 会清楚所有数据库。
六、redis-plus-plus
redis-plus-plus 是通过 C++ 访问 redis 服务器的客户端程序。
6.1 redis-plus-plus的安装
redis-plus-plus是基于 hiredis 实现的。hiredis 是一个 C 语言实现的 redis 客户端。因此安装 hiredis,直接使用包管理器安装即可。
ubuntu
bash
sudo apt install -y libhiredis-dev
下载 redis-plus-plus 源码
bash
git clone https://github.com/sewenew/redis-plus-plus.git
编译/安装 redis-plus-plus
bash
cd redis-plus-plus
mkdir build
cd build
cmake ..
make
sudo make install # 将库安装到系统目录下,方便后续使用
6.2 redis-plus-plus的使用
编译程序的时候,需要引入库文件。
- redis++ 的库
- hiredis 的库
- 线程库
bash
# 查找 redis-plus++ 的库文件
sudo find /usr -name "libredis*"
/usr/local/lib/libredis++.so
# 查找 hiredis 的库文件
sudo find /usr -name "libhiredis*"
/usr/lib/x86_64-linux-gnu/libhiredis.so
一般安装库后,默认的头文件都在 /usr/local/include ,而该路径是 g++ 的默认搜索路径 ,所以不需要 -I 指定。
库文件的默认搜索路径
bash
# g++ 默认的库搜索路径
# /usr/lib/x86_64-linux-gnu
# /usr/lib
# /lib
makefile:
makefile
test-redis-plus-plus: redis-plus-plus.cc
g++ -std=c++17 -o $@ $^ -L/usr/lib/x86_64-linux-gnu -L/usr/local/lib -lredis++ -lhiredis -pthread
.PHONY:clean
clean:
rm -rf test-redis-plus-plus
- 以上不需要 -L 也可以编译通过,首先
/usr/lib/x86_64-linux-gnu已经在 g++ 默认的搜索路径中,而/usr/local/lib也可能会因为一些特殊的配置,g++ 也会搜索。
以上 makefile 可以编译通过,但是在运行时会报错,操作系统在运行程序时找不到该依赖的动态库。
更新 /etc/ld.so.conf.d
bash
# 查看 /etc/ld.so.conf.d 有哪些文件
sudo ls /etc/ld.so.conf.d/
fakeroot-x86_64-linux-gnu.conf x86_64-linux-gnu.conf libc.conf
# 查看 x86_64-linux-gnu.conf 内容
sudo cat /etc/ld.so.conf.d/x86_64-linux-gnu.conf
/usr/local/lib/x86_64-linux-gnu
/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu
# 可以发现该文件中已经包含了 /usr/lib/x86_64-linux-gnu 我们只需添加 /usr/local/lib 即可
sudo vim /etc/ld.so.conf.d/usr-local-lib.conf
# 添加绝对路径 /usr/local/lib
sudo cat /etc/ld.so.conf.d/usr-local-lib.conf
/usr/local/lib
# 更新配置
sudo ldconfig
基本使用
cpp
#include <iostream>
#include <string>
#include <sw/redis++/redis++.h>
int main() {
sw::redis::Redis redis("tcp://127.0.0.1:6379");
std::string result = redis.ping();
std::cout << result << std::endl;
return 0;
}
输出结果
bash
PONG