# 分布式缓存
# Redis 持久化
# RDB 持久化
# 概念
RDB 全称 Redis Database Backup file(Redis 数据备份文件),也被叫做 Redis 数据快照,简单来说就是把内存中所有数据都记录到磁盘中,当 Redis 实例故障重启后,从磁盘读取快照文件,恢复数据,快照文件为 RDB 文件,默认是保存在当前运行目录
# RDB 机制
redis.conf 文件中,有触发 RDB 机制的配置
RDB 的其它配置也可以在 redis.conf 文件中设置
# RDB 的 fork 原理
bgsave 开始时会 fork 主进程得到子进程,子进程共享主进程的内存数据,完成 fork 后读取内存数据并写入 RDB 文件
- 当主进程执行读操作时,访问共享内存
- 当主进程执行写操作时,则会拷贝一份数据,执行写操作
# AOF 持久化
# 概念
AOF 全称为 Append Only FIle(追加文件),Redis 处理的每一个写命令都会记录在 AOF 文件,可以看做是命令日志文件
# 配置
AOF 默认是关闭的,需要修改 redis.conf 配置文件来开启 AOF
AOF 的命令记录的频率也可以通过 redis.conf 文件来配
配置项 | 刷盘时机 | 优点 | 缺点 |
---|---|---|---|
Alaways | 同步刷盘 | 可靠性高,几乎不丢失数据 | 性能影响大 |
everysec | 每秒刷盘 | 性能适中 | 最多丢失一秒数据 |
no | 操作系统控制 | 性能最好 | 可靠性较差,可能丢失大量数据 |
因为是记录命令,AOF 文件会比 RDB 文件大得多,而且 AOF 会记录同一个 key 的多次写操作,但只有最后一次写操作才有意义,通过执行 bgrewiteaof 命令,可以让 AOF 文件执行重写功能,用最少的命令达到相同效果
Redis 会在触发阈值时自动去重写 AOF 文件,阈值也可以在 redis.conf 中配置
# RDB && AOF 对比
RDB | AOF | |
---|---|---|
持久化方式 | 定时对整个内存做快照 | 记录每一次执行的命令 |
数据完整性 | 不完整,两次备份之间会丢失 | 相对完整,取决于刷盘策略 |
文件大小 | 会有压缩,文件体积小 | 记录命令,文件体积很大 |
宕机恢复速度 | 很快 | 慢 |
数据恢复优先级 | 低,因为数据完整性不如 AOF | 高,因为数据完整性更高 |
系统资源占用 | 高,大量 CPU 和内存消耗 | 低,主要是磁盘 IO 资源,但 AOF 重写时会占用大量 CPU 和内存资源 |
使用场景 | 可以容忍数分钟的数据丢失,追求更快的启动速度 | 对数据安全性要求较高 |
# Redis 主从
# 搭建主从架构
# 数据同步原理
# 第一次同步(全量同步原理)
# 两个概念
- Replication Id:简称 replid,是数据集的标记,id 一致则说明是同一数据集,每一个 master 都有唯一的 replid,slave 则会集成 master 节点的 replid
- offset:偏移量,随着记录在 repl_baklog 中的数据增多而逐渐扩大,slave 完成同步时也会记录当前同步的 offset,如果 slave 的 offset 小于 master 的 offset,说明 slave 数据落后于 master,需要更新 1
# 增量同步原理
# 优化 Redis 主从集群
- 在 master 中配置 repl-diskless-sync yes 启用无磁盘复制,避免全量同步时的磁盘 IO
- Redis 单节点上的内存占用不要太大,减少 RDB 导致的过多磁盘 IO
- 适当提高 repl_baklog 的大小,发现 slave 宕机时尽快实现故障恢复,尽可能避免全量同步
- 限制一个 master 上的 slave 节点数量,如果实现是太多 slave,则可以采用主 —— 从 —— 从链式结构,减少 master 压力
# Redis 哨兵
# Redis 哨兵的作用及工作原理
# 哨兵的作用
Redis 提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复
- 监控:Sentinel 会不断检查您的 master 和 slave 是否按预期工作
- 自动故障恢复:如果 master 故障,Sentinel 会将一个 slave 提升为 master,当故障恢复后也以新的 master 为主
- 通知:Sentinel 充当 Redis 客户端的服务发现来源,当集群发生故障转移时,会将最新消息推送给 Redis 的客户端
# 服务状态监控
Sentinel 基于心跳机制监测服务状态,每隔一秒向集群的每个实例发送 ping 命令
- 主观下线:如果某 sentinel 节点发现某实例未在规定时间响应,则认为该实例主观下线
- 客观下线:若超过指定数量(quorum)的 sentinel 都认为该实例主观下线,则该实例客观下线,quorun 值最好超过 Sentinel 实例数量的一半
# 选举新的 master
一旦发现 master 故障,sentinel 需要在 salve 中选择一个作为新的 master,选择依据如下:
- 首先判断 slave 节点与 master 节点断开时间长短,如果超过指定值(down-after-mulliseconds*10)则会排序该 slave 节点
- 然后判断 slave 节点的 salve-priority 值,越小优先级越高,如果是 0 则永不参与选举
- 如果 slave-prority 一样,则判断 slave 节点的 offset 值,越大说明数据越新,优先级越高
- 最后判断 slave 节点的运行 id 大小,越小优先级越高
# 实现故障转移
- sentinel 给备选的 slave 节点发送 slaveof no one 命令,让该节点称为 master
- sentinel 给其它 slave 发送 slaveof 192.168.150.101 7002 命令,让这些 slave 成为新的 master 从节点,开始从新的 master 上同步数据
- 最后,sentinel 将故障节点标记为 slave,当故障节点恢复后会自动成为新的 master 的 slave 节点
# RedisTemplate 的哨兵模式
# 引入依赖
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-data-redis</artifactId> | |
</dependency> |
# 配置 application.yaml
spring: | |
redis: | |
sentinel: | |
master: mymaster | |
nodes: | |
- 192.168.150.101:27001 | |
- 192.168.150.101:27002 | |
- 192.168.150.101:27003 |
# 配置主从读写分离
@Bean | |
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer() { | |
return configBuilder -> configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED); | |
} |
# Redis 分片集群
# 搭建分片集群
# 分片集群结构
- 集群中有多个 master,每个 master 保存不同数据
- 每个 master 都可以有多个 slave 节点
- master 之间通过 ping 检测彼此健康状态
- 客户端请求可以访问集群任意节点,最终会转发到正确的节点上
# 散列插槽
Redis 会把每一个 master 节点映射到 0~16383 个插槽上,从而寻找不同的 master
# 集群伸缩
添加或者移除节点到集群中
# 故障转移
# 过程
首先是该实例与其它实例失去连接 1
- 然后疑似宕机
- 最后是确定下线,自动提升一个 slave 节点为新的 master
# 数据迁移
利用 cluster failover 命令可以手动让集群中的某个 master 宕机,切换到执行 cluster failover 命令的 slave 节点,实现无感知的数据迁移
# RedisTemplate 访问分片集群
# 引入依赖
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-data-redis</artifactId> | |
</dependency> |
# 配置 application.yaml
spring: | |
redis: | |
cluster: | |
nodes: | |
- 192.168.150.101:7001 | |
- 192.168.150.101:7002 | |
- 192.168.150.101:7003 | |
- 192.168.150.101:8001 | |
- 192.168.150.101:8002 | |
- 192.168.150.101:8003 |
# 配置读写分离
@Bean | |
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer() { | |
return configBuilder -> configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED); | |
} |
# 多级缓存
# JVM 进程缓存
# 本地进程缓存
- Redis 分布式缓存,例如 Redis
- 优点:存储容量更大,可靠性好,可以在集群间共享
- 缺点:访问缓存有网络开销
- 场景:缓存数据量较大,可靠性要求较高,需要在集群间共享
- 进程本地缓存,例如 HashMap、GuavaCache
- 优点:读取本地内存,没有网络开销,速度更快
- 缺点:存储容量有限、可靠性较低、无法共享
- 场景:性能要求较高,缓存数据量较小
# Lua 语法
# 初识 Lua
Lua 是一个轻量小巧的脚本语言,用标准语言编写并以源代码形式开放,其设计目的为了让嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能
# 变量和循环
# 数据类型
数据类型 | 描述 |
---|---|
nil | 这个最简单,只有值 nil 属于该类,表示一个无效值(在条件表达式中相当于 false) |
boolean | 包含两个值,false 和 true |
number | 表示双精度类型的实浮点数 |
string | 字符串由一对双引号或单引号来表示 |
function | 由 C 或 Lua 编写的函数 |
table | Lua 中的表 table 其实一个关联数组,数组索引可以是数字、字符串或表类型,在 Lua 里,table 的创建是通过构造表达式来完成,最简单构造表达式是 {},用来创建一个空表 |
# 变量
Lua 声明变量的时候,并不需要指定数据类型
-- 声明字符串 | |
local str = 'hello' | |
-- 声明数字 | |
local num = 21 | |
-- 声明布尔类型 | |
local flag = true | |
-- 声明数字 key 为索引的 table | |
local str = {'java', 'python', 'lua'} | |
-- 声明 table,类似 java 的 map | |
local map = {name='jack', age=21} |
访问 table
-- 访问数组,lua 数组的角标从 1 开始 | |
print(arr[1]) | |
-- 访问 table | |
print(map['name']) | |
print(map.name) |
# 循环
数组、table 都可以利用 for 循环来遍历
遍历数组
-- 声明数组 key 为索引的 table | |
local str = {'java', 'python', 'lua'} | |
-- 遍历数组 | |
for index,value in ipairs(arr) do | |
print(index, value) | |
end |
遍历 table
-- 声明 map,也就是 table | |
local map = {name='jack', age=21} | |
-- 遍历 table | |
for key,value in pairs(map) do | |
print(key,value) | |
end |
# 函数和条件控制
# 函数
定义函数语法
function 函数名(argument11, argument2..., argumentn) | |
-- 函数体 | |
return 返回值 | |
end |
# 条件控制
if(布尔表达式) | |
then | |
-- [布尔表达式为 true 时 执行该语句块] | |
else | |
-- [布尔表达式为 false 时 执行该语句块] | |
end |
操作符 | 描述 | 实例 |
---|---|---|
and | 逻辑与操作符,若 A 为 false,则返回 A,否则返回 B | (A and B) |
or | 逻辑或操作符,若 A 为 true,则返回 A,否则返回 B | (A or B) |
not | 逻辑非操作符,若逻辑运算结果相反,如果条件为 true,逻辑非为 false | not(A and B) |
# OpenResty
# 缓存同步
# 缓存同步策略
- 设置有效期:给缓存设置有效期,到期后自动删除,再次查询更新
- 优势:简单、方便
- 缺点:时效性差,缓存过期之前可能不一致
- 场景:更新频率低,时效性要求低的业务
- 同步双写:在修改数据的同时,直接修改缓存
- 优势:时效性强,缓存与数据库一致
- 缺点:有代码侵入,耦合度高
- 场景:对一致性、时效性要求较高的缓存数据
- 异步通知:修改数据库时发送事件通知,相关服务监听到通知后修改缓存数据
- 优势:低耦合,可以同时通知多个缓存服务
- 缺点:时效性一般,可能存在中间不一致状态
- 场景:时效性要求一般,有多个服务需要同步