https://www.yuque.com/books/share/e04c68e0-44b2-4a74-abf7-f110ebf8c423?#(密码:gsz1) 《Redis 教程》
Redis 安装
关于 Redis 的版本
Redis 借鉴了 Linux 操作系统对于版本号的命名规则:
- 版本号第二位如果是奇数,则为非稳定版本(例如2.7、2.9、3.1)
- 如果是偶数,则为稳定版本(例如2.6、2.8、3.0、3.2)
当前奇数版本就是下一个稳定版本的开发版本,例如 2.9 版本是 3.0 版本的开发版本。所以我们在生产环境通常选取偶数版本的Redis,如果对于某些新的特性想提前了解和使用,可以选择最新的奇数版本。
在 macOS 中安装 Redis
在 macOS 中有两种方式:
- 方式一:编译安装
- 方式二(推荐):使用 Homebrew 安装
macOS 系统下的软件包管理工具 Homebrew 提供了较新版本的 Redis 包,所以我们可以直接使用它们来安装 Redis,省去了在 Linux 上需要手动编译的麻烦。
1、安装 Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
2、通过 Homebrew 安装 Redis
brew install redis
在 Linux 中安装 Redis
# 下载 Redis 源码
wget https://download.redis.io/releases/redis-6.0.9.tar.gz
# 解压 Redis 压缩包
tar xzf redis-6.0.9.tar.gz
# 进入 Redis 源码目录
cd redis-6.0.9
# 编译安装
make
现在已编译的二进制文件位于 src 目录中。使用以下命令运行 Redis:
$ ./src/redis-server
要将 Redis 二进制文件安装到 /usr/local/bin 中,只需使用:
make install
运行 Redis
编译后在 Redis 源代码目录的 src 文件夹中会有以下几个可执行文件:
我们最常用是 redis-server 和 redis-cli。
最简单的,直接运行 redis-server 即可启动 Redis:
redis-server
Redis 默认使用 6379 端口,我们也可以通过 --port 参数指定启动端口:
redis-server --port 1234
如果需要在后端运行 Redis:
redis-server --daemonize yes
查看 Redis 运行状态:
# 查看 Redis 后端运行进程
ps -ef | grep -i redis
停止 Redis
考虑到 Redis 有可能正在将内存中的数据同步到硬盘中,强行终止 Redis 进程可能会导致数据丢失。
所有正确停止 Redis 的方式应该是向 Redis 发送 SHUTDOWN 命令:
redis-cli shutdown
当 Redis 手动 Shutdown 命令后,会先断开所有客户端连接,然后根据配置执行持久化,最后完成退出。
Redis 可以妥善处理 SIGTERM 信号,所有使用 kill Redis 进程的 PID 也可以正常结束 Redis,效果与发送 SHUTDOWN 命令一样。
# 通过进程号停止 Redis
kill -9 4684
连接 Redis
edis-cli 是 Redis 自带的基于命令行的 Redis 客户端,也是我们学习和测试 Redis 的重要工具。
运行 redis-cli 即可连接数据库:
redis-cli
也可以指定服务器地址和端口连接:
redis-cli -h 127.0.0.1 -p 1234
不出差错的话,此时已经连接上 Redis 数据库,我们通过 Redis 提供的 PING 命令来测试与 Redis 是否连接正常:
127.0.0.1:6379> PING
PONG
127.0.0.1:6379>
Redis 返回 PONG,证明连接正常。
如果想要断开连接:
- 命令:quit
- 快捷键:Ctrl + C
Redis 配置
我们在之前介绍过可以通过 redis-server 的启动参数 port 设置了 Redis 服务的端口号,除此之外 Redis 还支持其他配置选项,如是否开启持久化、日志级别等。
通过命令行传递参数
最简单的方式就是在启动 redis-server 的时候直接传递命令参数。
redis-server --port 6380 --host 127.0.0.1
配置文件
由于可以配置的选项较多,通过启动参数设置这些选项并不方便,所以 Redis 支持通过配置文件来设置这些选项。
Redis 提供了一个配置文件的模板 redis.conf,位于源代码目录的根目录中。
我们建议把该文件放到 /etc/redis 目录中(该目录需要手动创建),以端口号命令,例如 6379.conf。
启用配置文件的方法是在启动时将配置文件的路径作为启动参数传递给 redis-server
redis-server 配置文件路径
通过启动参数传递同名的配置选项会覆盖配置文件中的相应的参数,就像这样:
redis-server 配置文件路径 --port 3000
在服务器运行时更改 Redis 配置
还可以在 Redis 运行时通过 CONFIG SET 命令在不重新启动 Redis 的清空下动态修改部分 Redis 配置。就像这样:
CONFIG SET logLevel warning
同样在运行的时候也可以使用 CONFIG GET 命令获得 Redis 当前的配置情况:
CONFIG GET logLevel
Redis 中的多数据库
一个 Redis 实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。这与我们熟知的在一个关系数据库中可以创建多个数据库类似,所有可以将其中的每个字典都理解成一个独立的数据库。
Redis 默认支持 16 个数据库,分别编号为 0、1、2、…14、15
- Redis 不支持自定义数据库名字
- 因为每个数据库都以编号命名,所有开发者必须要明确哪个数据库存放了哪些数据
- 可以通过配置参数 databases 修改支持的数据库个数
每个数据库都是独立的,也就是说你在 0 号数据库中插入的数据在 1 号数据库是访问不到的。
客户端与 Redis 建立连接后自动选择 0 号数据库,我们可以使用 SELECT 命令来更换数据库。
127.0.0.1:6379> SET a 1
OK
127.0.0.1:6379> KEYS *
1) "a"
127.0.0.1:6379> SELECT 16
(error) ERR DB index is out of range
127.0.0.1:6379> SELECT 15
OK
127.0.0.1:6379[15]> SET b 2
OK
127.0.0.1:6379[15]> KEYS *
1) "b"
127.0.0.1:6379[15]> SELECT 0
OK
127.0.0.1:6379> KEYS *
1) "a"
127.0.0.1:6379>
# 将指定 key 移动到指定数据库
move key db
当选择的数据库编号超过最大数据库编号时,默认编号的数据库
Redis 不支持为每个数据库设置不同的访问密码,所有一个客户端要么可以访问全部数据库,要么一个数据库也没有权限访问。
最重要的一点是多个数据库之间并不是完全隔离的,比如 FLUSHALL 命令可以清空一个 Redis 实例中所有数据库中的数据。
综上所述,这些数据库更像是一个命名空间,而不适宜存储不同应用程序的数据,比如不适宜使用 0 号数据库存储 A 应用数据而使用 1 号数据库存储 B 应用数据,这是非常不推荐的做法!!!
不同的应用应该使用不同的 Redis 实例存储数据。由于 Redis 非常轻量级,一个空的 Redis 占用的内存只有 1 MB 作用,所以不用担心多个 Redis 实例会额外占用很多内存的问题。
Redis 常用数据类型及操作命令(CRUD)
Redis 不是简单的键值存储,它实际上是一个数据结构服务器,支持不同类型的值。这意味着在传统键值存储中,您将字符串键与字符串值相关联,而在 Redis 中,该值不仅限于简单的字符串,还可以容纳更复杂的数据结构。
以下是 Redis 中支持的所有数据结构的列表:
Redis 中的键
Redis 密钥是二进制安全的,这意味着您可以使用任何二进制序列作为 key,从 “foo” 之类的字符串到 JPEG 文件的内容。空字符串也是有效的键。
- 不要太长,浪费空间
- 不要过短,不利于阅读
- 统一的命令规范
字符串(String)
字符串类型是 Redis 中最基本的数据类型,也是其它数据类型的基础。
-
它能存储任何形式的字符串,包括二进制数据。
-
你可以用它存储用户的邮箱、JSON 化的对象,甚至是一张图片
-
value 最多可以容纳数据大小为 512 MB
添加# 设置指定 key 的值 SET key value # 将给定 key 的值设为 value ,并返回 key 的旧值(old value) GETSET key value # 只有在 key 不存在时设置 key 的值 SETNX key value # 同时设置一个或多个 key-value 对 MSET key value [key value ...] # 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在 MSETNX key value [key value ...] # 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。 APPEND key value
注意:在 Redis 中命令不区分大小写。也就是说 SET foo bar 和 set foo bar 是一样的,但是我们约定使用大写表示它是一个 Redis 命令。
查询
# 获取指定 key 的值 GET key # 返回 key 中字符串值的子字符 GETRANGE key start end # 获取所有(一个或多个)给定 key 的值 MGET key [key ...] # 返回 key 所储存的字符串值的长度。 STRLEN key # 通用命令:查询集合中是否有指定的 key EXISTS key [key ...] # 通用命令,查询 key 的类型 TYPE key
修改
# 设置指定 key 的值 SET key value # 将给定 key 的值设为 value ,并返回 key 的旧值(old value) GETSET key value # 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。 APPEND key value
删除
# 通用命令:删除1个或多个指定的 key DEL key [key ...]
数字值
数字值在 Redis 中以字符串保存。# 将 key 中储存的数字值增一 INCR key # 将 key 所储存的值加上给定的增量值(increment) INCRBY key increment # 将 key 中储存的数字值减一 DECR key # key 所储存的值减去给定的减量值(decrement) DECRBY key decrement
哈希(Hash)
哈希(也叫散列)类型也是一种字典结构,其存储了字段和字段值的映射,但字符值只能是字符串,不能其它数据类型,换句话说,散列类型不能嵌套其它数据类型。一个哈希类型可以包含至少 232 - 1 个字段。
提示:除了散列类型,Redis 的其它数据类型同样不支持数据类型嵌套。
-
添加
# 将哈希表 key 中的字段 field 的值设为 value HSET key field value [field value ...] # 同时将多个 field-value (域-值)对设置到哈希表 key 中 HMSET key field value [field value ...] # 只有在字段 field 不存在时,设置哈希表字段的值 HSETNX key field value
-
查询
# 获取所有哈希表中的字段 HKEYS key # 获取哈希表中字段的数量 HLEN key # 获取所有给定字段的值 HMGET key field1 [field2] # 获取存储在哈希表中指定字段的值 HGET key field # 获取在哈希表中指定 key 的所有字段和值 HGETALL key # 查看哈希表 key 中,指定的字段是否存在 HEXISTS key field # 获取哈希表中所有值 HVALS key # 迭代哈希表中的键值对 HSCAN key cursor [MATCH pattern] [COUNT count]
-
修改
# 将哈希表 key 中的字段 field 的值设为 value HSET key field value [field value ...] # 为哈希表 key 中的指定字段的整数值加上增量 increment HINCRBY key field increment
-
删除
# 删除一个或多个哈希表字段 HDEL key field1 [field2] # 删除整个数据字段 DEL key [key ...]
列表(List)
-
列表类型类似于编程语言中的数组,可以存储一个有序的字符串列表,常用的操作就是向列表两端添加元素,或者获得列表的某一个片段。
-
列表类型内部使用双向链表实现的,所有向列表两端添加元素的时间复杂度为O(1),获取越接近两端的元素速度就越快。这意味着即时是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的(和从只有20个元素的列表中获取头部或尾部的10条记录的速度是一样的)。
-
不过使用链表的代价是通过索引访问元素比较慢。设想在 iPhone 发售当前有 1000 个人在商店排队购买,这时商家为了感谢大家的支持,决定奖励第486位的顾客异步免费的 iPhone。为了找到这第 486 位顾客,工作人员不得不从队首一个一个地数到 486 个人。但同时,无论队伍有多长,新来的人想加入队伍的话直接排到队尾就好了,和队伍里有多少人没有任何关系。这种情景与列表类型的特性很相似。
-
这种特性使列表类型能非常快速地完成关系数据库难以应付的场景:例如社交网站的新鲜事,我们关心的只是最新内容,使用列表类型存储,即使新鲜事的总数达到几千万个,获取其中最新的100条数据也是极快的。同样因为在两端插入记录的时间复杂度是O(1),列表类型也适合用来记录日志,可以保证加入新日志的速度不会受到已有日志数量额影响。
-
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
添加
# 将一个或多个值插入到列表头部 LPUSH key element [element ...] # 在列表的元素前或者后插入元素 LINSERT key BEFORE|AFTER pivot value # 将一个值插入到已存在的列表头部 LPUSHX key value # 通过索引设置列表元素的值 LSET key index value # 在列表中添加一个或多个值 RPUSH key value1 [value2] # 为已存在的列表添加值 RPUSHX key value
查询
# 通过索引获取列表中的元素 LINDEX key index # 获取列表长度 LLEN key # 获取列表指定范围内的元素 LRANGE key start stop
删除
# 移出并获取列表的第一个元素 LPOP key # 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 BLPOP key1 [key2 ] timeout # 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 BRPOP key1 [key2 ] timeout # 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 BRPOPLPUSH source destination timeout # 移除列表元素 # 如果 count > 0,则从头向尾遍历删除元素 # 如果 count < 0,则从后面向前面删除元素 # 如果 count = 0,则删除所有匹配的元素 LREM key count value # 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除 LTRIM key start stop # 移除列表的最后一个元素,返回值为移除的元素 RPOP key # 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 RPOPLPUSH source destination
集合(Set)
集合类型和数学中的集合概念相似,集合中的元素是唯一的、无序的,简单理解集合就是没有顺序且不重复的列表。
一个集合类型可以存储至多 232 - 1 个字符串。
集合类型和列表类型有相似之处,它们的主要区别是:
- 列表是有序的,集合是无序的
- 列表数据可以重复,集合中没有重复数据
集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等。由于集合类型在 Redis 内部是使用值为空的散列表实现的,所以这些操作的时间复杂度都是O(1)。
最方便的是多个集合之间还可以进行并集、交集和差集运算。
-
添加
# 向集合添加一个或多个成员 SADD key member1 [member2]
-
查询
# 返回集合中的所有成员 SMEMBERS key # 获取集合的成员数 SCARD key # 判断 member 元素是否是集合 key 的成员 SISMEMBER key member # 返回集合中一个或多个随机数 SRANDMEMBER key [count]
-
删除
# 移除集合中一个或多个成员 SREM key member1 [member2] # 移除并返回集合中的一个随机元素 SPOP key # 将 member 元素从 source 集合移动到 destination 集合 SMOVE source destination member
集合间聚合运算
多个集合之间还可以进行并集、交集和差集运算
# 返回第一个集合与其他集合之间的差异。
SDIFF key1 [key2]
# 返回给定所有集合的交集
SINTER key1 [key2]
# 返回所有给定集合的并集
SUNION key1 [key2]
# 返回给定所有集合的差集并存储在 destination 中
SDIFFSTORE destination key1 [key2]
# 返回给定所有集合的交集并存储在 destination 中
SINTERSTORE destination key1 [key2]
# 所有给定集合的并集存储在 destination 集合中
SUNIONSTORE destination key1 [key2]
使用场景
- 跟踪一些唯一性数据
- 比如访问网站的唯一 IP 地址信息,每次访问网站的时候记录用户 IP 地址,SET 自动保证数据的唯一不重复
- 充分利用 SET 聚合操作方便高效的特性,用于维护数据对象之间的关联关系
- 比如所有购买A商品的客户 ID 存储到指定的 SET 中,所有购买B商品的客户 ID 存储到指定的 SET 中,如果我们想要获取有哪个客户同时购买了这两个商品,我们只需要使用交集操作就可以轻松的查出来
有序集合(Sorted Set)
有序集合是一种类似于集合和哈希之间的混合数据类型。
- 与集合一样,排序集合由唯一的非重复字符串元素组成
- 有序集合中的元素不排序,但有序集合中的每个元素都关联了一个分数(这就是为什么类型也类似于哈希,因为每个元素都映射到一个值)
- 虽然集合中每个元素都是不同的,但是它们的分数确可以相同
有序集合类型在某些方面和列表类型有些相似。
相同点:
- 两者都是有序的
- 两者都可以获得某一范围的元素
不同点:
- 列表类型通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会较慢,所以它更适合实现如“新鲜事”或“日志”这样很少访问中间元素的应用
- 有序集合类似是使用哈希表实现的,所以即使读取位于中间部分的数据速度也很快
- 列表中不能简单的调整某个元素的位置,但是有序集合可以(通过更改元素的分数)
- 有序集合要比列表类型更耗费内存
添加
# 向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZADD key score member [score member ...]
查询
# 通过索引区间返回有序集合指定区间内的成员,分数从低到高排序
ZRANGE key start stop [WITHSCORES]
# 通过索引区间返回有序集合指定区间内的成员,分数从高到低排序
ZREVRANGE key start stop [WITHSCORES]
# 返回有序集中指定分数区间内的成员,分数从低到高排序
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
# 返回有序集中指定分数区间内的成员,分数从高到低排序
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
# 返回有序集合中指定成员的排名,有序集成员按分数值(从小到大)排序
ZRANK key member
# 返回有序集合中指定成员的排名,有序集成员按分数值(从大到小)排序
ZREVRANK key member
# 获取有序集合的成员数
ZCARD key
# 返回有序集中,成员的分数值
ZSCORE key member
# 计算在有序集合中指定区间分数的成员数
ZCOUNT key min max
修改
# 向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZADD key score member [score member ...]
# 有序集合中对指定成员的分数加上增量 increment
ZINCRBY key increment member
删除
# 移除有序集合中的一个或多个成员
ZREM key member [member ...]
# 移除有序集合中给定的排名区间的所有成员
ZREMRANGEBYRANK key start stop
# 移除有序集合中给定的分数区间的所有成员
ZREMRANGEBYSCORE key min max
有序集合间聚合运算
# 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination 中
ZINTERSTORE destination numkeys key [key ...]
# 计算给定的一个或多个有序集的并集,并存储在新的 key 中
ZUNIONSTORE destination numkeys key [key ...]
通用命令
# 返回所有 key
KEYS *
# 返回所有以 my 开头的 key
KEYS my*
# 获取 key 的类型
TYPE key
# 查询某个 key 是否存在
EXISTS key [key ...]
# 将 key 改名为 newkey
RENAME key newkey
# 删除指定 key
DEL key [key ...]
# 从当前数据库中随机返回(不删除)一个 key
RANDOMKEY
# 对 key 进行重命名
RENAME key newkey
# 清空当前数据库所有内容
FLUSHDB
# 清空所有数据库内容
FLUSHALL
# 将当前数据库的 key 移动到给定的数据库 db 当中
MOVE key db
Redis 过期时间
设置键的过期时间
# 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
EXPIRE key seconds
# 和 EXPIRE 一样,但是它以毫秒为单位
PEXPIRE key milliseconds
# EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置生存时间。
# 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
EXPIREAT key timestamp
# 这个命令和 EXPIREAT 命令类似,但它以毫秒为单位设置 key 的过期 unix 时间戳,而不是像 EXPIREAT 那样,以秒为单位。
PEXPIREAT key milliseconds-timestamp
上面这4个命令只是单位和表现形式上的不同,但实际上 EXPIRE、PEXPIRE 以及 EXPIREAT 命令的执行最后都会使用 PEXPIREAT 来实行。
比如使用 EXPIRE 来设置 KEY 的生存时间为 N 秒,那么后台是如何运行的呢:
- 它会调用 PEXPIRE 命令把 N 秒转换为M毫秒
- 然后获取当前的 UNIX 时间单位也是毫秒
- 把当前 UNIX 时间加上 M 毫秒传递给 PEXPREAT
另外给键设置了过期时间,这个时间保存在一个字典里,也是键值结构,键是一个指针,指向真实的键,而值这是一个长整型的 UNIX 时间。
获取键的过期时间
# 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
TTL key
# 类似于 TTL,但它以毫秒为单位返回 key 的剩余生存时间。
PTTL key
过期时间返回值说明:
清除键的过期时间
# 移除给定 key 的生存时间,将这个 key 从『易失的』(带生存时间 key )转换成『持久的』(一个不带生存时间、永不过期的 key )。
PERSIST key
注意:
- 使用 SET 或 GETSET 命令为键赋值也会同时清除键的过期时间。
- 其它只对键值进行操作的命令(如 INCR、LPUSH、HSET、ZREM)不会影响键的过期时间。
Redis 事务
Redis 中的事务
Redis 中提供了以下三个命令来处理事务:
# 标记一个事务块的开始
# 事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行
MULTI
# 执行所有事务块内的命令。
EXEC
# 取消事务,放弃执行事务块内的所有命令。
DISCARD
示例:
SET Jack 10
SET Rose 20
# Jack 给 Rose 转账 5 块钱
# 开启事务
MULTI
DECRBY Jack 5
INCRBY ROSE 5
EXEC
参考链接
Redis 持久化
Redis 提供了两种持久化方案:
- RDB 持久化,根据指定的规则“定时”将内存中的数据存储在硬盘上,在重启之后读取硬盘上的 .rdb 快照文件将数据恢复到内存中。
- AOF 持久化:AOF 持久化记录服务器执行的所有写操作命令形成 .aof 日志文件保存到硬盘中,并在服务器启动时,通过重新执行这些命令来还原数据集。
RDB 持久化
RDB 持久化相关配置规则如下:
save 900 1 # 每 900 秒至少有 1 个 key 变化了,则写入快照
save 300 10 # 每 300 秒至少有 10 个 key 变化了,则写入快照
save 60 10000 # 每 60 秒至少有 10000 个 key 变化了,则写入快照
dbfilename dump.rdb # 快照保存的文件名称
dir ./ # 快照文件保存路径
AOF 持久化
默认情况下,Redis 没有开启 AOF 方式的持久化,可以通过 appendonly 参数启用:
appendonly yes
AOF 文件的保存位置和 RDB 文件的位置相同,都是 dir 参数设置的。默认的文件名是 appendonly.aof,可以通过 appendfilename 来修改:
# AOF 文件和 RDB 文件保存目录是一样的
dir ./
# 同步的文件名称
appendfilename "appendonly.aof"
AOF 有三种同步策略:
# 每修改同步,每一次发送数据变化都会被立即同步到磁盘中,效率比较低,但是数据最安全
appendfsync always
# 默认值,每秒同步,异步完成,同步效率非常高,缺点是一旦系统出现宕机,这1秒之内操作的数据就会丢失
appendfsync everysec
# 不同步
appendfsync no
一般情况下使用默认值 everysec 就足够了,既兼顾了性能又保证了安全。
RDB vs AOF
参考阅读:http://doc.redisfans.com/topic/persistence.html。
参考链接
Redis 图形管理软件
Redis 的图形化管理软件有很多,这里我主要推荐RDM:https://rdm.dev/。
使用编程语言客户端操作 Redis
Redis 支持的程序客户端
参考官方支持列表:https://redis.io/clients。
在 Node.js 中操作 Redis
Node.js 中可以操作 Redis 的软件包推荐列表:https://redis.io/clients#nodejs。
推荐下面两个:
这里我主要以 ioredis 为例。
相关链接:
- API 参考文档:https://github.com/luin/ioredis/blob/master/API.md
- 更新日志:https://github.com/luin/ioredis/blob/master/Changelog.md
- 从 node_redis 迁移:https://github.com/luin/ioredis/wiki/Migrating-from-node_redis
基本使用
安装依赖:
npm install ioredis
const Redis = require("ioredis");
const redis = new Redis(); // uses defaults unless given configuration object
// ioredis supports all Redis commands:
redis.set("foo", "bar"); // returns promise which resolves to string, "OK"
// the format is: redis[SOME_REDIS_COMMAND_IN_LOWERCASE](ARGUMENTS_ARE_JOINED_INTO_COMMAND_STRING)
// the js: ` redis.set("mykey", "Hello") ` is equivalent to the cli: ` redis> SET mykey "Hello" `
// ioredis supports the node.js callback style
redis.get("foo", function (err, result) {
if (err) {
console.error(err);
} else {
console.log(result); // Promise resolves to "bar"
}
});
// Or ioredis returns a promise if the last argument isn't a function
redis.get("foo").then(function (result) {
console.log(result); // Prints "bar"
});
// Most responses are strings, or arrays of strings
redis.zadd("sortedSet", 1, "one", 2, "dos", 4, "quatro", 3, "three");
redis.zrange("sortedSet", 0, 2, "WITHSCORES").then((res) => console.log(res)); // Promise resolves to ["one", "1", "dos", "2", "three", "3"] as if the command was ` redis> ZRANGE sortedSet 0 2 WITHSCORES `
// All arguments are passed directly to the redis server:
redis.set("key", 100, "EX", 10);
有关更多实例,可以参考这里:https://github.com/luin/ioredis/tree/master/examples。
Pipelining
如果要发送一批命令(例如> 5),则可以使用流水线将命令在内存中排队,然后将它们一次全部发送到 Redis。这样,性能提高了50%〜300%(请参阅基准测试部分)。
redis.pipeline() 创建一个 Pipeline 实例。您可以像 Redis 实例一样在其上调用任何 Redis 命令。这些命令在内存中排队,并通过调用 exec 方法刷新到 Redis:
const pipeline = redis.pipeline();
pipeline.set("foo", "bar");
pipeline.del("cc");
pipeline.exec((err, results) => {
// `err` is always null, and `results` is an array of responses
// corresponding to the sequence of queued commands.
// Each response follows the format `[err, result]`.
});
// You can even chain the commands:
redis
.pipeline()
.set("foo", "bar")
.del("cc")
.exec((err, results) => {});
// `exec` also returns a Promise:
const promise = redis.pipeline().set("foo", "bar").get("foo").exec();
promise.then((result) => {
// result === [[null, 'OK'], [null, 'bar']]
});
每个链接的命令还可以具有一个回调,该回调将在命令得到答复时被调用:
redis
.pipeline()
.set("foo", "bar")
.get("foo", (err, result) => {
// result === 'bar'
})
.exec((err, result) => {
// result[1][1] === 'bar'
});
除了将命令分别添加到管道队列之外,您还可以将命令和参数数组传递给构造函数:
redis
.pipeline([
["set", "foo", "bar"],
["get", "foo"],
])
.exec(() => {
/* ... */
});
#length 属性显示管道中有多少个命令:
const length = redis.pipeline().set("foo", "bar").get("foo").length;
// length === 2
事务
大多数时候,事务命令 multi&exec 与管道一起使用。因此,在调用 multi 时,默认情况下会自动创建 Pipeline 实例,因此您可以像使用管道一样使用 multi:
redis
.multi()
.set("foo", "bar")
.get("foo")
.exec((err, results) => {
// results === [[null, 'OK'], [null, 'bar']]
});
如果事务的命令链中存在语法错误(例如,错误的参数数量,错误的命令名称等),则不会执行任何命令,并返回错误:
redis
.multi()
.set("foo")
.set("foo", "new value")
.exec((err, results) => {
// err:
// { [ReplyError: EXECABORT Transaction discarded because of previous errors.]
// name: 'ReplyError',
// message: 'EXECABORT Transaction discarded because of previous errors.',
// command: { name: 'exec', args: [] },
// previousErrors:
// [ { [ReplyError: ERR wrong number of arguments for 'set' command]
// name: 'ReplyError',
// message: 'ERR wrong number of arguments for \'set\' command',
// command: [Object] } ] }
});
就接口而言,multi 与管道的区别在于,当为每个链接的命令指定回调时,排队状态将传递给回调,而不是命令的结果:
redis
.multi()
.set("foo", "bar", (err, result) => {
// result === 'QUEUED'
})
.exec(/* ... */);
如果要使用不带管道的事务,请将 { pipeline: false } 传递给 multi,每个命令将立即发送到 Redis,而无需等待 exec 调用:
redis.multi({ pipeline: false });
redis.set("foo", "bar");
redis.get("foo");
redis.exec((err, result) => {
// result === [[null, 'OK'], [null, 'bar']]
});
multi 的构造函数还接受一批命令:
redis
.multi([
["set", "foo", "bar"],
["get", "foo"],
])
.exec(() => {
/* ... */
});
管道支持内联事务,这意味着您可以将管道中的命令子集分组为一个事务:
redis
.pipeline()
.get("foo")
.multi()
.set("foo", "bar")
.get("foo")
.exec()
.get("foo")
.exec();
错误处理
Redis服务器返回的所有错误都是 ReplyError 的实例,可以通过 Redis 进行访问:
const Redis = require("ioredis");
const redis = new Redis();
// This command causes a reply error since the SET command requires two arguments.
redis.set("foo", (err) => {
err instanceof Redis.ReplyError;
});
这是 ReplyError 的错误堆栈:
ReplyError: ERR wrong number of arguments for 'set' command
at ReplyParser._parseResult (/app/node_modules/ioredis/lib/parsers/javascript.js:60:14)
at ReplyParser.execute (/app/node_modules/ioredis/lib/parsers/javascript.js:178:20)
at Socket.<anonymous> (/app/node_modules/ioredis/lib/redis/event_handler.js:99:22)
at Socket.emit (events.js:97:17)
at readableAddChunk (_stream_readable.js:143:16)
at Socket.Readable.push (_stream_readable.js:106:10)
at TCP.onread (net.js:509:20)
默认情况下,错误堆栈没有任何意义,因为整个堆栈都发生在 ioredis 模块本身而不是代码中。因此,要找出错误在代码中的位置并不容易。 ioredis 提供了一个选项 showFriendlyErrorStack 来解决该问题。启用 showFriendlyErrorStack 时,ioredis 将为您优化错误堆栈:
const Redis = require("ioredis");
const redis = new Redis({ showFriendlyErrorStack: true });
redis.set("foo");
输出将是:
ReplyError: ERR wrong number of arguments for 'set' command
at Object.<anonymous> (/app/index.js:3:7)
at Module._compile (module.js:446:26)
at Object.Module._extensions..js (module.js:464:10)
at Module.load (module.js:341:32)
at Function.Module._load (module.js:296:12)
at Function.Module.runMain (module.js:487:10)
at startup (node.js:111:16)
at node.js:799:3
这次,堆栈告诉您错误发生在代码的第三行。
太好了!但是,优化错误堆栈会大大降低性能。因此,默认情况下,此选项是禁用的,只能用于调试目的。不建议在生产环境中使用此功能。