Redis高级篇 |单体Redis存在的问题及解决

单体Redis存在的问题及解决

  1. 数据丢失问题

  • 解决:实现Redis数据持久化

  1. 并发能力问题

  • 搭建主从集群,实现读写分离

  1. 存储能力问题

  • 搭建分片集群,利用插槽机制实现动态扩容

  1. 故障恢复问题

  • 利用Redis哨兵,实现健康检测和自动恢复


Redis的持久化

RDB

RDB全称Redis Database Backup file (Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。

快照文件又被称为:RDB文件,默认是保存至当前运行目录

RDB有两种实现:

  1. 由Redis主进程来执行RDB,会阻塞所有命令

>save
ok
通常在该节点宕机前,或者准备停机前才进行操作
  1. 开启子进程执行RDB,避免主进程收到影响

>bgsave
Background saving started
一般使用这个,在redis运行中使用

Redis默认是有持久化的,在停机之前,默认保存的文件名为:dump.rdb

以上的持久化是被动的,有没有主动的持久化呢?Redis内部有触发RDB的机制,可以在redis.conf文件中找到

redis.conf:
save 900 1  900s内有一个key被修改,则执行bgsave
save 300 10 300s内10个key
save 60 10000 60s内10000个key

# 禁用RDB
save ""

redis.conf中的其他配置
# 是否压缩,建议不开启,因为压缩会消耗cpu,且相对而言硬盘不值钱
rdbcompression yes

# RDB文件名称
dbfilename dump.rdb

# 文件保存的路径目录
dir ./

RDB底层实现原理

Redis高级篇 |单体Redis存在的问题及解决_第1张图片

当满足bgsave的时候,redis主进程fork得到子进程,子进程复制一份页表,通过页表将内存中的数据写入到RDB文件中,写好的RDB文件将会替换原来的旧的RDB文件。在整个子进程读取内存数据的过程中,内存空间中的数据是共享的,如果此时用户做出更新数据的操作,那么fork会将内存中的数据标记为只读,并且复制数据到副本,此后主进程的所有操作都是对副本进行操作,而子进程依旧继续读取原来的数据。

RDB缺点:

  1. 如果是save 60 1000,在60s内执行了1000次key更新,则进行RDB,那如果还没够1000次,这60s内宕机了,这999的数据就丢失了

  1. fork子进程、压缩、写出RDB文件都比较耗时


AOF

AOF全称为Append 0nly File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文

AOF默认关闭的,需要修改redis.conf配置文件来开启AOF:

# 是否开启AOF功能,默认是no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"

# 标识每执行一次写命令,立刻记录到AOF文件
# 主进程来执行,就是每次接收到写命令,主进程要写数据且将数据写入磁盘才算命令结束
# 可以绝对保证安全,但是性能不好
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后标识每隔一秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统觉得何时将缓冲区内容写回磁盘
appendfsync no

# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb

配置项

刷盘时间

优点

缺点

always

同步刷盘

可靠性高,几乎不丢失数据

性能影响大

everysec

每秒刷盘

性能适中

最多丢失1秒数据

no

操作系统控制

性能最好

可靠性差,可能丢失大量数据

AOF是记录命令,所以AOF文件会比RDB文件大的多,而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义,通过bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同的效果,比如:

set num 3
set name jack
set num 6

bgrewriteaof:
mset num 3 name jack

RDB跟AOF对比

RDB

AOF

持久化方式

定时对整个内存数据进行快照

记录每次执行的命令

数据完整性

不完整,两次备份之间数据可能会丢失

相对完整,取决于刷盘策略

文件大小

会有压缩,文件体积小

记录命令,文件体积很大

宕机恢复速度

很快

数据恢复优先级

低,因为数据完整性不如AOF

高,因为数据完整性更高

系统资源占用

高,大量cpu和内存消耗

低,主要是磁盘IO资源,但AOF重写时会占用大量CPU和内存资源

使用场景

可以容忍数分钟的数据丢失,追求更快的启动速度

对数据安全性要求较高


Redis默认采用RDB持久化方案,为什么?

  1. RDB产生的rdb文件是一个完整的数据快照文件,比较适合数据恢复

  1. 高性能:会fork独立进程,不占用主进程资源

  1. 简单:不必每次记录执行命令,只需要记录数据就行了


主从架构

单节点Redis的并发能力是有上限的,且不安全,万一宕机了内存中的数据就丢失了

可以组成redis集群,且redis的读操作较多,写操作较少,可以实现读写分离,读去一台redis,写去另一台,但是这种读写分离也要保证不管从哪个节点读取数据,数据都是一样的,这就需要主节点数据同步

Redis高级篇 |单体Redis存在的问题及解决_第2张图片

数据同步原理

全量同步

Redis高级篇 |单体Redis存在的问题及解决_第3张图片

  1. 第一阶段:从节点带着replid向主节点发送全量数据同步请求,主节点接收到之后判断是不是第一次同步即判断replid是否一致,如果是第一次同步,则将主节点的版本信息告诉从节点

  1. 第二阶段:主节点执行bgsave,开启异步进程生成RDB文件,并将RDB发给从节点,这个时候难免会有数据更新进来,于是主节点将发送RDB文件这个过程中的新的更新命令放入repl_baklog缓冲区,从节点接收到RDB文件就加载这个文件

  1. 第三阶段:主节点将repll_baklog缓冲区中的新命令发送给从节点,从节点执行新命令

主节点如何判断从节点是不是第一次同步

  • Replication ld:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replidslave则会继承master节点的replid

  • offset:偏移量,随着记录在repl baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新

总结来说:replid是用来标记是哪个主节点的redis从节点,offset用来判断从节点数据跟主节点数据的差距,如果两边offset一样说明数据相同,主从第一次同步是全量同步,但如果slave重启后同步,则执行增量同步


增量同步

Redis高级篇 |单体Redis存在的问题及解决_第4张图片

其中这个repl_baklog的缓冲区的执行过程:

repl_baklog是一个环形队列,master不断向里面写入数据,其中绿色的跟红色的差值就是代表为同步的数据,如果一直往里面塞数据,有可能会塞满,但是如果塞满了,就会继续塞,替代掉一开始的绿色。

但是这样也会有问题:

如果slave宕机太久,master写入的数据量太大,占满了整个repl_baklog,就会出现slave还没同步的数据也被master新写入的数据覆盖,即offset被覆盖溜了,此时为了数据的一致性,就必须要开始全量同步

如何优化Redis主从集群?

  1. 及时恢复slave,防止宕机太久,尽量避免全量同步

  1. 在master中配置repl-disklee-sync yes启用五磁盘复制,避免全量同步时的磁盘IO,代表着不再通过磁盘传送数据,而是通过网络的形式传递(需要网络的支持,网络不能太差)

  1. Redis单节点的内存占用不用太大,减少RDB导致的过多磁盘 IO

  1. 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从的链式结构,减少master压力


总结

全量同步跟增量同步的区别?

  1. 全量同步需要经过RDB过程,发送RDB文件给slave。后续命令通过repl_baklog发送给slave

  1. 增量同步不需要RDB过程,只需要获取repl_baklog中的offset后续命令

什么执行全量同步?

  1. 在slave第一次连接master的时候

  1. 在slave断开连接太久,repl_baklog中的offset被覆盖被迫执行

什么时候执行增量同步?

跟master连接之后,且repl_baklog中offset没有被覆盖


哨兵模式

Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用如下:

Redis高级篇 |单体Redis存在的问题及解决_第5张图片

  1. 监控

  • sentinel会不断检查master和slave是否按照预期工作

  1. 自动故障恢复

  • 如果master故障,sentinel会将一个slave提升为master,当故障实例恢复后以新的masater为主

  1. 通知

  • sentinel充当redis客户端的服务发现来源,当集群发送故障转移时,会将最新信息推送到redis的客户端

  • 客户端也不是直接访问redis,而是访问sentinel,由sentinel告诉客户端需要去哪个节点


服务状态检测

Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:

  • 主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。

  • 客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超

过Sentinel实例数量的一半。


选举新的master

一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:

  • 先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds *10)则会排除该slave节点

  • 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举。

  • 如果slave-prority一样,则判断slave节点的sffset值,越大说明数据越新,优先级越高

  • 最后是判断slave节点的运行id大小,越小优先级越高。


如何实现故障转移

当选中了其中一个slave为新的master后(例如slave1),故障的转移的步骤如下:

  • sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master

  • sentinel给所有其它slave发送slaveof 192.168.150.101 7002命令,让这些slave成为新master的从节点,开始从新的master上同步数据。

  • 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点

Redis高级篇 |单体Redis存在的问题及解决_第6张图片

分片集群

主从和哨兵可以解决高可用,高并发读的问题,但是还是有两个问题没解决:

  1. 海量数据存储问题

  1. 高并发写问题

可以用分片集群解决上面的问题,分片集群有以下特征:

  • 集群中有多个master,每个master保存不同的数据

  • 每个master都可以由多个slave节点

  • master之间通过ping检测彼此的状态

  • 客户端请求可以访问集群中任意节点,最终都会被转发到正确节点

Redis高级篇 |单体Redis存在的问题及解决_第7张图片


散列插槽

Redis会把每一个master节点映射到0-16383个插槽(hash slot)上,查看集群信息的时候可以看到

Redis高级篇 |单体Redis存在的问题及解决_第8张图片

数据key不是跟节点绑定,而是跟插槽绑定。redis会根据key的有效部分计算插槽值

  • key中包含**{},且{}中至少包含一个字符,“{}**”中的部分就是有效部分

  • key中不包含**{}**,整个key就是有效部分

举个例子:key是num,那就将num绑定到插槽上,但是如果说key是{demo}num,就是绑定demo,计算方式是利用CRC16算法得到一个hash值,将hash对16384取余,得到的就是slot


Redis如何判断某个key应该在哪个节点上?

  1. redis在分片的时候会将插槽分配给对应节点

  1. 将key的有效部分经过转化得到slot值,根据slot判断节点


如何将同一类数据固定的保存在同一个Redis实例

同一类数据使用相同的有效部分key,例如key都以:{type}作为前缀:{type}shop:id

为什么会有固定的想法?

因为如果判断key不在当前节点上,会重定向到key所在插槽的节点上获取数据,如果数据量大反复切换会耗时。


插槽伸缩

有时候需要对集群进行扩容,那进来的节点又没有redis初始化分配插槽数,就需要对插槽进行更改

  1. 添加一个节点并启动(此处略,需要配置节点文件)

  1. 将节点添加到集群

# NewNodeIp新进来的节点,ToNodeIp提供插槽的节点
redis-cli --cluster add-node NewNodeIp:NeNodePort ToNodeIp:ToNodePort

做完就已经添加成功,但是还没有分配插槽

  1. 添加插槽

# 需要分配插槽的节点
redis-cli --cluster reshard nodeIp:nodePort
随后
1.接收资源节点id
2.提供资源节点id

故障转移

当集群中有一个master宕机后,会发生什么?

  • 首先是该实例与其他实例失去连接

  • 然后是疑似宕机

  • 最后是确认下线,自动提升一个slave


数据迁移

利用故障转移的机制实现:

利用clusterfailover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移,具体流程如下:

  • 需要更换节点,比如更新设备:

  1. 使用clusterfailover手动让master宕机

  1. slave告诉master要开始数据同步了,master拒绝所有客户端请求,防止同步过程中出现新数据

  1. master返回当前数据的offset(从repl_baklog)给slave

  1. slave等待offset跟master一致

  1. master,slave开始故障转移

  1. 等到转移结束,slave广播故障转移结果:即:原slave变成master,原master变成slave

  • 其中clusterfailover支持三种模式

  • 缺省:默认的流程,上面说的流程

  • force:省略对offset的一致性校验

  • takeover:执行开始故障转移

Redis高级篇 |单体Redis存在的问题及解决_第9张图片

SpringBoot搭建集群

spring:
  redis:
#    password: 123456
    cluster:
      nodes:
        - 192.168.163.129:7010
        - 192.168.163.129:7020
        - 192.168.163.129:7030
        - 192.168.163.129:8010
        - 192.168.163.129:8020
        - 192.168.163.129:8030
#    sentinel:
#      master: mymaster
#      nodes:
#        - 192.168.163.129:27001
#        - 192.168.163.129:27002
#        - 192.168.163.129:27003
@Component
public class RedisConfig {
//    对lettuce的自定义配置
    @Bean
    public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
        return new LettuceClientConfigurationBuilderCustomizer() {
            @Override
            public void customize(LettuceClientConfiguration.LettuceClientConfigurationBuilder clientConfigurationBuilder) {
                clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
//                ReadFrom.REPLICA_PREFERRED:优先从slave节点读取,所有的slave都不可用才读取master
//                REPLICA:从slave节点读取
//                MASTER:从主节点读取
//                MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
            }
        };
    }
}

你可能感兴趣的:(Redis,缓存,redis)