Redis
介绍
Redis 是使用 c 语言开发的一个高性能键值数据库。
Redis 可以通过一些键值类型来存储数据。
键值类型:
- String 字符类型
- map 散列类型
- list 列表类型
- set 集合类型
- sortedset 有序集合类型
Redis 使用起来就像是 JS 中的 Map 数据结构(但 key 只能是字符串,value 要符合支持的数据结构)。在业务中,我们可以用 Redis 来存储用户的鉴权 token,用户带着 token 访问时,可以快速从 redis 取出并对比。
适用场景
- 缓存(数据查询、短连接、新闻内容、商品内容等等)。(最多使用)
- 分布式集群架构中的 session 分离。
- 聊天室的在线好友列表。
- 任务队列。(秒杀、抢购、12306 等等)
- 应用排行榜。
- 网站访问统计。
- 数据过期处理(可以精确到毫秒)
安装(macOS)
1 | brew install redis |
启动
1 | // 前台启动redis服务器 |
连接 Redis
启动 Redis 服务后,连接 redis 数据库:
1 | // Redis命令行客户端 |
查看 Redis 状态
1 | ps -ef | grep -i redis |
停止 Redis
防止中止进程导致数据丢失,应该使用shutdown
命令停止 Redis.
1 | // 此方法关闭 redis-server启动的进程 |
另一种方法是杀进程,进程号通过查看 Redis 状态得知.
1 | kill -9 进程号 |
通用命令
1 | 返回所有KEY |
设置过期时间
1 | 为指定key设置过期时间,当key过期就删除 |
获取过期时间
1 | 返回指定key的剩余时间(秒) |
多数据库
默认分配 16 个数据库,数字 0-15.默认使用 0 数据库.数据库相对独立,可以配置参数databases
修改个数.
切换数据库[SELECT]
1 | SELECT 1 |
知识体系
多个数据库并不是完全独立.如果要不同应用存储建议开多个 Redis 实例.
数据结构
Redis 数据库支持五种数据类型。
- 字符串(string)
- 哈希(hash)
- 列表(list)
- 集合(set)
- 有序集合(sorted set)
- 位图 ( Bitmaps )
- 基数统计 ( HyperLogLogs )
5 种基础数据类型
String 字符串
String 是一组字节。在 Redis 数据库中,字符串是二进制安全的。
这意味着它们具有已知长度,并且不受任何特殊终止字符的影响。
redis 的 string 可以包含任何数据。如数字,字符串,jpg 图片或者序列化的对象。
可以在一个字符串中存储最多 512 M 的内容。
使用 SET
命令在 name
键中存储字符串 redis.com.cn,然后使用 GET
命令查询 name。
1 | SET name "redis.com.cn" |
命令使用
命令 | 简述 | 使用 |
---|---|---|
GET | 获取存储在给定键中的值 | GET name |
SET | 设置存储在给定键中的值 | SET name value |
DEL | 删除存储在给定键中的值 | DEL name |
INCR | 将键存储的值加 1 | INCR key |
DECR | 将键存储的值减 1 | DECR key |
INCRBY | 将键存储的值加上整数 | INCRBY key amount |
DECRBY | 将键存储的值减去整数 | DECRBY key amount |
命令执行
1 | 127.0.0.1:6379> set hello world |
实战场景
- 缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到 redis 中,redis 作为缓存层,mysql 做持久化层,降低 mysql 的读写压力。
- 计数器:redis 是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。
- session:常见方案 spring session + redis 实现 session 共享,
List 列表
命令使用
命令 | 简述 | 使用 |
---|---|---|
RPUSH | 将给定值推入到列表右端 | RPUSH key value |
LPUSH | 将给定值推入到列表左端 | LPUSH key value |
RPOP | 从列表的右端弹出一个值,并返回被弹出的值 | RPOP key |
LPOP | 从列表的左端弹出一个值,并返回被弹出的值 | LPOP key |
LRANGE | 获取列表在给定范围上的所有值 | LRANGE key 0 -1 |
LINDEX | 通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。 | LINDEX key index |
使用列表的技巧
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpush+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
命令执行
1 | 127.0.0.1:6379> lpush mylist 1 2 ll ls mem |
实战场景
- 微博 TimeLine: 有人发布微博,用 lpush 加入时间轴,展示新的列表信息。
- 消息队列
Set 集合
- 图例
命令使用
命令 | 简述 | 使用 |
---|---|---|
SADD | 向集合添加一个或多个成员 | SADD key value |
SCARD | 获取集合的成员数 | SCARD key |
SMEMBERS | 返回集合中的所有成员 | SMEMBERS key member |
SISMEMBER | 判断 member 元素是否是集合 key 的成员 | SISMEMBER key member |
其它一些集合操作,请参考这里https://www.runoob.com/redis/redis-sets.html
命令执行
1 | 127.0.0.1:6379> sadd myset hao hao1 xiaohao hao |
实战场景
- 标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
- 点赞,或点踩,收藏等,可以放到 set 中实现
Hash 散列
- 图例
命令使用
命令 | 简述 | 使用 |
---|---|---|
HSET | 添加键值对 | HSET hash-key sub-key1 value1 |
HGET | 获取指定散列键的值 | HGET hash-key key1 |
HGETALL | 获取散列中包含的所有键值对 | HGETALL hash-key |
HDEL | 如果给定键存在于散列中,那么就移除这个键 | HDEL hash-key sub-key1 |
命令执行
1 | 127.0.0.1:6379> hset user name1 hao |
实战场景
- 缓存: 能直观,相比 string 更节省空间,的维护缓存信息,如用户信息,视频信息等。
Zset 有序集合
- 压缩列表(ziplist): ziplist 是为了提高存储效率而设计的一种特殊编码的双向链表。它可以存储字符串或者整数,存储整数时是采用整数的二进制而不是字符串形式存储。它能在 O(1)的时间复杂度下完成 list 两端的 push 和 pop 操作。但是因为每次操作都需要重新分配 ziplist 的内存,所以实际复杂度和 ziplist 的内存使用量相关
- 跳跃表(zSkiplist): 跳跃表的性能可以保证在查找,删除,添加等操作的时候在对数期望时间内完成,这个性能是可以和平衡树来相比较的,而且在实现方面比平衡树要优雅,这是采用跳跃表的主要原因。跳跃表的复杂度是 O(log(n))。
- 图例
命令使用
命令 | 简述 | 使用 |
---|---|---|
ZADD | 将一个带有给定分值的成员添加到有序集合里面 | ZADD zset-key 178 member1 |
ZRANGE | 根据元素在有序集合中所处的位置,从有序集合中获取多个元素 | ZRANGE zset-key 0-1 withccores |
ZREM | 如果给定元素成员存在于有序集合中,那么就移除这个元素 | ZREM zset-key member1 |
更多命令请参考这里 https://www.runoob.com/redis/redis-sorted-sets.html
命令执行
1 | 127.0.0.1:6379> zadd myscoreset 100 hao 90 xiaohao |
实战场景
- 排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。
3 种特殊类型
HyperLogLogs(基数统计)
- 什么是基数?
举个例子,A = {1, 2, 3, 4, 5}, B = {3, 5, 6, 7, 9};那么基数(不重复的元素)= 1, 2, 4, 6, 7, 9; (允许容错,即可以接受一定误差)
- HyperLogLogs 基数统计用来解决什么问题?
这个结构可以非常省内存的去统计各种计数,比如注册 IP 数、每日访问 IP 数、页面实时 UV、在线用户数,共同好友数等。
- 它的优势体现在哪?
一个大型的网站,每天 IP 比如有 100 万,粗算一个 IP 消耗 15 字节,那么 100 万个 IP 就是 15M。而 HyperLogLog 在 Redis 中每个键占用的内容都是 12K,理论存储近似接近 2^64 个值,不管存储的内容是什么,它一个基于基数估算的算法,只能比较准确的估算出基数,可以使用少量固定的内存去存储并识别集合中的唯一元素。而且这个估算的基数并不一定准确,是一个带有 0.81% 标准错误的近似值(对于可以接受一定容错的业务场景,比如 IP 数统计,UV 等,是可以忽略不计的)。
- 相关命令使用
1 | 127.0.0.1:6379> pfadd key1 a b c d e f g h i # 创建第一组元素 |
Bitmap (位存储)
- 用来解决什么问题?
比如:统计用户信息,活跃,不活跃! 登录,未登录! 打卡,不打卡! 两个状态的,都可以使用 Bitmaps!
如果存储一年的打卡状态需要多少内存呢? 365 天 = 365 bit 1 字节 = 8bit 46 个字节左右!
- 相关命令使用
使用 bitmap 来记录 周一到周日的打卡! 周一:1 周二:0 周三:0 周四:1 ……
1 | 127.0.0.1:6379> setbit sign 0 1 |
查看某一天是否有打卡!
1 | 127.0.0.1:6379> getbit sign 3 |
统计操作,统计 打卡的天数!
1 | 127.0.0.1:6379> bitcount sign # 统计这周的打卡记录,就可以看到是否有全勤! |
geospatial (地理位置)
geoadd(添加地理位置)
规则
两级无法直接添加,我们一般会下载城市数据(这个网址可以查询 GEO: http://www.jsons.cn/lngcode)!
- 有效的经度从-180 度到 180 度。
- 有效的纬度从-85.05112878 度到 85.05112878 度。
1 | 当坐标位置超出上述指定范围时,该命令将会返回一个错误。 |
geopos(获取指定的成员的经度和纬度)
1 | 127.0.0.1:6379> geopos china:city taiyuan manjing |
获得当前定位, 一定是一个坐标值!
geodist
如果不存在, 返回空
单位如下
- m
- km
- mi 英里
- ft 英尺
1 | 127.0.0.1:6379> geodist china:city taiyuan shenyang m |
georadius
附近的人 ==> 获得所有附近的人的地址, 定位, 通过半径来查询
获得指定数量的人
1 | 127.0.0.1:6379> georadius china:city 110 30 1000 km 以 100,30 这个坐标为中心, 寻找半径为1000km的城市 |
参数 key 经度 纬度 半径 单位 [显示结果的经度和纬度] [显示结果的距离] [显示的结果的数量]
georadiusbymember
显示与指定成员一定半径范围内的其他成员
1 | 127.0.0.1:6379> georadiusbymember china:city taiyuan 1000 km |
参数与 georadius 一样
geohash(较少使用)
该命令返回 11 个字符的 hash 字符串
1 | 127.0.0.1:6379> geohash china:city taiyuan shenyang |
将二维的经纬度转换为一维的字符串, 如果两个字符串越接近, 则距离越近
底层
geo 底层的实现原理实际上就是 Zset, 我们可以通过 Zset 命令来操作 geo
1 | 127.0.0.1:6379> type china:city |
查看全部元素 删除指定的元素
1 | 127.0.0.1:6379> zrange china:city 0 -1 withscores |
Stream 消息队列
为什么会设计 Stream
- PUB/SUB,订阅/发布模式
- 但是发布订阅模式是无法持久化的,如果出现网络断开、Redis 宕机等,消息就会被丢弃;
- 基于List LPUSH+BRPOP 或者 基于 Sorted-Set的实现
- 支持了持久化,但是不支持多播,分组消费等
为什么上面的结构无法满足广泛的 MQ 场景? 这里便引出一个核心的问题:如果我们期望设计一种数据结构来实现消息队列,最重要的就是要理解设计一个消息队列需要考虑什么?初步的我们很容易想到
- 消息的生产
- 消息的消费
- 单播和多播(多对多)
- 阻塞和非阻塞读取
- 消息有序性
- 消息的持久化
其它还要考虑啥嗯?借助美团技术团队的一篇文章,消息队列设计精要(opens new window) 中的图
我们不妨看看 Redis 考虑了哪些设计?
- 消息 ID 的序列化生成
- 消息遍历
- 消息的阻塞和非阻塞读取
- 消息的分组消费
- 未完成消息的处理
- 消息队列监控
- …这也是我们需要理解Stream的点,但是结合上面的图,我们也应该理解Redis Stream也是一种超轻量MQ并没有完全实现消息队列所有设计要点,这决定着它适用的场景。详细内容建议看这篇[Stream详细用法](https://pdai.tech/md/db/nosql-redis/db-redis-data-type-stream.html)
事务
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
Redis 事务相关命令和使用
- MULTI :开启事务,redis 会将后续的命令逐个放入队列中,然后使用 EXEC 命令来原子化执行这个命令系列。
- EXEC:执行事务中的所有操作命令。
- DISCARD:取消事务,放弃执行事务块中的所有命令。
- WATCH:监视一个或多个 key,如果事务在执行前,这个 key(或多个 key)被其他命令修改,则事务被中断,不会执行事务中的任何命令。
- UNWATCH:取消 WATCH 对所有 key 的监视。
标准的事务执行
给 k1、k2 分别赋值,在事务中修改 k1、k2,执行事务后,查看 k1、k2 值都被修改。
1 | 127.0.0.1:6379> set k1 v1 |
事务取消
1 | 127.0.0.1:6379> MULTI |
事务出现错误的处理
- 语法错误(编译器错误)
在开启事务后,修改 k1 值为 11,k2 值为 22,但 k2 语法错误,最终导致事务提交失败,k1、k2 保留原值。
1 | 127.0.0.1:6379> set k1 v1 |
- Redis 类型错误(运行时错误)
在开启事务后,修改 k1 值为 11,k2 值为 22,但将 k2 的类型作为 List,在运行时检测类型错误,最终导致事务提交失败,此时事务并没有回滚,而是跳过错误命令继续执行, 结果 k1 值改变、k2 保留原值
1 | 127.0.0.1:6379> set k1 v1 |
WATCH 命令
WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。
- CAS? 乐观锁?Redis 官方的例子帮你理解
被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回 nil-reply 来表示事务已经失败。
举个例子, 假设我们需要原子性地为某个值进行增 1 操作(假设 INCR 不存在)。
首先我们可能会这样做:
1 | val = GET mykey |
上面的这个实现在只有一个客户端的时候可以执行得很好。 但是, 当多个客户端同时对同一个键进行这样的操作时, 就会产生竞争条件。举个例子, 如果客户端 A 和 B 都读取了键原来的值, 比如 10 , 那么两个客户端都会将键的值设为 11 , 但正确的结果应该是 12 才对。
有了 WATCH ,我们就可以轻松地解决这类问题了:
1 | WATCH mykey |
使用上面的代码, 如果在 WATCH 执行之后, EXEC 执行之前, 有其他客户端修改了 mykey 的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。
这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。
- watch 是如何监视实现的呢?
Redis 使用 WATCH 命令来决定事务是继续执行还是回滚,那就需要在 MULTI 之前使用 WATCH 来监控某些键值对,然后使用 MULTI 命令来开启事务,执行对数据结构操作的各种命令,此时这些命令入队列。
当使用 EXEC 执行事务时,首先会比对 WATCH 所监控的键值对,如果没发生改变,它会执行事务队列中的命令,提交事务;如果发生变化,将不会执行事务中的任何命令,同时事务回滚。当然无论是否回滚,Redis 都会取消执行事务前的 WATCH 命令。
- watch 命令实现监视
在事务开始前用 WATCH 监控 k1,之后修改 k1 为 11,说明事务开始前 k1 值被改变,MULTI 开始事务,修改 k1 值为 12,k2 为 22,执行 EXEC,发回 nil,说明事务回滚;查看下 k1、k2 的值都没有被事务中的命令所改变。
1 | 127.0.0.1:6379> set k1 v1 |
- unwatch 取消监视
1 | 127.0.0.1:6379> set k1 v1 |
为什么 Redis 不支持回滚?
如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。
以下是这种做法的优点:
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。
如何理解 Redis 与事务的 ACID?
持久化
- 为什么需要持久化?
Redis 是个基于内存的数据库。那服务一旦宕机,内存中的数据将全部丢失。通常的解决方案是从后端数据库恢复这些数据,但后端数据库有性能瓶颈,如果是大数据量的恢复,1、会对数据库带来巨大的压力,2、数据库的性能不如 Redis。导致程序响应慢。所以对 Redis 来说,实现数据的持久化,避免从后端数据库中恢复数据,是至关重要的。
RDB 持久化
RDB 就是 Redis DataBase 的缩写,中文名为快照/内存快照,RDB 持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值。
触发方式
触发 rdb 持久化的方式有 2 种,分别是手动触发和自动触发。
手动触发
手动触发分别对应 save 和 bgsave 命令
自动触发
在以下 4 种情况时会自动触发
- redis.conf 中配置 save m n,即在 m 秒内有 n 次修改时,自动触发 bgsave 生成 rdb 文件;
- 主从复制时,从节点要从主节点进行全量复制时也会触发 bgsave 操作,生成当时的快照发送到从节点;
- 执行 debug reload 命令重新加载 redis 时也会触发 bgsave 操作;
- 默认情况下执行 shutdown 命令时,如果没有开启 aof 持久化,那么也会触发 bgsave 操作;
redis.conf 中配置 RDB
快照周期:内存快照虽然可以通过技术人员手动执行 SAVE 或 BGSAVE 命令来进行,但生产环境下多数情况都会设置其周期性执行条件。
- Redis 中默认的周期新设置
1 | 周期性执行条件的设置格式为 |
以上三项默认信息设置代表的意义是:
- 如果 900 秒内有 1 条 Key 信息发生变化,则进行快照;
- 如果 300 秒内有 10 条 Key 信息发生变化,则进行快照;
- 如果 60 秒内有 10000 条 Key 信息发生变化,则进行快照。读者可以按照这个规则,根据自己的实际请求压力进行设置调整。
- 其它相关配置
1 | 文件名称 |
dbfilename: RDB 文件在磁盘上的名称。
dir: RDB 文件的存储路径。默认设置为“./”,也就是 Redis 服务的主目录。
stop-writes-on-bgsave-error:上文提到的在快照进行过程中,主进程照样可以接受客户端的任何写操作的特性,是指在快照操作正常的情况下。如果快照操作出现异常(例如操作系统用户权限不够、磁盘空间写满等等)时,Redis 就会禁止写操作。这个特性的主要目的是使运维人员在第一时间就发现 Redis 的运行错误,并进行解决。一些特定的场景下,您可能需要对这个特性进行配置,这时就可以调整这个参数项。该参数项默认情况下值为 yes,如果要关闭这个特性,指定即使出现快照错误 Redis 一样允许写操作,则可以将该值更改为 no。
rdbcompression:该属性将在字符串类型的数据被快照到磁盘文件时,启用 LZF 压缩算法。Redis 官方的建议是请保持该选项设置为 yes,因为“it’s almost always a win”。
rdbchecksum:从 RDB 快照功能的 version 5 版本开始,一个 64 位的 CRC 冗余校验编码会被放置在 RDB 文件的末尾,以便对整个 RDB 文件的完整性进行验证。这个功能大概会多损失 10%左右的性能,但获得了更高的数据可靠性。所以如果您的 Redis 服务需要追求极致的性能,就可以将这个选项设置为 no。
RDB 优缺点
- 优点
- RDB 文件是某个时间节点的快照,默认使用 LZF 算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景;
- Redis 加载 RDB 文件恢复数据要远远快于 AOF 方式;
- 缺点
- RDB 方式实时性不够,无法做到秒级的持久化;
- 每次调用 bgsave 都需要 fork 子进程,fork 子进程属于重量级操作,频繁执行成本较高;
- RDB 文件是二进制的,没有可读性,AOF 文件在了解其结构的情况下可以手动修改或者补全;
- 版本兼容 RDB 文件问题;
针对 RDB 不适合实时持久化的问题,Redis 提供了 AOF 持久化方式来解决
AOF 持久化
Redis 是“写后”日志,Redis 先执行命令,把数据写入内存,然后才记录日志。日志里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存。PS: 大多数的数据库采用的是写前日志(WAL),例如 MySQL,通过写前日志和两阶段提交,实现数据和逻辑的一致性。
而 AOF 日志采用写后日志,即先写内存,后写日志。
为什么采用写后日志?
Redis 要求高性能,采用写日志有两方面好处:
- 避免额外的检查开销:Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。
- 不会阻塞当前的写操作
但这种方式存在潜在风险:
- 如果命令执行完成,写日志之前宕机了,会丢失数据。
- 主线程写磁盘压力大,导致写盘慢,阻塞后续操作。
如何实现 AOF
AOF 日志记录 Redis 的每个写命令,步骤分为:命令追加(append)、文件写入(write)和文件同步(sync)。
- 命令追加 当 AOF 持久化功能打开了,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器的 aof_buf 缓冲区。
- 文件写入和同步 关于何时将 aof_buf 缓冲区的内容写入 AOF 文件中,Redis 提供了三种写回策略
Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
- 三种写回策略的优缺点
上面的三种写回策略体现了一个重要原则:trade-off,取舍,指在性能和可靠性保证之间做取舍。
redis.conf 中配置 AOF
默认情况下,Redis 是没有开启 AOF 的,可以通过配置 redis.conf 文件来开启 AOF 持久化,关于 AOF 的配置如下:
1 | appendonly参数开启AOF持久化 |
以下是 Redis 中关于 AOF 的主要配置信息:
appendonly:默认情况下 AOF 功能是关闭的,将该选项改为 yes 以便打开 Redis 的 AOF 功能。
appendfilename:这个参数项很好理解了,就是 AOF 文件的名字。
appendfsync:这个参数项是 AOF 功能最重要的设置项之一,主要用于设置“真正执行”操作命令向 AOF 文件中同步的策略。
什么叫“真正执行”呢?还记得 Linux 操作系统对磁盘设备的操作方式吗? 为了保证操作系统中 I/O 队列的操作效率,应用程序提交的 I/O 操作请求一般是被放置在 linux Page Cache 中的,然后再由 Linux 操作系统中的策略自行决定正在写到磁盘上的时机。而 Redis 中有一个 fsync()函数,可以将 Page Cache 中待写的数据真正写入到物理设备上,而缺点是频繁调用这个 fsync()函数干预操作系统的既定策略,可能导致 I/O 卡顿的现象频繁 。
从持久化中恢复数据
其实想要从这些文件中恢复数据,只需要重新启动 Redis 即可.
加载步骤
- redis 重启时判断是否开启 aof,如果开启了 aof,那么就优先加载 aof 文件;
- 如果 aof 存在,那么就去加载 aof 文件,加载成功的话 redis 重启成功,如果 aof 文件加载失败,那么会打印日志表示启动失败,此时可以去修复 aof 文件后重新启动;
- 若 aof 文件不存在,那么 redis 就会转而去加载 rdb 文件,如果 rdb 文件不存在,redis 直接启动成功;
- 如果 rdb 文件存在就会去加载 rdb 文件恢复数据,如加载失败则打印日志提示启动失败,如加载成功,那么 redis 重启成功,且使用 rdb 文件恢复数据;
那么为什么会优先加载 AOF 呢?因为 AOF 保存的数据更完整,通过上面的分析我们知道 AOF 基本上最多损失 1s 的数据。
Node 中使用 Redis
推荐模块ioredis
.
1 | const ioredis = require("ioredis"); |
使用 pipeline 管道
存在多条命令时,使用pipeline
可以提高效率.使用exec
结束 pipeline 就会给数据库发送请求.
1 | async function main() { |
每个链式命令也可以有一个回调,当命令得到回复时将调用它
1 | redis |
除了单独向管道队列添加命令外,还可以将一组命令和参数传递给构造函数:
1 | redis |
查看管道中有多少命令
1 | redis.pipeline().set("foo", "bar").get("foo").length; // 2 |
事务
调用multi
时会自动创建pipeline
.
1 | async function main() { |
错误调试
showFriendlyErrorStack
: 设置为 true 会在报错时提供错误信息.建议只在调试时开启.