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
相关推荐
Rick199334 分钟前
Redis 分布式锁 + 部署模式
redis·分布式
NiceCloud喜云8 小时前
Opus 4.8 的 Effort Control 怎么选:Low 到 Max 五档策略
android·java·大数据·前端·c++·python·spring
cjhbachelor8 小时前
c++继承
c++
肩上风骋8 小时前
C++14特性
开发语言·c++·c++14特性
程序员老邢9 小时前
《技术底稿 43》今日踩坑复盘:Redis 乱码 + MySQL 配置注入失败
redis·技术底稿·redisson 序列化·mysql 配置·项目踩坑·微服务问题排查
Mr. zhihao11 小时前
Redis五大高级数据结构:原理-场景-底层-横向对比
数据结构·redis
QiLinkOS11 小时前
【从实验室到商业战场:发明专利如何重塑科技与企业的共生生态】
大数据·c语言·数据结构·c++·人工智能·单片机·算法
Irissgwe12 小时前
c++11(lambda表达式与包装器、线程库)
c++·c++11·lambda表达式·线程库·包装器·互斥量库·条件变量库
Peter·Pan爱编程13 小时前
14. Lambda 表达式:随手可写的函数对象
c++·算法·ai编程
不想写代码的星星13 小时前
从分支预测角度看 C++:为什么你的热循环慢得离谱?
c++