目录
- 1. 分布式缓存
- 1-1. Redis持久化
- RDB持久化
- AOF持久化
- RDB与AOF对比
- 总结
- 1-2. Redis主从集群
- 全量同步
- 增量同步
- 主从优化
- 总结
- 1-3. Redis哨兵
- 哨兵作用
- 集群监控原理
- 集群故障恢复原理
- RedisTemplate访问哨兵
- 总结
- 1-4. Redis分片集群
- 散列插槽
- 集群伸缩
- 故障转移
- 2. 多级缓存
- 2-1. 浏览器客户端缓存
- 2-2. nginx缓存
- 2-3. Redis缓存
- 2-4. 本地进程缓存
- 3. Redis最佳实践
- 3-1. 如何设计优化的Key
- 3-2. 拒绝BigKey
- 3-3. 恰当的数据类型
- 3-3. 批处理优化
1. 分布式缓存
1-1. Redis持久化
RDB持久化
RDB全称 Redis Database Backup file
(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件称为RDB文件,默认是保存在当前运行目录。
RDB持久化在四种情况下会执行:
- 执行save命令
- 执行bgsave命令
- Redis停机时
- 触发RDB条件时
1)save命令
执行下面的命令,可以立即执行一次RDB:
save命令会导致主进程执行RDB,这个过程中其它所有命令都会被阻塞。只有在数据迁移时可能用到。
2)bgsave命令
下面的命令可以异步执行RDB:
这个命令执行后会开启独立进程完成RDB,主进程可以持续处理用户请求,不受影响。
3)停机时
Redis停机时会执行一次save
命令,实现RDB持久化。
4)触发RDB条件
Redis内部有触发RDB的机制,可以在redis.conf
文件中找到(保持默认即可),格式如下:
# 900秒内,如果至少有1个key被修改,则执行bgsave , 如果是save "" 则表示禁用RDB
save 900 1
save 300 10
save 60 10000
# 是否压缩 ,建议不开启,压缩也会消耗cpu,磁盘的话不值钱
rdbcompression yes
# RDB文件名称
dbfilename dump.rdb
# 文件保存的路径目录
dir ./
RDB原理
当 bgsave 执行时,主进程会 fork(fork()是unix和linux这种操作系统的一个api
) 一个子进程,子进程共享主进程的内存数据,完成fork后子进程读取内存数据并写入 RDB 文件。
当子进程读取内存数据写入 RDB 文件时,主进程可以继续进行工作,依靠的是 copy-on-write
技术。
- 当主进程执行读操作时,直接访问共享内存即可。
- 当主进程执行写操作时,则会在内存中拷贝一份数据,对拷贝的数据执行写操作,这样不会影响到子进程读取的内存数据。
如果不使用copy-on-write会怎么样?
- 不使用copy-on-write,就意味着子进程在进行写RDB文件时,主进程可以修改子进程要读取的内存数据,那么就无法保证某一时刻数据的一致性。
- 比如,在某一时刻k1 : 1,k2:2,子进程将k1写入到RDB文件后,此时主进程对k2进行了修改,子进程再读取k2就不是2了,造成了数据不一致问题。
AOF持久化
AOF全称为Append Only File
(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。(由主进程写入到AOF文件)
AOF配置
AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF:
# 是否开启AOF功能,默认是no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"
# 表示每执行一次写命令,立即记录到AOF文件
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no
AOF文件重写
因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行 bgrewriteaof
命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
Redis会在触发阈值时自动去重写AOF文件(开启子进程执行)。阈值也可以在redis.conf中配置:
# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb
RDB与AOF对比
RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。
如果两者同时开启,会优先执行AOF文件。
总结
RDB:
- RDB是一种快照持久化方法,它会在指定的时间间隔内生成数据的完整快照。
- 适合于灾难恢复,可以很方便的被迁移到另一个数据中心。
- RDB在保存快照时速度快,恢复时也非常迅速,适合用作备份。
- 最后一次快照之后的数据可能会丢失,因为这部分数据还没有被写入快照。
AOF:
- AOF记录每一个写命令到AOF文件
- AOF提供了更好的数据安全性,可以配置为每秒同步一次,或者每写入一条命令就同步一次。
- AOF文件通常会比RDB文件更大,且恢复速度可能会更慢,但可以通过AOF文件重写进行压缩。
- AOF在系统崩溃时能最大化数据恢复,最多只丢失几秒钟的数据。
如果需要快速恢复且可以接受少量数据丢失,RDB可能是更好的选择。
如果数据的完整性非常关键且可以接受较慢的恢复速度,则应该使用AOF。
在很多场景下,结合使用RDB和AOF能提供更为可靠的数据保护机制。
1-2. Redis主从集群
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。
假设有A、B两个Redis实例,如何让B作为A的slave节点?
在B节点执行命令:slaveof(replicaof) A的IP A的port
全量同步
简述全量同步的流程?
- slave节点首先请求增量同步
- master节点判断
replid
,发现不一致,拒绝增量同步 - master将完整内存数据生成RDB文件,发送RDB到slave
- slave清空本地数据,加载master的RDB
- master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
- slave执行接收到的命令,保持与master之间的同步
增量同步
主从优化
总结
简述全量同步和增量同步区别?
- 全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个将命令发送给slave。
- 增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave
什么时候执行全量同步?
- slave节点第一次连接master节点时
- slave节点断开时间太久,repl_baklog中的offset已经被覆盖时
什么时候执行增量同步?
- slave节点重启后,并且在repl_baklog中能找到offset时
1-3. Redis哨兵
Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。
哨兵的结构如图:
哨兵作用
- 监控:Sentinel 会不断检查您的 master 和slave是否正常工作(每隔1s发送 ping 命令,查看是否有回应)
- 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
- 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端
集群监控原理
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
- 主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
- 客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
集群故障恢复原理
一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:
- 首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
- 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
- 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
- 最后是判断slave节点的运行id大小,越小优先级越高。
- 也就是挑选一个延迟最小、优先级最高且数据最完整的节点来晋升为新的主节点
当选出一个新的master后,该如何实现切换呢?
流程如下:
- sentinel给备选的slave1节点发送
slaveof no one
命令,让该节点成为master - sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
- 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
RedisTemplate访问哨兵
在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。
1、依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、配置Redis的sentinel地址
在配置文件application.yml中指定redis的sentinel相关信息:
spring:
redis:
sentinel:
master: mymaster # 在sentinel.coinfig文件中设置的主节点的名称
nodes:
- 192.168.150.101:27001
- 192.168.150.101:27002
- 192.168.150.101:27003
3、配置读写分离
@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
这个bean中配置的就是读写策略,包括四种:
- MASTER:从主节点读取
- MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
- REPLICA:从slave(replica)节点读取
- REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master
总结
Sentinel的三个作用是什么?
- 监控:监控每个节点的健康状态,也就是每隔1s向每个节点发送ping命令
- 故障转移:如果master节点故障,Sentinel会从从节点中挑选一个延迟最小、优先级最高且数据最完整的节点来晋升为新的主节点
- 通知:当集群发生故障转移时,会将最新信息推送给Redis的客户端
Sentinel如何判断一个redis实例是否健康?
- 每隔1秒发送一次ping命令,如果超过一定时间没有响应则认为是主观下线
- 如果大多数sentinel都认为实例主观下线,则判定客观下线
故障转移原理?
- 如果master节点故障,Sentinel会从从节点中挑选一个延迟最小、优先级最高且数据最完整的节点来晋升为新的主节点
故障转移步骤有哪些?
- 首先选定一个slave作为新的master,执行
slaveof no one
- 然后让所有节点都执行slaveof 新master
- 修改故障节点的配置,添加slaveof 新master
1-4. Redis分片集群
散列插槽
集群伸缩
故障转移
2. 多级缓存
2-1. 浏览器客户端缓存
2-2. nginx缓存
2-3. Redis缓存
2-4. 本地进程缓存
3. Redis最佳实践
3-1. 如何设计优化的Key
Redis的Key虽然可以自定义,但最好遵循下面的几个最佳实践约定:
- 遵循基本格式:[业务名称]:[数据名称]:[id]
- 长度不超过
44
字节(Redis 4.0+)、Redis 4.0 以下的,长度不超过39
字节 - 不包含特殊字符
例如:我们的登录业务,保存用户信息,其key可以设计成如下格式:
这样设计的好处:
- 可读性强
- 避免key冲突
- 方便管理
- 更节省内存: key是string类型,底层编码包含int、embstr和raw三种。embstr在小于44字节使用,采用连续内存空间,内存占用更小。当字节数大于44字节时,会转为raw模式存储,在raw模式下,内存空间不是连续的,而是采用一个指针指向了另外一段内存空间,在这段空间里存储SDS内容,这样空间不连续,访问的时候性能也就会收到影响,还有可能产生内存碎片
3-2. 拒绝BigKey
推荐值:
- 单个key的value小于
10KB
- 对于集合类型的key,建议元素数量小于
1000
3-3. 恰当的数据类型
3-3. 批处理优化
批量处理的方案:
- 原生的 MSET 等操作
- Pipeline 批处理
注意事项:
- 批处理时不建议一次携带太多命令
- Pipeline的多个命令之间不具备原子性