Redis基础

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 , hallohxllo

  • h*llo 匹配 hlloheeeello

  • h[ae]llo 匹配 hellohallo 但不匹配 hillo

  • h[^e]llo 匹配 hallo , hbllo , ... 但不匹配 hello

  • h[a-b]llo 匹配 hallohbllo

  • 显示 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

返回值:nonestringlistsetzsethashstream 等。

2.9 flushall

删除当前 redis 中所有数据库的所有键。

redis 复制代码
flushall

三、Redis数据类型和内部编码

Redis 常用的五种数据类型:

  1. String(字符串)

  2. Hash(哈希)

  3. List(列表)

  4. Set(集合)

  5. 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

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 使用

阻塞版本命令blpopbrpop

  • 在列表中有元素的情况下,阻塞和⾮阻塞表现是⼀致的。但如果列表中没有元素,⾮阻塞版本会理解返回 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。

  • 服务器返回:服务器返回两个值:

    1. 新的游标:用于下一次遍历的起点。

    2. 数据集合:本次遍历到的一批元素。

  • 继续遍历:用户拿着返回的新游标,继续调用命令。

  • 遍历结束:当服务器返回的游标为 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)。

渐进式遍历的重要特点

  1. 不会阻塞服务器。

  2. 重复与遗漏的可能性:

由于 Redis 的遍历是基于哈希槽(hash slot)的,且在遍历过程中如果有数据变化(增删改),可能会发生:

  • 重复返回:同一个元素可能在多次遍历中被返回(尤其是当哈希表在扩容或缩容时,元素可能会从旧槽移动到新槽,导致被扫描两次)。

  • 遗漏:理论上在特定条件下,如果元素在扫描到该槽位后被移动到未扫描的槽位,可能会遗漏。

结论:SCAN 命令提供的是概率保证,它能保证遍历的完整性,但不能保证在遍历过程中不变的数据集合不出现重复或少量的遗漏。

  1. count 参数知识一个提示。
  • COUNT 默认是 10。但这并不意味着每次一定返回 10 条。
  1. 状态由游标维持。
  • 服务器的内存中并不保存客户端的遍历状态,状态信息被打包在游标中由客户端持有。因此,即使客户端断开重连,只要知道游标值,就可以继续遍历。

  • 游标并非是一个单调递增的整数,它只是一个用于标记位置的点。

使用方式

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的使用

编译程序的时候,需要引入库文件。

  1. redis++ 的库
  2. hiredis 的库
  3. 线程库
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
相关推荐
only-qi2 小时前
从单机到集群:Redis 高可用演进之路(深度解析主从、哨兵、Twemproxy、Codis 与 Redis Cluster)
redis·redis集群
D_evil__2 小时前
【Effective Modern C++】第五章 右值引用、移动语义和完美转发:28. 理解引用折叠
c++
enjoy嚣士2 小时前
Java 之 实现C++库函数等价函数遇到的问题
java·开发语言·c++
tod1133 小时前
Redis 持久化机制深度解析(RDB / AOF)
数据库·redis·缓存
Ivanqhz3 小时前
半格与数据流分析的五个要素(D、V、F、I、Λ)
开发语言·c++·后端·算法·rust
元让_vincent3 小时前
DailyCoding C++ | SLAM里的“幽灵数据”:从一个未初始化的四元数谈C++类设计
开发语言·c++·slam·构造函数·类设计·激光里程计
A9better4 小时前
C++——指针与内存
c语言·开发语言·c++·学习
今儿敲了吗5 小时前
18| 差分数组
c++·笔记·学习·算法
浅念-5 小时前
C++ 模板初阶:从泛型编程到函数模板与类模板
c语言·开发语言·数据结构·c++·笔记·学习