Redis进阶学习
1、Redis事务
1.1、事务的概念
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断;但是队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。也就是说 Redis 的事务具有隔离性,但是它没有隔离级别的概念。
其次,Redis 事务不保证原子性。在 Redis 中,单条命令是原子性执行的,但是事务不保证原子性,且没有回滚。如:事务在执行 EXEC 之前,入队的命令可能会出错。命令可能在 EXEC 调用后失败。
以下是 Redis 事务从开始到执行会经历的三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
1.2、事务中的几个重要命令
- 开启事务:
multi
- 执行所有事务:
exec
- 取消事务,放弃执行所有事务:
discard
- 监视一个或多个 key ,如果在事务执行之前 key 被其他命令改动,则终止事务:
watch
- 取消对 key 的监视:
unwatch
如何执行?如下:
从执行 Multi
命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入完成
执行 exec
后,Redis 会将之前的命令队列中的命令依次执行。
组队的过程中可以通过 discard
来放弃组队。
从执行 multi
命令到执行 exec
这段过程中,如果某一个命令出现了错误,那么整个队列都会取消。而当执行了 exec
命令之后,如果有命令出现了错误,那么只有出现错误的命令不会被执行,其他的命令不会被影响。
示例如下:
执行事务:
放弃事务:
若在事务队列中存在命令性错误(类似于 java 编译性错误),则执行 EXEC 命令时,所有命令都不会执行:
若在事务队列中存在语法性错误(类似于 java 的 1/0 的运行时异常),则执行 EXEC 命令时,其他正确命令会被执行,错误命令抛出异常:
2、锁
2.1、悲观锁
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,认为这个世界是黑暗的,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 block 直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
2.2、乐观锁
乐观锁(Optimistic Lock),顾名思义,就是很乐观,认为这个世界是光明的,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis 就是利用这种 check-and-set 机制实现事务的。
而 Redis 中默认使用的就是乐观锁。
通过上一章中的命令 watch
,我们可以实现乐观锁。
1 | watch key [key] ... |
例如:很多人同时对一个值进行操作,一旦这个值被修改,且被其他人监听,则其他人无法修改这个值。
通过 unwatch
,可以取消乐观锁。
1 | unwatch key [key] ... |
缺点:如果单纯使用 watch,可能导致 key 的值无法完全被修改。
场景:假设库存有 500 个商品,2000 个人进行秒杀购买(2000 个程序监听商品的 key),假设 1999 人同时购买,其内部程序监听的商品数量为 500,最后一个人却已经购买成功,商品数量变为499,则前面的事务被打断(监听的 500 数量),导致 1999 人会购买失败,库存还有 499 个商品。
3、持久化
Redis 提供了以下 2 个不同形式的持久化方式:
- RDB(Redis DataBase)
- AOF(Append Of File)
我们分成两个部分进行学习。
3.1、RDB持久化
Redis 中默认使用的就是 RDB 的持久化方式。
该种方式在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是 Snapshot 快照,它恢复时是将快照文件直接读到内存里。
3.1.1、备份的执行过程
Redis会单独创建fork一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化都结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程是不进行任何IO操作的。这就确保了级高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式就要比AOF方式更加的高效。
RDB 的缺点是最后一次持久化之后的数据可能丢失
我们在生产环境会将 dump.rdb
这个文件进行备份。
以下是 RDB 持久化的一个执行流程:
RDB 所保存的文件就是 dump.rdb
文件。他的好处就是:效率高。
3.1.4、RDB 的备份策略
对于 RDB 的备份策略,如下所示:
1 | save 3600 1 -- 如果 1 个 key 发生改变(增删改),则 1 个小时后备份一次 |
除此之外,还可以手动执行命令生成 RDB 快照,进入 redis 客户端执行命令 save
或bgsave
可以生成 dump.rdb
文件。
每次命令执行都会将所有 redis 内存快照到一个新的 .rdb
文件里,并覆盖原有 .rdb
快照文件。
其中:
save | bgsave | |
---|---|---|
IO类型 | 同步 | 异步 |
是否阻塞其他命令 | 是 | 否(fork的时候会短暂阻塞) |
复杂度 | O(n) | O(n) |
优点 | 不会消耗额外内存 | 不阻塞操作 |
缺点 | 阻塞操作 | 会消耗额外内存 |
- save:save 时只管保存,其它不管,全部阻塞。手动保存。不建议
- bgsave:Redis 会在后台异步进行快照操作,快照同时还可以响应客户端请求。推荐
配置自动生成rdb文件后台使用的是bgsave方式。
3.1.4、RDB 持久化方式的优缺点
优点:
因为dump.rdb文件是二进制文件,所以当redis服务崩溃恢复的时候,能很快的将文件数据恢复到内存之中。
适合大规模的数据恢复
对数据完整性和一致性要求不高更适合使用
节省磁盘空间
缺点:
RDB每次持久化需要将所有内存数据写入文件,然后替换原有文件,当内存数据量很大的时候,频繁的生成快照会很耗性能。
如果将生成快照的策略设置的时间间隔很大,会导致redis宕机的时候丢失过的的数据。
Fork 的时候,内存中的数据被克隆了一份,大致 2 倍的膨胀性需要考虑
3.2、AOF持久化
为解决RDB方式丢失数据的问题,从1.1版本开始,redis 增加了一种更加可靠的方式:AOF 持久化方式。
在使用AOF方式时,redis 每执行一次修改数据命令,都会将该命令追加到 appendonly.aof 文件中(先写入 os cache,然后通过 sync 刷盘)。
3.2.1、AOF 的持久化流程
- 客户端的请求写命令会被 append 追加到 AOF 缓冲区内;
- AOF 缓冲区根据 AOF 持久化策略[always,everysec,no]将操作 sync 同步到磁盘的 AOF 文件中;
- AOF 文件大小超过重写策略或手动重写时,会对 AOF 文件 rewrite 重写,压缩 AOF 文件容量;
- Redis 服务重启时,会重新 load 加载 AOF 文件中的写操作达到数据恢复的目的;
3.2.2、开启 AOF 持久化
由于 Redis 默认使用的是 RDB 持久化方式,所以我们需要手动开启 AOF 持久化。
默认情况下,配置文件如下:
1 | appendonly no |
如果需要开启,所以通过将 no
修改成 yes
即可开启。
redis重启的时候,会重放 appendonly.aof 中的命令恢复数据。
如果两者同时开启,Redis 所使用的是 AOF 的持久化。
对于 AOF,有三种同步频率可供配置:
1 | appendfsync always:每次执行写命令都会刷盘,非常慢,也非常安全。 |
推荐使用 everysec
,该策略下,最多会丢1秒的数据。
3.2.3、AOF重写
因为 appendonly.aof
文件中存储的是执行命令,所以会产生很多没用的命令,因此,redis 会定期根据最新的内存数据生成新的 .aof
文件。
如何使用重写?命令如下:
1 | no-appendfsync-on-rewrite yes |
如果 no-appendfsync-on-rewrite
改为 yes
,代表不写入 AOF 文件,只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高 性能)
如果 no-appendfsync-on-rewrite
改为 no
,还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低)
那么怎么样才会触发重写呢?如下:
Redis 会记录上次重写时的 AOF 大小,默认配置是当 AOF 文件大小是上次 rewrite 后大小的一倍且文件大于 64M 时触发。
重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定 Redis 要满足一定条件才会进行重写。
下面两个配置可以控制.aof
文件重写的频率:
1 | auto‐aof‐rewrite‐min‐size 64mb: -- aof文件至少达到了64m才会触发重写 |
AOF 重写的流程:
- bgrewriteaof 触发重写,判断是否当前有 bgsave 或 bgrewriteaof 在运行,如果有,则等待该命令结束后再继续执行
- 主进程 fork 出子进程执行重写操作,保证主进程不会阻塞
- 子进程遍历 redis 内存中数据到临时文件,客户端的写请求同时写入 aof_buf 缓冲区和 aof_rewrite_buf 重写缓冲区保证原 AOF 文件完整以及新 AOF 文件生成期间的 新的数据修改动作不会丢失
- 子进程写完新的 AOF 文件后,向主进程发信号,父进程更新统计信息
- 主进程把 aof_rewrite_buf 中的数据写入到新的 AOF 文件。
- 使用新的 AOF 文件覆盖旧的 AOF 文件,完成 AOF 重写
3.2.4、AOF 的优缺点
优点:
备份机制更稳健,丢失数据概率更低
可读的日志文本,通过操作 AOF 稳健,可以处理误操作
缺点:
比起 RDB 占用更多的磁盘空间
恢复备份速度要慢
每次读写都同步的话,有一定的性能压力
存在个别 Bug,造成恢复不能
3.3、RDB和AOF对比
命令 | RDB | AOF |
---|---|---|
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 容易丢数据 | 根据策略而定,相对不容易 |
4、主从复制
4.1、什么是主从复制
主从复制是指将一台 Redis 服务的数据,复制到其他 Redis 服务器上。前者称为主节点(master),后者称为从节点(slave)。数据的复制是单向的,只能从主节点到从节点。
其中 Master 以写为主,Slave 以读为主。
默认情况下,每一台 Redis 服务都是主节点,一个主节点可以有多个从节点(也可以没有),但一个从节点只能有一个主节点。
为什么要有主从复制呢?
假设只有一台 Redis 服务,这时候是单机模式。首先会出现第一个问题:如果 Redis 服务宕机了,就会造成数据的丢失,会造成很大的损失。
第二个问题:一台服务器的内存是有限的,尽管内存可以升级,但是终究是有限的,所以通过多台服务器可以减轻服务器的压力。
针对上述问题,我们需要准备多台服务器,配置主从复制。将数据保存在多台服务器上,并且保证每台服务器的数据是同步的。即使有一台服务器宕机,也不影响用户的使用。redis 可以继续实现高可用,同时实现数据的冗余备份。
所以就要有主从复制,那么主从复制的具体作用是什么呢?
- 数据冗余,实现数据的热备份,这也是持久化实现的另一种方式。
- 针对单机故障问题,一个节点故障,其他节点可以提供服务,不影响用户使用。实现了快速恢复故障,这也是服务冗余。
- 读写分离,master 服务主要用来写,slave 服务主要用来读数据。可以提高服务器的负载能力,可以根据需求的变化,添加从节点的数量。
- 负载均衡,同时配合读写分离,由主节点提供写服务,从节点提供读服务,分担服务器的负载。在写少读多的情况下,通过多个从节点分担读负载,能够大大提高 Redis 服务的并发量和负载。
- 高可用的基石,主从复制是哨兵和集群模式能够实施的基础。
4.2、主从复制的实现原理
Redis 的主从复制分为以下两个阶段:sync
阶段和 command propagate
阶段。
4.2.1、sync(同步)阶段
当从节点启动之后,会发送 sync
指令给主节点,要求全量同步数据,具体的步骤如下图所示:
- Slave 节点向 Master 节点发送 sync 指令,以请求数据同步
- Master 节点在接收到 sync 指令后,会执行一次 BGSAVE 指令,将当前 Master 节点中的数据保存到对应的 RDB 文件中。当 Master 节点完成 RDB 文件的导出后,再将导出的 RBD 文件发送给 Slave 节点。由于在这个过程中 Master 节点依旧有可能发生数据写入操作,在这种情况下 Master 节点会将执行的指令放入到对应的缓冲区
- Slave 节点在接受到 Master 节点导出的 RDB 文件之后,会删除现有节点的所有数据,然后加载这个 RDB 文件的内容到 Slave 节点
- 当 Slave 节点数据加载完成之后,Master 会将缓冲区中暂存的指令发送到 Slave 节点
- Slave 执行收到的指令,完成数据的同步
4.2.2、Command Propagate(命令传播)阶段
数据同步完成之后,如果后续 Master 节点继续收到了新的写入操作的指令,那么也需要将该命令传播到 Slave 节点以完成数据的同步。这个过程就被称为 “命令传播”。
主从服务器在完成第一次同步后,双方之间就会维护一个 TCP 连接。
后续主服务器可以通过这个连接继续将写操作命令传播给从服务器,然后从服务器执行该命令,使得与主服务器的数据库状态相同。
而且这个连接是长连接的,目的是避免频繁的 TCP 连接和断开带来的性能开销。
上面的这个过程被称为基于长连接的命令传播,通过这种方式来保证第一次同步后的主从服务器的数据一致性。
4.3、配置主从复制
首先在本地启动三个 Redis 服务,用来模拟不同服务器上面的 Redis 服务。
一主二从
主服务器:
1 | include /etc/redis.conf # 核心配置文件 |
从服务器:
1 | include /etc/redis.conf # 核心配置文件 |
如果重新配置了文件内容,则需要重启 Redis 服务。
启动好 3 个不同端口服务后,我们再分别开启 Redis 连接:
1 | redis-cli -p 6379 |
通过 info replication
指令查看信息:
可以发现,默认情况下,开启的每个 Redis 服务器都是主节点。
所以,现在我们要配置为一个 Master 和 两个 Slave(即一主二从)
6379 为主,6380、6381 为从,分别在 6380、6381 的 Redis 上执行如下指令:
1 | slaveof 127.0.0.1 6379 |
然后,6080和6381就变成了从机,在主机设置值,在从机都可以取到,但是从机不能写值。
但是我们这里是使用命令搭建,是「暂时的」,如果重启三个 Redis 服务,则又恢复到三主的地位。
如果想配置「永久的」,则去配置里进行修改,找到 slaveof <ip> <port>
指令进行配置:
当主机断电宕机后,默认情况下从机的角色不会发生变化,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。
薪火相传
上一个 Slave 可以是下一个 Slave 和 Master,Slave 同样可以接收其他 Slaves 的连接和同步请求,那么该 Slave 作为了链条中下一个的 Master,可以有效减轻 Master 的写压力,去中心化降低风险。
在一个从机用 slaveof <ip> <port>
指令连接另一个从机。
反客为主
当一个 master 宕机后,后面的 slave 可以立刻升为 master,其后面的 slave 不用做任何修改。
有两种方式可以产生新的主机:
- 从机手动执行命令
slaveof no one
,这样执行以后从机会独立出来成为一个主机 - 使用哨兵模式(自动选举)
4.4、哨兵模式
4.4.1、什么是哨兵模式?
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis 从 2.8 开始正式提供了 Sentinel(哨兵)架构来解决这个问题。
哨兵模式就是自动实现反客为主的一种形式,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是 哨兵通过发送命令,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例。
这里的哨兵有两个作用:
- 通过发送命令,让 Redis 服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到 Master 宕机,会自动将 Slave 切换成 Master,然后通过 发布订阅模式 通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对 Redis 服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
假设主服务器宕机,哨兵 1 先检测到这个结果,系统并不会马上进行 failover 过程,仅仅是哨兵 1 主观的认为主服务器不可用,这个现象成为 主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行 failover [故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为 客观下线。
4.4.2、配置哨兵模式
配置哨兵配置文件 sentinel.conf
:
1 | sentinel monitor 主机名称 host port 1 |
启动哨兵:
1 | redis-sentinel myredis/sentinel.conf |
此时哨兵监视着我们的主机 6379,当我们断开主机后:
哪个从机会被选举为主机呢?根据优先级别:slave-priority
,这个指令需要去每个从机的配置文件进行配置,默认都是 100。
建议每个从机都配置不同的 slave-priority
,这样可以避免复制延时。
需要注意的是:值越小优先级越高。
复制延时:由于所有的写操作都是先在 Master 上操作,然后同步更新到 Slave 上,所以从 Master 同步到 Slave 机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave 机器数量的增加也会使这个问题更加严重。
哨兵模式的优缺点:
优点:
哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
主从可以切换,故障可以转移,系统的可用性更好
哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
实现哨兵模式的配置其实是很麻烦的,里面有很多配置项
以下是哨兵模式的完整配置:
1 | Example sentinel.conf |