当前位置: 首页 > article >正文

Redis 常见数据类型

官方文档 RedisCommands

1)Redis 的命令有上百个,如果纯靠死记硬背比较困难,但是如果理解 Redis 的一些机制,会发现这些命令有很强的通用性。

2)Redis 不是万金油,有些数据结构和命令必须在特定场景下使用,一旦使用不当可能对 Redis 本身或者应用本身造成致命伤害。

基本全局命令

Redis 有 5 种数据结构,但它们都是键值对种的值,对于键来说有一些通用的命令。

KEYS

返回所有满足样式(pattern)的 key。支持如下统配样式。

• h?llo 匹配 hello , hallo 和 hxllo (匹配任意一个字符)

• h*llo 匹配 hllo 和heeeello (匹配0个或多个字符)

• h[ae]llo 匹配hello 和hallo 但不匹配 hillo (只匹配a字符和e字符)

• h[^e]llo 匹配hallo , hbllo , ... 但不匹配 hello (只匹配非e字符的其他字符)

• h[a-b]llo 匹配hallo 和 hbllo (匹配a-b之间的所有字符)

语法:

KEYS pattern

命令有效版本:1.0.0 之后

时间复杂度:O(N)

返回值:匹配 pattern 的所有 key。

EXISTS

判断某个 key 是否存在。

语法:

EXISTS key [key ...]

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:key 存在的个数。

DEL

删除指定的 key。

语法:

DEL key [key ...]

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:删除掉的 key 的个数。

EXPIRE

为指定的 key 添加秒级的过期时间(Time To Live TTL)

语法:

EXPIRE key seconds

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:1 表示设置成功。0 表示设置失败。

TTL

获取指定 key 的过期时间,秒级。

TTL key

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:剩余过期时间。-1 表示没有关联过期时间,-2 表示 key 不存在。

💡 EXPIRE 和 TTL 命令都有对应的支持毫秒为单位的版本:PEXPIRE 和 PTTL


键的过期机制

TYPE

返回 key 对应的数据类型。

语法:

TYPE key

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值: none , string , list , set , zset , hash and stream .。

数据结构和内部编码

type 命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)、list(列表)、hash(哈希)、set(集合)、zset(有序集合),但这些只是 Redis 对外的数据结构。

实际上 Redis 针对每种数据结构都有自己的底层内部编码实现,而且是多种实现,这样 Redis 会在合适的场景选择合适的内部编码

数据结构

内部编码

string

raw(最基本的字符串)

int(计数)

embstr(针对段字符串进行的特殊优化)

hash

hashtable(哈希表)

ziplist(压缩列表,在哈希表里面元素比较少的时候,可能就优化成ziplist)

list

linkedlist(链表)

ziplist(压缩列表)

set

hashtable(哈希表)

intset(集合中存的都是整数)

zset

skiplist(跳表)

ziplist(压缩列表)

可以看到每种数据结构都有至少两种以上的内部编码实现,例如 list 数据结构包含了 linkedlist 和ziplist 两种内部编码。同时有些内部编码,例如 ziplist,可以作为多种数据结构的内部实现,可以通过 object encoding 命令查询内部编码:

String字符串

字符串类型是 Redis 最基础的数据类型,关于字符串需要特别注意:

1)首先 Redis 中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的,例如列表和集合的元素类型是字符串类型,所以字符串类型能为其他 4 种数据结构的学习奠定基础。

2)字符串类型的值实际可以是字符串,包含一般格式的字符串或者类似 JSON、XML 格式的字符串;数字,可以是整型或者浮点型;甚至是二进制流数据,例如图片、音频、视频等。不过一个字符串的最大值不能超过 512 MB。

由于 Redis 内部存储字符串完全是按照二进制流的形式保存的,所以 Redis 是不处理字符集编码问题的,客户端传入的命令中使用的是什么字符集编码,就存储什么字符集编码

典型使用场景

缓存(Cache)功能

Redis 作为缓冲层,MySQL 作为存储层,绝大部分请求的数据都是从 Redis 中获取。由于 Redis 具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。

Redis + MySQL 组成的缓存存储架构

  1. 假设业务是根据用户uid获取用户信息
UserInfo getUserInfo(long uid)
  1. 首先从 Redis 获取用户信息,我们假设用户信息保存在 "user:info:<uid>" 对应的键中:
// 根据 uid 得到 Redis 的键
String key = "user:info:" + uid;
// 尝试从 Redis 中获取对应的值
String value = Redis 执行命令:get key;
// 如果缓存命中(hit)
if (value != null) {
// 假设我们的用户信息按照 JSON 格式存储
UserInfo userInfo = JSON 反序列化(value);
return userInfo;
}
  1. 如果没有从 Redis 中得到用户信息,及缓存 miss,则进一步从 MySQL 中获取对应的信息,随后写入缓存并返回:
// 如果缓存未命中(miss)
if (value == null) {
// 从数据库中,根据 uid 获取用户信息
UserInfo userInfo = MySQL 执行 SQL:select * from user_info where uid =
<uid>
// 如果表中没有 uid 对应的用户信息
if (userInfo == null) {
响应 404
return null;
}
// 将用户信息序列化成 JSON 格式
String value = JSON 序列化(userInfo);
// 写入缓存,为了防止数据腐烂(rot),设置过期时间为 1 小时(3600 秒)
Redis 执行命令:set key value ex 3600
// 返回用户信息
return userInfo;
}

通过增加缓存功能,在理想情况下,每个用户信息,一个小时期间只会有一次 MySQL 查询,极大地提升了查询效率,也降低了 MySQL 的访问数。

计数(Counter)功能

许多应用都会使用 Redis 作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。例如视频网站的视频播放次数可以使用Redis 来完成:用户每播放一次视频,相应的视频播放数就会自增 1。

// 在 Redis 中统计某视频的播放次数
long incrVideoCounter(long vid) {
    key = "video:" + vid;
    long count = Redis 执行命令:incr key
    return counter;
}
// 实际中要开发一个成熟、稳定的真实计数系统,要面临的挑战远不止如此简单:防作弊、按
// 照不同维度计数、避免单点问题、数据持久化到底层数据源等。

共享会话(Session)

一个分布式 Web 服务将用户的 Session 信息(例如用户登录信息)保存在各自的服务器中,但这样会造成一个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,这样当用户刷新一次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。

Session 分散存储

为了解决这个问题,可以使用 Redis 将用户的 Session 信息进行集中管理,如图 2-13 所示,在这种模式下,只要保证 Redis 是高可用和可扩展性的,无论用户被均衡到哪台 Web 服务器上,都集中从Redis 中查询、更新 Session 信息。

Redis 集中管理 Session

手机验证码

很多应用出于安全考虑,会在每次进行登录时,让用户输入手机号并且配合给手机发送验证码,然后让用户再次输入收到的验证码并进行验证,从而确定是否是用户本人。为了短信接口不会频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过 5 次。

短信验证码

此功能可以用以下伪代码说明基本实现思路:

String 发送验证码(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 位数的验证码();
    validationKey = "validation:" + phoneNumber;
    // 验证码 5 分钟(300 秒)内有效
    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;
    }
}

常见命令

SET

将 string 类型的 value 设置到 key 中。如果 key 之前存在,则覆盖,无论原来的数据类型是什么。之前关于此 key 的 TTL (获取指定key的过期时间)也全部失效。

SET key value [expiration EX seconds|PX milliseconds] 1 [NX|XX]
set key value ex 10 相当于 set key value expire key 10
// 但是单个命令是原子的,保证这条语句是在同一时间执行的,分开写不能保证语句是同一时间执行的

命令有效版本:1.0.0 之后

时间复杂度:O(1)

SET 命令支持多种选项来影响它的行为:

• EX seconds —— 使用秒作为单位设置 key 的过期时间。

• PX milliseconds —— 使用毫秒作为单位设置 key 的过期时间。

• NX —— 只在 key 不存在时才进行设置,即如果 key 之前已经存在,设置不执行。

• XX —— 只在 key 存在时才进行设置,即如果 key 之前不存在,设置不执行。

注意:由于带选项的 SET 命令可以被 SETNX 、SETEX 、PSETEX 等命令代替,所以之后的版本中,Redis 可能进行合并。

返回值:

• 如果设置成功,返回 OK。

• 如果由于 SET 指定了 NX 或者 XX 但条件不满足,SET 不会执行,并返回 (nil)。

GET

获取 key 对应的 value。如果 key 不存在,返回 nil。如果 value 的数据类型不是 string,会报错。

语法:

GET key

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:key 对应的 value,或者 nil 当 key 不存在。

MGET和MSET

MGET 一次性获取多个 key 的值。如果对应的 key 不存在或者对应的数据类型不是 string,返回 nil。

MSET 一次性设置多个 key 的值。

MGET key [key ...]
MSET key value [key value ...]

命令有效版本:1.0.0 之后

时间复杂度:O(N) N 是 key 数量

MGET 返回值:对应 value 的列表

MSET返回值:永远是 OK


多次get和单词mget

使用 mget / mset 由于可以有效地减少了网络时间,所以性能相较更高。会使用批量操作,可以有效提高业务处理效率,但是要注意,每次批量操作所发送的键的数量也不是无节制的,否则可能造成单一命令执行时间过长,导致 Redis 阻塞。

SETNX

设置 key-value 但只允许在 key 之前不存在的情况下。

SETNX key value

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:1 表示设置成功。0 表示没有设置。

计数命令

INCR

将 key 对应的 string 表示的数字加一。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。

INCR key

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:integer 类型的加完后的数值。

INCRBY

将 key 对应的 string 表示的数字加上对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。

INCRBY key decrement

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:integer 类型的加完后的数值。

DECR

将 key 对应的 string 表示的数字减一。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。

DECR key

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:integer 类型的减完后的数值。

DECYBY

将 key 对应的 string 表示的数字减去对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是一个整型或者范围超过了 64 位有符号整型,则报错。/dmk

DECRBY key decrement

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:integer 类型的减完后的数值。

INCRBYFLOAT

将 key 对应的 string 表示的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的不是 string,或者不是一个浮点数,则报错。允许采用科学计数法表示浮点数。

INCRBYFLOAT key increment

命令有效版本:2.6.0 之后

时间复杂度:O(1)

返回值:加/减完后的数值。

很多存储系统和编程语言内部使用 CAS 机制实现计数功能,会有一定的 CPU 开销,但在 Redis 中完全不存在这个问题,因为 Redis 是单线程架构,任何命令到了 Redis 服务端都要顺序执行。

其他命令

APPEND

如果 key 已经存在并且是一个 string,命令会将 value 追加到原有 string 的后边。如果 key 不存在,则效果等同于 SET 命令。

APPEND KEY VALUE

命令有效版本:2.0.0 之后

时间复杂度:O(1). 追加的字符串一般长度较短, 可以视为 O(1).

返回值:追加完成之后 string 的长度。

GETRANGE

返回 key 对应的 string 的子串,由 start 和 end 确定(左闭右闭)。可以使用负数表示倒数。-1 代表倒数第一个字符,-2 代表倒数第二个,其他的与此类似。超过范围的偏移量会根据 string 的长度调整成正确的值。

GETRANGE key start end

命令有效版本:2.4.0 之后

时间复杂度:O(N). N 为 [start, end] 区间的长度. 由于 string 通常比较短, 可以视为是 O(1)

返回值:string 类型的子串

SETRANGE

覆盖字符串的一部分,从指定的偏移开始。

SETRANGE key offset value

命令有效版本:2.2.0 之后

时间复杂度:O(N), N 为 value 的长度. 由于一般给的 value 比较短, 通常视为 O(1).

返回值:替换后的 string 的长度。

STRLEN

获取 key 对应的 string 的长度。当 key 存放的类似不是 string 时,报错。

STRLEN key

命令有效版本:2.2.0 之后

时间复杂度:O(1)

返回值:string 的长度。或者当 key 不存在时,返回 0。

字符串类型命令

Hash哈希

几乎所有的主流编程语言都提供了哈希(hash)类型,它们的叫法可能是哈希、字典、关联数组、映射。在 Redis 中,哈希类型是指值本身又是一个键值对结构,形如 key = "key",value = { {field1, value1 }, ..., {fieldN, valueN } }

字符串和哈希类型对比

哈希类型中的映射关系通常称为 field-value,用于区分 Redis 整体的键值对(key-value),注意这里的 value 是指 field 对应的值,不是键(key)对应的值,请注意 value 在不同上下文的作用。

命令

HSET/HGET

HSET key field value [field value ...]
HGET key field

HEXISTS

判断 hash 中是否有指定的字段。

HEXISTS key field

命令有效版本:2.0.0 之后

时间复杂度:O(1)

返回值:1 表示存在,0 表示不存在。

HDEL

删除 hash 中指定的字段。

HDEL key field [field ...]

命令有效版本:2.0.0 之后

时间复杂度:删除一个元素为 O(1). 删除 N 个元素为 O(N).

返回值:本次操作删除的字段个数。

HKEYS

获取hash中的所有字段

HKEYS key

命令有效版本:2.0.0 之后

时间复杂度:O(N), N 为 field 的个数.

返回值:字段列表。

HVALS

获取hash中所有的值

HVALS key

命令有效版本:2.0.0 之后

时间复杂度:O(N), N 为 field 的个数.

返回值:所有的值。

HGETALL

获取hash中所有字段以及对应的值

HGETALL key

命令有效版本:2.0.0 之后

时间复杂度:O(N), N 为 field 的个数.

返回值:字段和对应的值。

HMGET

一次获取hash中多个字段

HMGET key field [field ...]

命令有效版本:2.0.0 之后

时间复杂度:只查询一个元素为 O(1), 查询多个元素为 O(N), N 为查询元素个数.

返回值:字段对应的值或者 nil。

在使用 HGETALL 时,如果哈希元素个数比较多,会存在阻塞 Redis 的可能。如果开发人员只需要获取部分 field,可以使用 HMGET,如果一定要获取全部 field,可以尝试使用 HSCAN命令,该命令采用渐进式遍历哈希类型

HLEN

获取hash中所有字段的个数

HLEN key

命令有效版本:2.0.0 之后

时间复杂度:O(1)

返回值:字段个数。

HSETNX

在字段不存在的情况下,设置 hash 中的字段和值。

HSETNX key field value

命令有效版本:2.0.0 之后

时间复杂度:O(1)

返回值:1 表示设置成功,0 表示失败。

HINCRBY

将 hash 中字段对应的数值添加指定的值。

HINCRBY key field increment

命令有效版本:2.0.0 之后

时间复杂度:O(1)

返回值:该字段变化之后的值。

HINCRBYFLOAT

HINCRBY 的浮点数版本。

HINCRBYFLOAT key field increment

命令有效版本:2.6.0 之后

时间复杂度:O(1)

返回值:该字段变化之后的值。

内部编码

哈希的内部编码有两种:

• ziplist(压缩列表):当哈希类型元素个数小于 hash-max-ziplist-entries 配置(默认 512 个)、同时所有值都小于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使用 ziplist 作为哈希的内部实现,ziplist 使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable 更加优秀。

• hashtable(哈希表):当哈希类型无法满足 ziplist 的条件时,Redis 会使用 hashtable 作为哈希的内部实现,因为此时 ziplist 的读写效率会下降,而 hashtable 的读写时间复杂度为 O(1)。

  1. 当 field 个数比较少且没有大的 value 时,内部编码为 ziplist:
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2
OK
127.0.0.1:6379> object encoding hashkey
"ziplist"
  1. 当有 value 大于 64 字节时,内部编码会转换为 hashtable:
127.0.0.1:6379> hset hashkey f3 "one string is bigger than 64 bytes ... 省略..."
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"
  1. 当 field 个数超过 512 时,内部编码也会转换为 hashtable:
127.0.0.1:6379> hmset hashkey f1 v1 h2 v2 f3 v3 ... 省略 ... f513 v513
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"

使用场景

为关系型数据表记录的两条用户信息,用户的属性表现为表的列,每条用户信息表现为行。如果映射关系表示这两个用户信息

关系型数据表保存用户信息

映射关系表示用户信息

相比于使用 JSON 格式的字符串缓存用户信息,哈希类型变得更加直观,并且在更新操作上变得更灵活。可以将每个用户的 id 定义为键后缀,多对 field-value 对应用户的各个属性,类似如下伪代码:

UserInfo getUserInfo(long uid) {
    // 根据 uid 得到 Redis 的键
    String key = "user:" + uid;
    // 尝试从 Redis 中获取对应的值
    userInfoMap = Redis 执行命令:hgetall key;
    // 如果缓存命中(hit)
    if (value != null) {
        // 将映射关系还原为对象形式
        UserInfo userInfo = 利用映射关系构建对象(userInfoMap);
        return userInfo;
    }
    // 如果缓存未命中(miss)
    // 从数据库中,根据 uid 获取用户信息
    UserInfo userInfo = MySQL 执行 SQL:select * from user_info where uid = <uid>
    // 如果表中没有 uid 对应的用户信息
    if (userInfo == null) {
        响应 404
        return null;
    }
    // 将缓存以哈希类型保存
    Redis 执行命令:hmset key name userInfo.name age userInfo.age city
    userInfo.city
    // 写入缓存,为了防止数据腐烂(rot),设置过期时间为 1 小时(3600 秒)
    Redis 执行命令:expire key 3600
    // 返回用户信息
    return userInfo;
}

但是需要注意的是哈希类型和关系型数据库有两点不同之处:

• 哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的 field,而关系型数据库一旦添加新的列,所有行都要为其设置值,即使为 null,如图 2-18 所示。

• 关系数据库可以做复杂的关系查询,而 Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等基本不可能,维护成本高。

List列表

列表类型是用来存储多个有序的字符串,a、b、c、d、e 五个元素从左到右组成了一个有序的列表,列表中的每个字符串称为元素(element),一个列表最多可以存储 个元素。在 Redis 中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。

命令

LPUSH/RPUSH

将一个或者多个元素从左侧放入(头插)到 list 中。

LPUSH key element [element ...]
RPUSH key element [element ...]

命令有效版本:1.0.0 之后

时间复杂度:只插入一个元素为 O(1), 插入多个元素为 O(N), N 为插入元素个数.

返回值:插入后 list 的长度。

LPUSHX/RPUSHX

在 key 存在时,将一个或者多个元素从左/右侧侧放入(头/尾插)到 list 中。不存在,直接返回

LPUSHX key element [element ...]
RPUSHX key element [element ...]

命令有效版本:2.0.0 之后

时间复杂度:只插入一个元素为 O(1), 插入多个元素为 O(N), N 为插入元素个数.

返回值:插入后 list 的长度。

LRANGE

获取从 start 到 end 区间的所有元素,左闭右闭。

LRANGE key start stop

命令有效版本:1.0.0 之后

时间复杂度:O(N)

返回值:指定区间的元素。

L/RPOP

从 list 左/右侧取出元素(即头/尾删)。

LPOP key
RPOP key

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:取出的元素或者 nil

LINDEX

获取从左数第 index 位置的元素。

LINDEX key index

命令有效版本:1.0.0 之后

时间复杂度:O(N)

返回值:取出的元素或者 nil。

LINSERT

在特定位置插入元素。

LINSERT key <BEFORE | AFTER> 1 pivot element

命令有效版本:2.2.0 之后

时间复杂度:O(N)

返回值:插入后的 list 长度。

LLEN

获取 list 长度。

LLEN key

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:list 的长度。

阻塞版本命令

blpop 和 brpop 是 lpop 和 rpop 的阻塞版本,和对应非阻塞版本的作用基本一致,除了:

• 在列表中有元素的情况下,阻塞和非阻塞表现是一致的。但如果列表中没有元素,非阻塞版本会理解返回 nil,但阻塞版本会根据 timeout,阻塞一段时间,期间 Redis 可以执行其他命令,但要求执行该命令的客户端会表现为阻塞状态(如图 2-22 所示)。

• 命令中如果设置了多个键,那么会从左向右进行遍历键,一旦有一个键对应的列表中可以弹出元素,命令立即返回。

• 如果多个客户端同时多一个键执行 pop,则最先执行命令的客户端会得到弹出的元素。

阻塞版本的 blpop 和 非阻塞版本 lpop 的区别

列表命令小结

内部编码

列表类型的内部编码有两种:

• ziplist(压缩列表):当列表的元素个数小于 list-max-ziplist-entries 配置(默认 512 个),同时列表中每个元素的长度都小于 list-max-ziplist-value 配置(默认 64 字节)时,Redis 会选用ziplist 来作为列表的内部编码实现来减少内存消耗。

• linkedlist(链表):当列表类型无法满足 ziplist 的条件时,Redis 会使用 linkedlist 作为列表的内部实现。

  1. 当元素个数较少且没有大元素时,内部编码为 ziplist:
127.0.0.1:6379> rpush listkey e1 e2 e3
OK
127.0.0.1:6379> object encoding listkey
"ziplist"
  1. 当元素个数超过 512 时,内部编码为 linkedlist:
127.0.0.1:6379> rpush listkey e1 e2 e3 ... 省略 e512 e513
OK
127.0.0.1:6379> object encoding listkey
"linkedlist"
  1. 当某个元素的长度超过 64 字节时,内部编码为 linkedlist
127.0.0.1:6379> rpush listkey "one string is bigger than 64 bytes ... 省略 ..."
OK
127.0.0.1:6379> object encoding listkey
"linkedlist"

使用场景

消息队列

Redis 可以使用 lpush + brpop 命令组合实现经典的阻塞式生产者-消费者模型队列,生产者客户端使用 lpush 从列表左侧插入元素,多个消费者客户端使用 brpop 命令阻塞式地从队列中"争抢" 队首元素。通过多个客户端来保证消费的负载均衡和高可用性。

Redis 阻塞消息队列模型


分频道的消息队列

Redis 同样使用 lpush + brpop 命令,但通过不同的键模拟频道的概念,不同的消费者可以通过 brpop 不同的键值,实现订阅不同频道的理念。


微博 Timeline

每个用户都有属于自己的 Timeline(微博列表),现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。

  1. 每篇微博使用哈希结构存储,例如微博中 3 个属性:title、timestamp、content:
hmset mblog:1 title xx timestamp 1476536196 content xxxxx
...
hmset mblog:n title xx timestamp 1476536196 content xxxxx
  1. 向用户 Timeline 添加微博,user:<uid>:mblogs 作为微博的键:
lpush user:1:mblogs mblog:1 mblog:3
...
lpush user:k:mblogs mblog:9
  1. 分页获取用户的 Timeline,例如获取用户的前 10 篇微博:
keylist = lrange user:1:mblogs 0 9
for key in keylist {
    hgetall key
}

Set集合

集合类型也是保存多个字符串类型的元素的,但和列表类型不同的是,集合中 元素之间是无序的,元素不允许重复,。一个集合中最多可以存储 个元素。Redis 除了支持集合内的增删查改操作,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多问题。

集合类型

普通命令

SADD

将一个或者多个元素添加到 set 中。注意,重复的元素无法添加到 set 中。

SADD key member [member ...]

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:本次添加成功的元素个数。

SMEMBERS

获取一个 set 中的所有元素,注意,元素间的顺序是无序的。

SMEMBERS key

命令有效版本:1.0.0 之后

时间复杂度:O(N)

返回值:所有元素的列表。

SCARD

获取一个 set 的基数(cardinality),即 set 中的元素个数。

SCARD key

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:set 内的元素个数。

SPOP

从 set 中删除并返回一个或者多个元素。注意,由于 set 内的元素是无序的,所以取出哪个元素实际是未定义行为,即可以看作随机的。

SPOP key [count]

命令有效版本:1.0.0 之后

时间复杂度:O(N), n 是 count

返回值:取出的元素。

SMOVE

将一个元素从源 set 取出并放入目标 set 中。

SMOVE source destination member

命令有效版本:1.0.0 之后

时间复杂度:O(1)

返回值:1 表示移动成功,0 表示失败。

SREM

将指定的元素从 set 中删除。

SREM key member [member ...]

命令有效版本:1.0.0 之后

时间复杂度:O(N), N 是要删除的元素个数.

返回值:本次操作删除的元素个数。

集合间的操作

交集(inter)、并集(union)、差集(diff)

SINTER

获取给定 set 的交集中的元素。

SINTER key [key ...]

命令有效版本:1.0.0 之后

时间复杂度:O(N * M), N 是最小的集合元素个数. M 是最大的集合元素个数.

返回值:交集的元素。

SINTERSTORE

获取给定 set 的交集中的元素并保存到目标 set 中。

SINTERSTORE destination key [key ...]

命令有效版本:1.0.0 之后

时间复杂度:O(N * M), N 是最小的集合元素个数. M 是最大的集合元素个数.

返回值:交集的元素个数。

SUNION

获取给定 set 的并集中的元素。

SUNION key [key ...]

命令有效版本:1.0.0 之后

时间复杂度:O(N), N 给定的所有集合的总的元素个数.

返回值:并集的元素。

SUNIONSTORE

获取给定 set 的并集中的元素并保存到目标 set 中。

SUNIONSTORE destination key [key ...]

命令有效版本:1.0.0 之后

时间复杂度:O(N), N 给定的所有集合的总的元素个数.

返回值:并集的元素个数。

SDIFF

获取给定 set 的差集中的元素。

SDIFF key [key ...]

命令有效版本:1.0.0 之后

时间复杂度:O(N), N 给定的所有集合的总的元素个数.

返回值:差集的元素。

SDIFFSTORE

获取给定 set 的差集中的元素并保存到目标 set 中。

SDIFFSTORE destination key [key ...]

命令有效版本:1.0.0 之后

时间复杂度:O(N), N 给定的所有集合的总的元素个数.

返回值:差集的元素个数。

集合命令小结

内部编码

• intset(整数集合):当集合中的元素都是整数并且元素的个数小于 set-max-intset-entries 配置(默认 512 个)时,Redis 会选用 intset 来作为集合的内部实现,从而减少内存的使用。

• hashtable(哈希表):当集合类型无法满足 intset 的条件时,Redis 会使用 hashtable 作为集合的内部实现。

  1. 当元素个数较少并且都为整数时,内部编码为 intset:
127.0.0.1:6379> sadd setkey 1 2 3 4
(integer) 4
127.0.0.1:6379> object encoding setkey
"intset"
  1. 当元素个数超过 512 个,内部编码为 hashtable:
127.0.0.1:6379> sadd setkey 1 2 3 4
(integer) 513
127.0.0.1:6379> object encoding setkey
"hashtable"
  1. 当存在元素不是整数时,内部编码为 hashtable:
127.0.0.1:6379> sadd setkey a
(integer) 1
127.0.0.1:6379> object encoding setkey
"hashtable"

使用场景

集合类型比较典型的使用场景是标签(tag)。例如 A 用户对娱乐、体育板块比较感兴趣,B 用户对历史、新闻比较感兴趣,这些兴趣点可以被抽象为标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于增强用户体验和用户黏度都非常有帮助。 例如一个电子商务网站会对不同标签的用户做不同的产品推荐。

下面的演示通过集合类型来实现标签的若干功能。

  1. 给用户添加标签
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
...
sadd user:k:tags tag1 tag2 tag4
  1. 给标签添加用户
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3
...
sadd tagk:users user:1 user:4 user:9 user:28
  1. 删除用户下的标签
srem user:1:tags tag1 tag5
...
  1. 删除标签下的用户
srem tag1:users user:1
srem tag5:users user:1
...
  1. 计算用户的共同兴趣标签
sinter user:1 1:tags user:2:tags

Zset有序集合

有序集合相对于字符串、列表、哈希、集合来说会有一些陌生。它保留了集合不能有重复成员的特点,但与集合不同的是,有序集合中的每个元素都有一个唯一的浮点类型的分数(score)与之关联,着使得有序集合中的元素是可以维护有序性的,但这个有序不是用下标作为排序依据而是用这个分数。

有序集合提供了获取指定分数和元素范围查找、计算成员排名等功能,合理地利用有序集合,可以帮助我们在实际开发中解决很多问题。

有序集合中的元素是不能重复的,但分数允许重复。类比于一次考试之后,每个人一定有一个唯一的分数,但分数允许相同。

普通命令

ZADD

添加或者更新指定的元素以及关联的分数到 zset 中,分数应该符合 double 类型,+inf/-inf 作为正负极限也是合法的。

ZADD 的相关选项:

• XX:仅仅用于更新已经存在的元素,不会添加新元素。

• NX:仅用于添加新元素,不会更新已经存在的元素。

• CH:默认情况下,ZADD 返回的是本次添加的元素个数,但指定这个选项之后,就会还包含本次更新的元素的个数。

• INCR:此时命令类似 ZINCRBY 的效果,将元素的分数加上指定的分数。此时只能指定一个元素和分数。

ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member ...]

命令有效版本:1.2.0 之后

时间复杂度:O(log(N))

返回值:本次添加成功的元素个数。

ZCARD

获取一个 zset 的基数(cardinality),即 zset 中的元素个数。

ZCARD key

命令有效版本:1.2.0 之后

时间复杂度:O(1)

返回值:zset 内的元素个数。

ZCOUNT

返回分数在 min 和 max 之间的元素个数。默认情况下,min 和 max 都是包含的,可以通过 ( 排除。

ZCOUNT key min max

命令有效版本:2.0.0 之后

时间复杂度:O(log(N))

返回值:满足条件的元素列表个数。

ZRANGE

返回指定区间里的元素,分数按照升序。带上 WITHSCORES 可以把分数也返回。

ZRANGE key start 1 stop [WITHSCORES]

此处的 [start, stop] 为下标构成的区间. 从 0 开始, 支持负数.

命令有效版本:1.2.0 之后

时间复杂度:O(log(N)+M)

返回值:区间内的元素列表。

ZREVRANGE

返回指定区间里的元素,分数按照降序。带上 WITHSCORES 可以把分数也返回。

这个命令可能在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。

ZREVRANGE key start stop [WITHSCORES]

命令有效版本:1.2.0 之后

时间复杂度:O(log(N)+M)

返回值:区间内的元素列表。

ZRANGEBYSCORE

返回分数在 min 和 max 之间的元素,默认情况下,min 和 max 都是包含的,可以通过 ( 排除。

这个命令可能在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。

ZRANGEBYSCORE key min 1 max [WITHSCORES]

命令有效版本:1.0.5 之后

时间复杂度:O(log(N)+M)

返回值:区间内的元素列表。

ZPOPMAX

删除并返回分数最高的 count 个元素。

ZPOPMAX key [count]

命令有效版本:5.0.0 之后

时间复杂度:O(log(N) * M)

返回值:分数和元素列表。

BZPOPMAX

ZPOPMAX 的阻塞版本。

BZPOPMAX key [key ...] timeout

命令有效版本:5.0.0 之后

时间复杂度:O(log(N))

返回值:元素列表。

ZPOPMIN

删除并返回分数最低的 count 个元素。

ZPOPMIN key [count]

命令有效版本:5.0.0 之后

时间复杂度:O(log(N) * M)

返回值:分数和元素列表。

BZPOPMIN

ZPOPMIN 的阻塞版本。

BZPOPMIN key [key ...] timeout

命令有效版本:5.0.0 之后

时间复杂度:O(log(N))

返回值:元素列表。

ZRANK

返回指定元素的排名,升序。

ZRANK key member

命令有效版本:2.0.0 之后

时间复杂度:O(log(N))

返回值:排名。

ZREVRANK

返回指定元素的排名,降序。

ZREVRANK key member

命令有效版本:2.0.0 之后

时间复杂度:O(log(N))

返回值:排名。

ZSCORE

返回指定元素的分数。

ZSCORE key member

命令有效版本:1.2.0 之后

时间复杂度:O(1)

返回值:分数。

ZREM

删除指定的元素。

ZREM key member [member ...]

命令有效版本:1.2.0 之后

时间复杂度:O(M*log(N))

返回值:本次操作删除的元素个数。

ZREMRANGEBYRANK

按照排序,升序删除指定范围的元素,左闭右闭。

ZREMRANGEBYRANK key start stop

命令有效版本:2.0.0 之后

时间复杂度:O(log(N)+M)

返回值:本次操作删除的元素个数。

ZREMRANGEBYSCORE

按照分数删除指定范围的元素,左闭右闭。

ZREMRANGEBYSCORE key min max

命令有效版本:1.2.0 之后

时间复杂度:O(log(N)+M)

返回值:本次操作删除的元素个数。

ZINCRBY

为指定的元素的关联分数添加指定的分数值。

ZINCRBY key increment member

命令有效版本:1.2.0 之后

时间复杂度:O(log(N))

返回值:增加后元素的分数。

ZINTERSTORE

求出给定有序集合中元素的交集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元素对应的分数按照不同的聚合方式和权重得到新的分数。

ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight
[weight ...]] [AGGREGATE <SUM | MIN | MAX>]

命令有效版本:2.0.0 之后

时间复杂度:O(N*K)+O(M*log(M)) N 是输入的有序集合中, 最小的有序集合的元素个数; K 是输入了

几个有序集合; M 是最终结果的有序集合的元素个数.

返回值:目标集合中的元素个数

2 表示后面会指定 2 个有序集合 作为输入集合,分别是 zset1 和 zset2。 如果你有更多的有序集合参与交集计算,可以增加这个数字,并在后面列出这些集合的名称。

ZUNIONSTORE

求出给定有序集合中元素的并集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元素对应的分数按照不同的聚合方式和权重得到新的分数。

ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight
[weight ...]] [AGGREGATE <SUM | MIN | MAX>]

命令有效版本:2.0.0 之后

时间复杂度:O(N)+O(M*log(M)) N 是输入的有序集合总的元素个数; M 是最终结果的有序集合的元素

个数.

返回值:目标集合中的元素个数

内部编码

  1. ziplist(压缩列表):当有序集合的元素个数小于 zset-max-ziplist-entries 配置(默认 128 个),同时每个元素的值都小于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会用 ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。
  2. skiplist(跳表):当 ziplist 条件不满足时,有序集合会使用 skiplist 作为内部实现,因为此时ziplist 的操作效率会下降。

当元素个数较少且每个元素较小时,内部编码为 ziplist:
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3
(integer) 3
127.0.0.1:6379> object encoding zsetkey
"ziplist"


当元素个数超过 128 个,内部编码 skiplist
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3 ... 省略 ... 82 e129
(integer) 129
127.0.0.1:6379> object encoding zsetkey
"skiplist"


当某个元素大于 64 字节时,内部编码 skiplist:
127.0.0.1:6379> zadd zsetkey 50 "one string bigger than 64 bytes ... 省略 ..."
(integer) 1
127.0.0.1:6379> object encoding zsetkey
"skiplist"

使用场景

有序集合比较典型的使用场景就是排行榜系统。例如常见的网站上的热榜信息,榜单的维度可能是多方面的:按照时间、按照阅读量、按照点赞量。本例中我们使用点赞数这个维度,维护每天的热榜:

假如用户A发布了一篇文章,并获得了3个赞,可以使用有序集合的zadd和zincrby功能,之后在获得赞继续使用zincrby,如果用户删除文章了,需要将用户从榜单中删除掉,可以使用zrem。展示获赞最多的10个用户,可以使用zrevrange命令实现

渐进式遍历

Redis 使用 scan 命令进行渐进式遍历键,进而解决直接使用 keys 获取键时可能出现的阻塞问题。每次 scan 命令的时间复杂度是 O(1),但是要完整地完成所有键的遍历,需要执行多次 scan。

首次 scan 从 0 开始.

当 scan 返回的下次位置为 0 时, 遍历结束.

SCAN

以渐进式的方式进行键的遍历。

SCAN cursor [MATCH pattern] [COUNT 1 count] [TYPE type]

命令有效版本:2.8.0 之后

时间复杂度:O(1)

返回值:下一次 scan 的游标(cursor)以及本次得到的键。

渐进性遍历 scan 虽然解决了阻塞的问题,但如果在遍历期间键有所变化(增加、修改、删除),可能导致遍历时键的重复遍历或者遗漏,这点务必在实际开发中考虑。

数据库管理

Redis 提供了几个面向 Redis 数据库的操作,分别是 dbsize、select、flushdb、flushall 命令,

切换数据库

select dbIndex

许多关系型数据库,例如 MySQL 支持在一个实例下有多个数据库存在的,但是与关系型数据库用字符来区分不同数据库名不同,Redis 只是用数字作为多个数据库的实现。Redis 默认配置中是有 16个数据库。select 0 操作会切换到第一个数据库,select 15 会切换到最后一个数据库。0 号数据库和15 号数据库保存的数据是完全不冲突的,即各种有各自的键值对。默认情况下,我们处于数据库 0。

Redis 管理的数据库

Redis 中虽然支持多数据库,但随着版本的升级,其实不是特别建议使用多数据库特性。如果真的需要完全隔离的两套键值对,更好的做法是维护多个 Redis 实例,而不是在一个Redis 实例中维护多数据库。这是因为本身 Redis 并没有为多数据库提供太多的特性,其次无论是否有多个数据库,Redis 都是使用单线程模型,所以彼此之间还是需要排队等待命令的执行。同时多数据库还会让开发、调试和运维工作变得复杂。所以实践中,始终使用数据库 0 其实是一个很好的选择。

清除数据库

flushdb / flushall 命令用于清除数据库,区别在于 flushdb 只清除当前数据库,flushall 会清楚所有数据库。


http://www.kler.cn/a/573795.html

相关文章:

  • 数据结构预算法-图论- 最小生成树-prim and kruskal
  • 第1章:项目概述与环境搭建
  • 游戏引擎学习第136天
  • 使用mybatis plus的@Select自定义sql时,如何实现通用的分页查询?
  • 2025前端最新面试题-安全篇
  • DeepSeek 与云原生后端:AI 赋能现代应用架构
  • 达梦数据库备份
  • 什么是kube-proxy?
  • 容器 /dev/shm 泄漏学习
  • 嵌入式系统启动流程分析:从汇编到C语言环境的过渡分析
  • Van Uploader解决Android11及以下系统上传图片无反应问题
  • java面向对象(详细讲解)
  • 记录linux安装mysql后链接不上的解决方法
  • 期权帮|2025年股指期货最新规则是什么?
  • 2025.3.3总结
  • android接入rocketmq
  • FastGPT 源码:混合检索调用链路
  • PHP fastadmin 学习
  • [杂学笔记]HTTP1.0和HTTP1.1区别、socket系列接口与TCP协议、传输长数据的时候考虑网络问题、慢查询如何优化、C++的垃圾回收机制
  • MQ保证消息的顺序性