Redis-基础
1. 简介
redis是一个key-value的存储系统。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave同步。
1.1 数据结构
Redis可以存储键与5种不同数据结构类型之间的映射,这5种数据结构类型分别为String(字符串)、List(列表)、Set(集合)、Hash(散列)和 Zset(有序集合)。
这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。
1.2 为什么快
- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)。
- 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的。
- 采用单进程单线程(这也解释为什么redis可以处理高并发问题),避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
- 使用多路I/O复用模型。
- 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
2. 安装
redis 安装很简单,只要下载好,直接运行即可。官方下载地址
这里主要讲下docker-compose安装
docker-compose.yaml
配置信息如下
version: '3.7'
services:
redis:
# 容器名称
container_name: redis
# 镜像 默认redis:latest
image: redis
# 自启动
restart: always
# 覆盖dockerfile中的cmd名称
# 启动redis并开启AOF模式
command: ["redis-server", "--appendonly", "yes"]
# 宿主机端口6379:容器默认端口6379
ports:
- "6379:6379"
# 文件映射
volumes:
- ./data:/data
我们可以使用redis桌面工具Redis Desktop Manager
连接安装启动好的redis server进行测试。
创建一个新的链接,因为安装的时候并没有开启认证,所以仅需要输入地址和端口即可。
3. 常用命令
由于redis命令太多了 这里主要讲一下常用的命令。
我们可以随便进一个database进行练习
3.1 keys
序号 | 命令 | 描述 |
---|---|---|
1 | DEL key | 该命令用于在 key 存在时删除 key。 |
2 | EXISTS key | 检查给定 key 是否存在。 |
3 | EXPIRE key seconds | 为给定 key 设置过期时间,以秒计。 |
4 | EXPIREAT key timestamp | 为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 |
5 | PEXPIRE key milliseconds | 设置 key 的过期时间以毫秒计。 |
6 | PEXPIREAT key milliseconds-timestamp | 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计7 |
7 | KEYS pattern | 查找所有符合给定模式( pattern)的 key 。 |
8 | MOVE key db | 将当前数据库的 key 移动到给定的数据库 db 当中。 |
9 | PERSIST key | 移除 key 的过期时间,key 将持久保持。 |
10 | PTTL key | 以毫秒为单位返回 key 的剩余的过期时间。 |
11 | TTL key | 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 |
12 | RENAME key newkey | 修改 key 的名称 |
13 | TYPE key | 返回 key 所储存的值的类型。 |
3.2 string
序号 | 命令 | 描述 |
---|---|---|
1 | SET key value | 设置指定 key 的值 |
2 | GET key | 获取指定 key 的值。 |
3 | GETRANGE key start end | 返回 key 中字符串值的子字符 |
4 | GETSET key value | 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
5 | MGET key1 key2.. | 获取所有(一个或多个)给定 key 的值。 |
6 | SETEX key seconds value | 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
7 | SETNX key value | 只有在 key 不存在时设置 key 的值。 |
8 | STRLEN key | 返回 key 所储存的字符串值的长度。 |
9 | MSET key value [key value ...] | 同时设置一个或多个 key-value 对。 |
10 | PSETEX key milliseconds value | 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。 |
11 | INCR key | 将 key 中储存的数字值增一。 |
12 | INCRBY key increment | 将 key 所储存的值加上给定的增量值(increment)。 |
13 | INCRBYFLOAT key increment | 将 key 所储存的值加上给定的浮点增量值(increment) 。 |
14 | DECR key | 将 key 中储存的数字值减一。 |
15 | DECRBY key decrement | key 所储存的值减去给定的减量值(decrement) 。 |
3.3 hash
序号 | 命令 | 描述 |
---|---|---|
1 | HDEL key field1 [field2] | 删除一个或多个哈希表字段 |
2 | HEXISTS key field | 查看哈希表 key 中,指定的字段是否存在。 |
3 | HGET key field | 获取存储在哈希表中指定字段的值。 |
4 | HGETALL key | 获取在哈希表中指定 key 的所有字段和对应的值 |
5 | HINCRBY key field increment | 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
6 | HINCRBYFLOAT key field increment | 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
7 | HKEYS key | 获取所有哈希表中的字段 |
8 | HLEN key | 获取哈希表中字段的数量 |
9 | HMGET key field1 [field2] | 获取所有给定字段的值 |
10 | HMSET key field1 value1 [field2 value2 ] | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
11 | HSET key field value | 将哈希表 key 中的字段 field 的值设为 value 。 |
12 | HSETNX key field value | 只有在字段 field 不存在时,设置哈希表字段的值。 |
13 | HVALS key | 获取哈希表中所有值。 |
3.4 list
序号 | 命令及描述 | 描述 |
---|---|---|
1 | BLPOP key1 [key2 ] timeout | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
2 | BRPOP key1 [key2 ] timeout | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
3 | LINDEX key index] | 通过索引获取列表中的元素 |
4 | LINSERT key BEFORE|AFTER pivot value | 在列表的元素前或者后插入元素 |
5 | LLEN key | 获取列表长度 |
6 | LPOP key | 移出并获取列表的第一个元素 |
7 | LPUSH key value1 [value2] | 将一个或多个值插入到列表头部 |
8 | LPUSHX key value | 将一个值插入到已存在的列表头部 |
9 | LRANGE key start stop | 获取列表指定范围内的元素 |
10 | LREM key count value | 移除列表元素 |
11 | LSET key index value | 通过索引设置列表元素的值 |
12 | LTRIM key start stop | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
13 | RPOP key | 移除列表的最后一个元素,返回值为移除的元素。 |
14 | RPUSH key value1 [value2] | 在列表中添加一个或多个值 |
15 | RPUSHX key value | 为已存在的列表添加值 |
3.5 set
序号 | 命令 | 描述 |
---|---|---|
1 | SADD key member1 [member2] | 向集合添加一个或多个成员 |
2 | SCARD key | 获取集合的成员数 |
3 | SDIFF key1 [key2] | 返回第一个集合与其他集合之间的差异。 |
4 | SDIFFSTORE destination key1 [key2] | 返回给定所有集合的差集并存储在 destination 中 |
5 | SINTER key1 [key2] | 返回给定所有集合的交集 |
6 | SINTERSTORE destination key1 [key2] | 返回给定所有集合的交集并存储在 destination 中 |
7 | SISMEMBER key member | 判断 member 元素是否是集合 key 的成员 |
8 | SMEMBERS key | 返回集合中的所有成员 |
9 | SPOP key | 移除并返回集合中的一个随机元素 |
10 | SRANDMEMBER key [count] | 返回集合中一个或多个随机数 |
11 | SREM key member1 [member2] | 移除集合中一个或多个成员 |
12 | SUNION key1 [key2] | 返回所有给定集合的并集 |
13 | SUNIONSTORE destination key1 [key2] | 所有给定集合的并集存储在 destination 集合中 |
3.6 sorted set
序号 | 命令 | 描述 |
---|---|---|
1 | ZADD key score1 member1 [score2 member2] | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
2 | ZCARD key | 获取有序集合的成员数 |
3 | ZCOUNT key min max | 计算在有序集合中指定区间分数的成员数 |
4 | ZINCRBY key increment member | 有序集合中对指定成员的分数加上增量 increment |
5 | ZINTERSTORE destination numkeys key [key ...] | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination 中 |
6 | ZLEXCOUNT key min max | 在有序集合中计算指定字典区间内成员数量 |
7 | ZRANGE key start stop [WITHSCORES] | 通过索引区间返回有序集合指定区间内的成员 |
8 | ZRANGEBYLEX key min max [LIMIT offset count] | 通过字典区间返回有序集合的成员 |
9 | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] | 通过分数返回有序集合指定区间内的成员 |
10 | ZRANK key member | 返回有序集合中指定成员的索引 |
11 | ZREM key member [member ...] | 移除有序集合中的一个或多个成员 |
12 | ZREMRANGEBYLEX key min max | 移除有序集合中给定的字典区间的所有成员 |
13 | ZREMRANGEBYRANK key start stop | 移除有序集合中给定的排名区间的所有成员 |
14 | ZREMRANGEBYSCORE key min max | 移除有序集合中给定的分数区间的所有成员 |
15 | ZREVRANGE key start stop [WITHSCORES] | 返回有序集中指定区间内的成员,通过索引,分数从高到低 |
16 | ZREVRANGEBYSCORE key max min [WITHSCORES] | 返回有序集中指定分数区间内的成员,分数从高到低排序 |
17 | ZREVRANK key member | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
18 | ZSCORE key member | 返回有序集中,成员的分数值 |
19 | ZUNIONSTORE destination numkeys key [key ...] | 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
20 | ZSCAN key cursor [MATCH pattern] [COUNT count] | 迭代有序集合中的元素(包括元素成员和元素分值) |
4. key过期策略
key 以两种方式过期:被动方式(惰性过期)和主动方式(定期过期)。
4.1 惰性过期
只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
4.2 定期过期
redis会每隔一定的时间,从expires字典中扫描一定数量的key,并清除其中已过期的key。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
Redis 会定期在具有过期设置的key中随机测试一些键。所有已经过期的key都从key空间中删除。
具体来说,Redis 每秒执行 10 次(100ms一次):
- 从具有关联过期的key集中测试 20 个随机key。
- 删除所有发现过期的key。
- 如果超过 25% 的key已过期,将从步骤 1 重新开始。
5. 驱逐key
当 Redis 用作缓存时,通常可以方便地让它在用户添加新数据时自动驱逐旧数据。
LRU (Least Recently Used)
实际上只是支持的驱逐方法之一。 Redis 使用的 LRU 算法,实际上是精确 LRU 的近似值。
从 Redis 4.0 版开始,引入了新的 LFU(Least Frequently Used)
驱逐策略。
5.1 配置指令
我们可以通过redis命令查看当前的驱逐策略,可以看出当前默认的驱逐策略为noeviction
127.0.0.1:6379> config get maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"
查看当前的maxmemory
设置,可以看出当前的设置为0
,相当于没有限制
127.0.0.1:6379> config get maxmemory
1) "maxmemory"
2) "0"
当然,如果要设置这些参数,可以使用命令或者直接修改配置文件redis.conf
例如,为了配置 100 兆字节的内存限制,可以在redis.conf
文件中使用以下指令。
maxmemory 100mb
5.2 驱逐策略
-
noeviction
:当内存使用超过配置的时候会返回错误,不会驱逐任何key。 -
allkeys-lru
:通过首先尝试删除最近较少使用的 (LRU) key,以便为添加的新数据腾出空间。 -
volatile-lru
:通过首先尝试删除最近较少使用的 (LRU)key,但仅限于具有过期时间的key,以便为添加的新数据腾出空间。 -
allkeys-random
:随机驱逐key以便为添加的新数据腾出空间。 -
volatile-random
:随机驱逐key以便为添加的新数据腾出空间,但仅驱逐具有过期时间的key。 -
volatile-ttl
:驱逐具有过期时间的key,并尝试首先驱逐具有较短生存时间(TTL)的key,以便为添加的新数据腾出空间。 -
allkeys-lfu
:通过首先尝试删除使用频率最少的key,以便为添加的新数据腾出空间。 -
volatile-lfu
:通过首先尝试删除使用频率最少的key,但仅限于具有过期时间的key,以便为添加的新数据腾出空间。
7和8从 Redis 4.0 版开始引入
如何抉择:
如果没有与先决条件匹配的驱逐键,则策略volatile-lru、volatile-random和volatile-ttl 的行为类似于noeviction。
根据应用程序的访问模式选择正确的驱逐策略很重要,也可以在应用程序运行时重新配置策略,并使用 Redis INFO输出监控缓存未命中和命中的数量以调整当前的设置.
一般来说,作为一个经验法则:
- 当期望请求的流行度呈幂律分布时,请使用allkeys-lru策略,也就是说,如果期望元素子集的访问频率远高于其他元素。如果您不确定,这是一个不错的选择。
- 如果当前有一个循环访问,其中所有的键都被连续扫描,或者希望分布均匀(所有元素可能以相同的概率访问)时,请使用allkeys-random。
- 如果希望能够通过在创建缓存对象时使用不同的 TTL 值来向 Redis 提供关于什么是到期的良好候选者的提示,请使用volatile-ttl。
该volatile-lru和volatile-random,当你想用一个实例两个缓存,并拥有一套永久键的策略是有用的。然而,运行两个 Redis 实例来解决这样的问题通常是一个更好的主意。
还值得注意的是,为键设置过期会消耗内存,因此使用像allkeys-lru这样的策略会提高内存效率,因为无需为键在内存压力下被逐出设置过期。
5.3 驱逐程序如何运作
重要的是要了解驱逐过程的工作方式如下:
- 客户端运行新命令,导致添加更多数据。
- Redis 检查内存使用情况,如果大于
maxmemory
限制,则根据策略驱逐键。 - 执行新命令,等等。
所以我们不断地越过内存限制的边界,越过它,然后通过驱逐键返回到限制之下。
如果某个命令导致使用大量内存(例如存储到新密钥中的大集合交集)一段时间,则内存限制可能会明显超出。
5.4 新生key策略
另外一个问题是,当创建新对象的时候,对象的使用计数如果为0,很容易就会被淘汰掉(LFU),还需要为新生key设置一个初始counter。counter会被初始化为LFU_INIT_VAL,默认5。
6. 持久化
Redis 提供了一系列不同的持久性选项:
RDB(Redis Database)
:RDB 持久性以指定的时间间隔执行数据集的时间点快照。AOF(Append Only File)
:AOF 持久化记录服务器收到的每个写操作,在服务器启动时会再次播放,重建原始数据集。命令使用与 Redis 协议本身相同的格式以仅附加的方式记录。当日志变得太大时,Redis 能够在后台重写日志。无持久性
:如果希望数据在服务器运行时一直存在,您可以完全禁用持久性。RDB + AOF
:可以在同一个实例中组合 AOF 和 RDB。请注意,在这种情况下,当 Redis 重新启动时,AOF 文件将用于重建原始数据集,因为它保证是最完整的。
要理解的最重要的事情是 RDB 和 AOF 持久性之间的不同权衡。让我们从 RDB 开始:
6.1 RDB优势
- RDB 是 Redis 数据的非常紧凑的单文件时间点表示。RDB 文件非常适合备份。例如,您可能希望在最近 24 小时内每小时存档一次 RDB 文件,并在 30 天内每天保存一个 RDB 快照。这使您可以在发生灾难时轻松恢复不同版本的数据集。
- RDB 非常适合灾难恢复,它是一个可以传输到远程数据中心或 Amazon S3。
- RDB 最大限度地提高了 Redis 的性能,因为 Redis 父进程为了持久化需要做的唯一工作是派生一个将完成所有其余工作的子进程。父实例永远不会执行磁盘 I/O 或类似操作。
- 与 AOF 相比,RDB 允许更快地重新启动大数据集。
- 在副本上,RDB 支持重启和故障转移后的部分重新同步。
6.2 RDB 的缺点
- 如果您需要在 Redis 停止工作(例如断电后)时将数据丢失的可能性降至最低,那么 RDB 并不好。您可以在生成 RDB 的地方配置不同的保存点(例如,在对数据集进行至少 5 分钟和 100 次写入之后,但您可以有多个保存点)。但是,您通常会每五分钟或更长时间创建一个 RDB 快照,因此,如果 Redis 因任何原因在没有正确关闭的情况下停止工作,您应该准备好丢失最近几分钟的数据。
- RDB 经常需要
fork()
以便使用子进程在磁盘上持久化。如果数据集很大,fork()
可能会很耗时,如果数据集很大且 CPU 性能不是很好,可能会导致 Redis 停止为客户端服务几毫秒甚至一秒钟。AOF 也需要fork()
但你可以调整你想要重写日志的频率,而不会对持久性进行任何权衡。
6.3 AOF优势
- 使用 AOF Redis 更持久:你可以有不同的 fsync 策略:根本没有 fsync,每秒 fsync,每次查询 fsync。使用 fsync 每秒写入性能的默认策略仍然很棒(fsync 是使用后台线程执行的,当没有 fsync 正在进行时,主线程将努力执行写入。)但您只能丢失一秒钟的写入。
- AOF 日志是仅附加日志,因此在断电时不会出现寻道或损坏问题。即使日志由于某种原因(磁盘已满或其他原因)以半写命令结束,redis-check-aof 工具也能够轻松修复它。
- 当 AOF 变得太大时,Redis 能够在后台自动重写。重写是完全安全的,因为当 Redis 继续追加到旧文件时,会使用创建当前数据集所需的最少操作集生成一个全新的文件,一旦第二个文件准备就绪,Redis 就会切换这两个文件并开始追加到新的那一个。
- AOF 以易于理解和解析的格式包含所有操作的日志。您甚至可以轻松导出 AOF 文件。例如,即使您不小心使用FLUSHALL命令刷新了所有内容,只要在此期间没有重写日志,您仍然可以通过停止服务器、删除最新命令并重新启动 Redis 来再次保存您的数据集。
6.4 AOF的缺点
- AOF 文件通常比相同数据集的等效 RDB 文件大。
- AOF 可能比 RDB 慢,具体取决于确切的 fsync 策略。一般来说,将 fsync 设置为每秒性能仍然非常高,并且禁用 fsync 后,即使在高负载下,它也应该与 RDB 一样快。即使在写入负载巨大的情况下,RDB 仍然能够提供更多关于最大延迟的保证。
- 过去我们在特定命令中遇到过罕见的错误(例如有一个涉及阻塞命令的错误,如BRPOPLPUSH) 导致生成的 AOF 在重新加载时无法重现完全相同的数据集。这些错误很少见,我们在测试套件中进行了测试,自动创建随机复杂数据集并重新加载它们以检查一切正常。但是,使用 RDB 持久性几乎不可能出现此类错误。为了更清楚地说明这一点:Redis AOF 通过增量更新现有状态来工作,就像 MySQL 或 MongoDB 那样,而 RDB 快照一次又一次地从头开始创建所有内容,这在概念上更加健壮。但是 - 1)需要注意的是,每次Redis重写AOF时,都是从数据集中包含的实际数据开始重新创建,与始终附加的 AOF 文件(或重写读取旧 AOF 而不是读取内存中的数据)相比,对错误的抵抗力更强。2) 我们从未收到过用户关于在现实世界中检测到的 AOF 损坏的单一报告。
6.5 如何抉择?
一般的迹象是,如果您想要与 PostgreSQL 可以提供的数据安全程度相当的数据安全性,则应该同时使用这两种持久性方法。
如果您非常关心您的数据,但在发生灾难时仍然可以忍受几分钟的数据丢失,您可以简单地单独使用 RDB。
有很多用户单独使用 AOF,但我们不鼓励它,因为不时拥有 RDB 快照对于进行数据库备份、更快地重新启动以及在 AOF 引擎中出现错误时是一个好主意。
注意:由于所有这些原因,我们很可能在未来将 AOF 和 RDB 统一为一个持久化模型(长期计划)。
以下部分将说明有关这两种持久性模型的更多细节。
6.6 RDB如何运作的
默认情况下,Redis 将数据集的快照保存在磁盘上的一个名为dump.rdb
. 如果数据集中至少有 M 次更改,您可以将 Redis 配置为每 N 秒保存一次数据集,或者您可以手动调用SAVE或BGSAVE命令。
例如,如果至少有 1000 个键更改,此配置将使 Redis 每 60 秒自动将数据集转储到磁盘:
save 60 1000
这种策略被称为快照。
具体操作步骤如下:
每当 Redis 需要将数据集转储到磁盘时,就会发生以下情况:
- Redis forks。我们现在有一个子进程和一个父进程。
- 子进程开始将数据集写入临时 RDB 文件。
- 当子进程写完新的 RDB 文件时,它会替换旧的。
这种方法允许 Redis 从写时复制语义中受益。
6.7 AOF 如何运作的
快照不是很持久。如果您运行Redis的计算机停止运行,您的电源线发生故障,或者您不小心kill -9
您的实例,Redis上写入的最新数据将丢失。虽然这对于某些应用程序来说可能不是什么大问题,但存在完全持久性的用例,在这些情况下,Redis 不是一个可行的选择。
该只追加文件是Redis的选择,完全耐用的策略。它在 1.1 版中可用。
你可以在你的配置文件中开启AOF:
appendonly yes
从现在开始,每次 Redis 收到更改数据集的命令(例如SET)时,它都会将其附加到 AOF 中。当您重新启动 Redis 时,它将重新播放 AOF 以重建状态。
6.7.1 日志重写
您可以猜到,随着写入操作的执行,AOF 变得越来越大。例如,如果您将一个计数器递增 100 次,您最终将在数据集中得到一个包含最终值的键,但在 AOF 中有 100 个条目。重建当前状态不需要这些条目中的 99 个。
所以Redis支持了一个有趣的特性:它能够在不中断对客户端的服务的情况下在后台重建AOF。每当您发出BGREWRITEAOF 时, Redis 都会写入在内存中重建当前数据集所需的最短命令序列。如果您在 Redis 2.2 中使用 AOF,则需要手动运行BGREWRITEAOF。Redis 2.4 能够自动触发日志重写(更多信息请参见 2.4 示例配置文件)。
6.7.2 仅追加文件的持久性如何?
您可以配置 Redis 将 fsync
数据在磁盘上的次数。共有三个选项:
appendfsync always
:fsync
每次将新命令附加到 AOF 时。非常非常缓慢,非常安全。请注意,在执行来自多个客户端或管道的一批命令之后,这些命令会附加到 AOF,因此这意味着单个写入和单个 fsync(在发送回复之前)。appendfsync everysec
:fsync
每一秒。足够快(在 2.4 中可能和快照一样快),如果发生灾难,您可能会丢失 1 秒的数据。appendfsync no
:永远不要fsync
,只需将您的数据交到操作系统的手中。更快、更不安全的方法。通常 Linux 会使用这种配置每 30 秒刷新一次数据,但这取决于内核的精确调整。
建议(和默认)策略是fsync
每秒。它既非常快又非常安全。该always
策略在实践中很慢,但它支持组提交,因此如果有多个并行写入,Redis 会尝试执行单个fsync
操作。
6.7.3 如果我的 AOF 被截断,我该怎么办?
有可能是服务器在写入AOF文件时崩溃,或者写入时存储AOF文件的卷已满。发生这种情况时,AOF 仍包含表示数据集给定时间点版本的一致数据(使用默认 AOF fsync 策略,该版本可能长达一秒),但 AOF 中的最后一个命令可能会被截断。Redis 的最新版本无论如何都可以加载 AOF,只需丢弃文件中最后一个格式不正确的命令。在这种情况下,服务器将发出如下日志:
* Reading RDB preamble from AOF file...
* Reading the remaining AOF tail...
# !!! Warning: short read while loading the AOF file !!!
# !!! Truncating the AOF at offset 439 !!!
# AOF loaded anyway because aof-load-truncated is enabled
如果需要,您可以更改默认配置以强制 Redis 在这种情况下停止,但默认配置是继续,无论文件中的最后一个命令格式不正确,以保证重启后的可用性。
旧版本的 Redis 可能无法恢复,可能需要执行以下步骤:
-
制作 AOF 文件的备份副本。
-
使用
redis-check-aof
Redis附带的工具修复原始文件:
$ redis-check-aof --fix
-
可选
diff -u
用于检查两个文件之间的区别。 -
使用固定文件重新启动服务器。
6.7.4 如果我的 AOF 损坏了,我该怎么办?
如果 AOF 文件不仅被截断,而且被中间的无效字节序列损坏,事情就会变得更加复杂。Redis 会在启动时抱怨并中止:
* Reading the remaining AOF tail...
# Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>
最好的办法是运行该redis-check-aof
实用程序,最初没有--fix
选项,然后了解问题,在文件中给定的偏移处跳转,看看是否可以手动修复文件:AOF 使用相同的格式Redis 协议,手动修复非常简单。否则有可能让实用程序为我们修复文件,但在这种情况下,从无效部分到文件末尾的所有 AOF 部分可能会被丢弃,如果损坏发生,将导致大量数据丢失在文件的初始部分。
AOF具体操作如下:
日志重写使用已用于快照的相同的写时复制技巧。这是它的工作原理:
- Redis fork,所以现在我们有一个子进程和一个父进程。
- 子进程开始在临时文件中写入新的 AOF。
- 父进程在内存缓冲区中累积所有新更改(但同时它将新更改写入旧的仅附加文件中,因此如果重写失败,我们是安全的)。
- 当子进程完成重写文件时,父进程收到一个信号,并将内存缓冲区附加到子进程生成的文件的末尾。
- 现在 Redis 原子地将旧文件重命名为新文件,并开始将新数据附加到新文件中。