1. 事务
1.1 事务的概述
Redis 事务通过 MULTI
、EXEC
、DISCARD
、WATCH
几个命令来实现
- MULTI:开启事务
- EXEC:提交事务
- DISCARD:放弃事务
- WATCH:为 Redis 事务提供 check-and-set (CAS)行为
Redis 事务可以一次执行多条命令,Redis 事务有如下特点:
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行
事务操作如下:
1.2 事务发生错误
Reids 事务发生错误分为两种情况:
- 事务提交前发生错误:在发送命令过程中发生错误
- 事务提交后发生错误:在执行命令过程中发生错误
①:事务提交前发生错误
这里故意将 incr 命令
写错,从结果我们可以看到:这条 incr 命令
没有入队,并且事务执行失败,num1 和 num2 都没有值。
②:事务提交后发生错误
上面的事务命令中,我给 num1
设置了一个 a,然后执行自增命令,最后获取 num1 的值。
我们发现第二条命令执行发生了错误,但是整个事务依然提交成功了。
从上面现象中可以得出,Redis 事务不支持回滚操作。如果支持的话,整个事务的命令都不应该被执行。
1.3 Redis 为什么不支持事务回滚
以下是这种做法的优点:
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 incr 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 incr , 回滚是没有办法处理这些情况的
1.4 放弃事务
当执行 discard
命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出
1.5 WATCH 命令
WATCH 机制:使用 WATCH 监视一个或多个 key , 跟踪 key 的 value 修改情况,如果有key 的 value 值在事务 EXEC 执行之前被修改了,整个事务被取消。EXEC 返回提示信息,表示事务已经失败
WATCH 监视了一个带过期时间的键,那么即使这个键过期了,事务仍然可以正常执行
1.6 取消 WATCH
- WATCH 命令可以被调用多次。对键的监视从 WATCH 执行之后开始生效,直到调用 EXEC 为止。不管事务是否成功执行,对所有键的监视都会被取消
- 当客户端断开连接时,该客户端对键的监视也会被取消
- UNWATCH 命令可以手动取消对所有键的监视
1.7 Redis 脚本和事务
从定义上来说, Redis 中的脚本本身就是一种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快
2. 持久化
2.1 为什么需要持久化
我们知道 Redis 是内存数据库,主打高性能,速度快。相比 Redis 而言,MySQL 的数据则是保存再硬盘中速度慢,但是稳定性好。
你想想 Redis 数据保存在内存中,一旦服务器宕机了,数据岂不是全部都没了,这将会出现很大问题。所以 Redis 为了弥补这一缺陷,提供数据持久化机制,即使服务器宕机,依然可以保证数据不丢失
2.2 持久化简介
Redis 提供了两种持久化机制 RDB 和 AOF,适用于不同场景:
- RDB 持久化方式:能够在指定的时间间隔能对你的数据进行快照存储
- AOF 持久化方式:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。AOF 命令以 Redis 协议追加保存每次写的操作到文件末尾。Redis还能对 AOF 文件进行后台重写,使得 AOF 文件的体积不至于过大
2.2.1 RDB
RDB 持久化是通过在指定时间间隔对数据进行快照。比如:在 8 点钟对数据进行持久化,那么 Redis 会 fork 一个子进程将 8 点那一刻内存中的数据持久化到磁盘上。
触发 RDB 持久化有以下几种方法:
- 执行 save 命令:执行 save 命令进行持久会阻塞 Redis,备份期间 Redis 无法对外提供服务,一般不建议使用,使用场景为 Redis 服务器需要停机维护的情况下
- 执行 bgsave 命令:bgsave 命令不会阻塞 Redis 主进程,持久化期间 Redis 依然可以正常对外提供服务
- 通过配置文件中配置的 save 规则来触发:
save 900 1:900s 内有 1 个 key 发生变化,则触发 RDB 快照
save 300 10:300s 内有 10 个 key 发生变化,则触发 RDB 快照
save 60 10000:60s 内有 10000 个 key 发生变化(新增、修改、删除),则触发 RDB 快照
save “”:该配置表示关闭 RDB 持久化
2.2.1.1 RDB 持久化原理
Redis 进行 RDB 时,会 fork 一个子进程来进行数据持久化,这样不妨碍 Redis 继续对外提供服务,提高效率
2.2.1.2 RDB 优缺点
优点:
- RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份。比如:你可以在每个小时报保存一下过去 24 小时内的数据。同时,每天保存过去30天的数据。这样,即使出了问题你也可以根据需求恢复到不同版本的数据集
- RDB 在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他 IO 操作,所以 RDB 持久化方式可以最大化 Redis 的性能
- 与 AOF 相比,在恢复大的数据集的时候,RDB方式会更快一些
缺点:
- 如果备份间隔时间较长,RDB 会丢失较多的数据。比如 8 点备份一次,8 点半服务器宕机,那么这半小时内的数据就会丢失了
2.2.2 AOF
AOF 持久化是通过日志的方式,记录每次 Redis 的写操作。当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF 命令以 Redis 协议追加保存每次写的操作到文件末尾。
Redis 还能对 AOF 文件进行后台重写,使得 AOF 文件的体积不至于过大.
AOF 持久化配置:
# 是否开启 aof no:关闭;yes: 开启
appendonly no
# aof 文件名
appendfilename "appendonly.aof"
# aof 同步策略
# appendfsync always # 每个命令都写入磁盘,性能较差
appendfsync everysec # 每秒写一次磁盘,Redis 默认配置
# appendfsync no # 由操作系统执行,默认Linux配置最多丢失30秒
# aof 重写期间是否同步
no-appendfsync-on-rewrite no
# 重写触发策略
auto-aof-rewrite-percentage 100 # 触发重写百分比 (指定百分比为0,将禁用aof自动重写功能)
auto-aof-rewrite-min-size 64mb # 触发自动重写的最低文件体积(小于64mb不自动重写)
# 加载aof时如果有错如何处理
# 如果该配置启用,在加载时发现aof尾部不正确是,会向客户端写入一个log,但是会继续执行,如果设置为 no ,发现错误就会停止,必须修复后才能重新加载。
aof-load-truncated yes
# aof 中是否使用 rdb
# 开启该选项,触发AOF重写将不再是根据当前内容生成写命令。而是先生成RDB文件写到开头,再将RDB生成期间的发生的增量写命令附加到文件末尾。
aof-use-rdb-preamble yes
2.2.2.1 AOF 文件写入
aof 文件是命令追加的方式,先将命令写入缓冲区,时间到了再写如磁盘中:
appendfsync always # 每个命令都写入磁盘,性能较差
appendfsync everysec # 每秒写一次磁盘,Redis 默认配置
appendfsync no # 由操作系统执行,默认Linux配置最多丢失30秒
上面配置就是何时写入磁盘中
2.2.2.2 AOF 重写
aof 文件虽然丢失的数据少,但是随着时间的增加,aof 文件体积越来越大,占用磁盘空间越来越大,恢复时间长。所以 redis 会对 aof 文件进行重写,以减少 aof 文件体积:
-- 重写前的 aof
set k1 20
set k2 40
set k1 35
set k3 34
set k2 19
-- 这里 k1 最终的值为 35,k2 最终值为 19,所以不需要写入两个命令
-- 重写后
set k1 35
set k3 34
set k2 19
2.2.2.3 混合持久化
从 Redis 4.0 版本开始,引入了混合持久化机制,纯AOF方式、RDB+AOF方式,这一策略由配置参数aof-use-rdb-preamble(使用RDB作为AOF文件的前半段)控制,默认关闭(no),设置为yes可开启
- no:按照AOF格式写入命令,与4.0前版本无差别;
- yes:先按照RDB格式写入数据状态,然后把重写期间AOF缓冲区的内容以AOF格式写入,文件前半部分为RDB格式,后半部分为AOF格式。
混合持久化优点如下:
- 大大减少了 aof 文件体积
- 加快了 aof 文件恢复速度,前面是 rdb ,恢复速度快
2.2.2.4 AOF 数据恢复
AOF 数据恢复有两种:
- 纯 AOF:恢复时,取出 AOF 中命令,一条条执行恢复
- RDB + AOF:先执行 RDB 加载流程,执行完毕后,再取出余下命令,开始一条条执行
2.2.2.5 AOF 优缺点
优点
- AOF 实时性更好,丢失数据更少
- AOF 已经支持混合持久化,文件大小可以有效控制,并提高了数据加载时的效率
缺点
- 对于相同的数据集合,AOF 文件通常会比 RDB 文件大
- 在特定的 fsync 策略下,AOF 会比 RDB 略慢
- AOF 恢复速度比 RDB 慢
3. Redis 集群
在生产环境中,我们使用 Redis 通常采用集群模式,因为单机版 Redis 稳定性可靠性较低,而且存储空间有限。
Redis 支持三种集群模式:
- 主从复制
- 哨兵模式
- Cluster 模式
3.1 主从复制
主从复制模式,有一个主,多个从,从而实现读写分离。主机负责写请求,从机负责读请求,减轻主机压力
3.1.1 主从复制原理
主从复制过程如下:
- 从数据库启动成功后,连接主数据库,发送 SYNC 命令;
- 主数据库接收到 SYNC 命令后,开始执行 BGSAVE 命令生成 RDB 文件并使用缓冲区记录此后执行的所有写命令;
- 主数据库 BGSAVE 执行完后,向所有从数据库发送快照文件,并在发送期间继续记录被执行的写命令;
- 从数据库收到快照文件后丢弃所有旧数据,载入收到的快照;
- 主数据库快照发送完毕后开始向从数据库发送缓冲区中的写命令;
- 从数据库完成对快照的载入,开始接收命令请求,并执行来自主数据库缓冲区的写命令;(从数据库初始化完成)
- 主数据库每执行一个写命令就会向从数据库发送相同的写命令,从数据库接收并执行收到的写命令(从数据库初始化完成后的操作)
- 出现断开重连后,2.8 之后的版本会将断线期间的命令传给重数据库,增量复制。
- 主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。Redis 的策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
3.1.2 主从复制优缺点
优点:
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离
- Slave 同样可以接受其它 Slaves 的连接和同步请求,这样可以有效的分载 Master 的同步压力
- Master Server 是以非阻塞的方式为 Slaves 提供服务。所以在 Master-Slave 同步期间,客户端仍然可以提交查询或修改请求
缺点
- 主从不具备容错和恢复能力,一旦主机挂了,那么整个集群处理可读状态,无法处理写请求,会丢失数据
- 主机宕机后无法自动恢复,只能人工手动恢复
- 集群存储容量有限,容量上线就是主库的内存的大小,无法存储更多内容
3.2 哨兵集群
哨兵的作用是起到监控作用。一旦 Redis 集群出现问题了,哨兵会立即做出相应动作,应对异常情况。
哨兵模式是基于主从复制模式上搭建的,因为主从复制模式情况下主服务器宕机,会导致整个集群不可用,需要人工干预。所以,哨兵模式在主从复制模式下引入了哨兵来监控整个集群,哨兵模式架构图如下:
哨兵功能:
-
监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。
-
自动故障转移(Automatic failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
-
配置提供者(Configuration provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。
-
通知(Notification):哨兵可以将故障转移的结果发送给客户端。
3.2.1 下线判断
Redis 下线分为主观下线和客观下线两种
- 主观下线:单台哨兵任务主库处于不可用状态
- 客观下线:整个哨兵集群半数以上的哨兵都认为主库处于可不用状态
哨兵集群中任意一台服务器判断主库不可用时,此时会发送命令给哨兵集群中的其他服务器确认,其他服务器收到命令后会确认主库的状态,如果不可用,返回 YES,可用则返回 NO,当有半数的服务器都返回 YES,说明主库真的不可用,此时需要重新选举:
3.2.2 主库选举
当哨兵集群判定主库下线了,此时需要重新选举出一个新的主库对外提供服务。那么该由哪个哨兵来完成这个新库选举和切换的动作呢?
注意:这里不能让每个哨兵都去选举,可能会出现每个哨兵选举出的新主库都不同,这样就没法判定,所以需要派出一个代表 ------ 哨兵代表选择
3.2.3 哨兵代表选择
哨兵的选举机制其实很简单,就是一个Raft选举算法: 选举的票数大于等于num(sentinels)/2+1时,将成为领导者,如果没有超过,继续选举
任何一个想成为 Leader 的哨兵,要满足两个条件:
- 第一,拿到半数以上的赞成票;
- 第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。
以 3 个哨兵为例,假设此时的 quorum 设置为 2,那么,任何一个想成为 Leader 的哨兵只要拿到 2 张赞成票,就可以了。
3.2.4 新库选择
上面已经选举出了哨兵代表,此时代表需要完成新主库的选择,新库的选择需要满足以下几个标准:
- 新库需要处于健康状态,也就是和哨兵之间保持正常的网络连接
- 选择salve-priority从节点优先级最高(redis.conf)的
- 选择复制偏移量最大,只复制最完整的从节点
3.2.5 故障转移
故障转移要实现新老主库之间的切换
故障转移流程如下:
3.2.6 哨兵模式优缺点
优点
- 实现了集群的监控,故障转移,实现了高可用
- 拥有主从复制模式的所有优点
缺点
- 集群存储容量有限,容量上线就是主库的内存的大小,无法存储更多内容
3.3 Cluser 集群
Redis 的哨兵模式实现了高可用了,但是每台 Redis 服务器上存储的都是相同的数据,浪费内存,而且很难实现容量上的扩展。所以在 redis3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的内容
3.3.1 Redis 集群的数据分片
Redis 集群没有使用一致性hash, 而是引入了 哈希槽的概念.
Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽。集群的每个节点负责一部分hash槽。
举个例子:比如当前集群有3个节点,那么:
- 节点 A 包含 0 到 5500号哈希槽.
- 节点 B 包含5501 到 11000 号哈希槽.
- 节点 C 包含11001 到 16384号哈希槽.
这种结构很容易添加或者删除节点.。比如如果我想新添加个节点 D,我需要从节点 A, B, C中得部分槽到 D 上。如果我想移除节点 A,需要将 A 中的槽移到 B 和 C 节点上,然后将没有任何槽的 A 节点从集群中移除即可。
由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。