【黑马点评 - 高级篇】Redis分布式缓存原理(Redis持久化 RDB AOF + 主从集群 哨兵 分片集群 + 多级缓存)

目录

一、Redis持久化

[1、RDB(Redis 数据快照)](#1、RDB(Redis 数据快照))

(1)RDB触发方式

(2)RDB的执行原理

(3)RDB优缺点

2、AOF(只追加文件)

(1)AOF开启方式

(2)AOF优缺点

3、RDB与AOF对比

[二、Redis 主从集群、哨兵、分片集群](#二、Redis 主从集群、哨兵、分片集群)

[1、Redis 主从集群](#1、Redis 主从集群)

(1)如何搭建主从集群

(2)第一次同步:全量同步

(3)后续同步:增量同步

(4)优化Redis主从集群方法

(5)全量同步与增量同步的区别

[2、Redis 哨兵](#2、Redis 哨兵)

(1)哨兵作用

(2)搭建哨兵集群

(3)RedisTemplate的哨兵模式

[3、Redis 分片集群](#3、Redis 分片集群)

(1)分片集群作用

(2)搭建分片集群

(3)散列插槽

(4)集群伸缩

(5)故障转移

[(6)RedisTemplate 访问分片集群](#(6)RedisTemplate 访问分片集群)

三、多级缓存

1、什么是多级缓存?

2、JVM进程缓存

(1)Caffeine

3、Lua语法

4、多级缓存

[(1)OpenResty 入门](#(1)OpenResty 入门)

[(2)OpenResty 获取请求参数](#(2)OpenResty 获取请求参数)

(3)通过http请求查询Tomcat

(4)Tomcat集群的负载均衡

(5)OpenResty中查询Redis

(6)nginx本地缓存

[5、缓存同步 - Canal 异步通知](#5、缓存同步 - Canal 异步通知)

6、多级缓存总结

四、Redis的最佳实践


一、Redis持久化

1、RDB(Redis 数据快照)

  • RDB 是 Redis Database 的缩写,也常被称为 Redis 数据快照。
  • 核心思想: 在指定的时间间隔内,将内存中的整个数据集以二进制压缩格式保存到一个磁盘文件
  • 当Redis实例故障重启后,从磁盘读取快照文件,恢复数据

(1)RDB触发方式

第一种:手动触发

  • save:由主进程来执行RDB,会阻塞所有命令
  • bgsave:开启子进程执行RDB,避免主进程受到影响

第二种:自动触发

  • 在redis.conf文件中设置了 save 规则,例如 save 900 1(在900秒内至少有1个键被改变)则执行bgsave

  • RDB什么时候执行?++服务停止时,会自动执行一次 RDB 快照++

  • RDB的其它配置也可以在redis.conf文件中设置:

    • // 是否压缩,建议不开启,压缩也会消耗cpu,磁盘的话不值钱
      rdbcompression yes

    • // RDB文件名称
      dbfilename dump.rdb

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

(2)RDB的执行原理

RDB方式bgsave的基本流程:

  • 触发fork() :执行bgsave时,主进程调用**fork()**创建子进程。注:fork() 是一个操作系统系统调用,用于创建一个与当前进程几乎完全相同的新的进程(类似于复制)
  • 并行工作
    • 子进程遍历fork时刻的内存数据,写入临时RDB文件
    • 主进程则继续响应客户端请求
  • Copy-on-Write写时拷贝:当主进程要修改数据时,操作系统会复制被修改的内存页,主进程以后读和写都在这个副本中操作,保证子进程快照数据的一致性。
  • 替换:子进程完成后,用新RDB文件替换旧的RDB文件。

面试时回答 :RDB的核心原理是基于forkCopy-on-Write。执行bgsave时,主进程调用fork创建子进程。子进程负责将fork时刻的内存数据写入临时RDB文件,而主进程继续服务客户端请求。若期间有数据修改,操作系统通过Copy-on-Write机制复制内存页,确保子进程快照的一致性。最后,子进程用新RDB文件替换旧RDB文件。

(3)RDB优缺点

优点:

  • 对主进程影响小:由于持久化工作由子进程完成,主进程几乎不会受到 I/O 操作的阻塞,确保了 Redis 的高性能。

  • 快速重启:相比于另一种持久化方式 AOF,RDB 在恢复大型数据集时速度要快得多,因为它直接是数据的二进制映射。

缺点:

  • 存在数据丢失风险:RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险
  • fork耗时:fork子进程、压缩、写出RDB文件都比较耗时

2、AOF(只追加文件)

  • AOF(Append Only File),全称是 只追加文件。
  • 核心原理:记录下所有会修改数据库状态的写操作命令,并以 Redis 协议格式将这些命令顺序追加到一个文件的末尾。
  • 当 Redis 重启时,它会重新执行一遍 AOF 文件中的所有命令,从而在内存中重建整个数据集。

一个生动的比喻:

  • 如果把 RDB 看作是对数据做的 "定时快照",那么 AOF 就是 "记账本"。
  • RDB(快照):今天下午2:00,你的银行账户里有 1000元。(只记录结果)
  • AOF(日志):
    • 1号,工资收入 +5000元
    • 5号,购物支出 -500元
    • ...(记录每一步操作)
  • 当你想计算当前余额时,只需要从最初的余额开始,按照这个"记账本"把所有的收支重新算一遍,就能得到最新的金额。这就是 AOF 的恢复过程。

(1)AOF开启方式

(2)AOF优缺点

优点:

  • 数据丢失的风险低:通过合适的同步策略(如 everysec),可以将数据丢失的风险降至极低,甚至秒级。

缺点:

  • 文件体积大:AOF 文件记录的是每一个操作命令,其体积通常会大于同数据集的 RDB 文件。
  • 数据恢复速度较慢:在数据集非常大时,重新执行所有 AOF 文件中的命令来恢复数据,这个过程会比加载 RDB 文件慢得多。

3、RDB与AOF对比

如果想要启动速度快 ------ RDB

如果想要数据安全性高 ------ AOF

面试官问:你是选择RDB还是AOF?

答:我通常采用两者结合的方案。使用AOF 作为主持久化 方式,配置为每秒同步,保证数据安全。同时,定期执行RDB快照,用于历史备份。这样既保证了数据可靠性,又兼顾了恢复效率和运维便利性。

二、Redis 主从集群、哨兵、分片集群

1、Redis 主从集群

(1)如何搭建主从集群

假设有A、B两个Redis实例,如何让B作为A的slave节点?

在B节点执行命令:slaveof 【A的IP】 【A的port】

(2)第一次同步:全量同步

主从第一次同步是全量同步

  • 第一阶段:返回数据版本信息
    • slave执行replicao命令与master建立连接,并请求数据同步
    • master判断slave是否是第一次同步,若是第一次,则返回master的数据版本信息
    • salve接收到数据版本信息,并保存
  • 第二阶段:发送RDB文件
    • master执行bgsave命令,生成RDB文件并发送
    • slave清空本地数据并加载RDB文件
    • 因为bgsave是异步命令,在RDB文件发送过程中还会有新数据更新,因此master还需要记录RDB期间所有命令,存入repl_baklog文件,这样RDB + repl_baklog文件就组成了完整的数据内容
  • 第三阶段:发送repl_baklog中的命令
    • master持续发送repl_baklog中的命令,slave执行接收到的命令,保证同步

那master如何判断slave是不是第一次来同步数据?

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

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

因此slave做数据同步,必须向master声明自己的 replication idoffset,master才可以判断到底需要同步哪些数据

梳理一下全量同步流程

  • slave节点请求增量同步
  • master节点判断replid,发现不一致,拒绝增量同步(replid不一致说明是第一次同步,选择全量同步)
  • master将完整内存数据生成RDB,发送RDB到slave
  • slave清空本地数据,加载master的RDB
  • master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
  • slave执行接收到的命令,保持与master之间的同步

(3)后续同步:增量同步

repl_baklog 是一个环形缓冲区,用于记录主节点执行的写命令。

  • 固定大小的先进先出(FIFO)队列
  • 由三个重要指针组成:
    • master_repl_offset:主节点当前写入位置
    • slave_repl_offset:从节点当前读取位置
    • backlog_first_byte_offset:缓冲区起始位置
  • 当缓冲区写满时,新的数据会覆盖旧的数据
  • 大小由配置参数 repl-backlog-size 决定,默认 1MB

正常情况

  • 主节点持续写入新命令到 repl_baklog
  • 从节点及时读取并执行这些命令
  • master_repl_offset 与 slave_repl_offset 差值很小
  • 从节点数据与主节点基本实时同步

异常情况

  • 网络长时间中断从节点长时间宕机
  • repl_baklog 中的旧数据被新数据覆盖
  • 从节点的 slave_repl_offset 指向的数据已不存在于缓冲区
  • 触发全量同步,主节点重新生成 RDB 文件发送给从节点

(4)优化Redis主从集群方法

我们知道全量同步需要生成RDB文件,非常消耗系统资源和时间

因此可以从以下几个方面来优化Redis主从集群:

  • 启用无磁盘复制:在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO(直接将内容复制到网络中传给slave)
  • 从节点内存限制:Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
  • 提高repl_baklog的大小:适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
  • 采用主从从链:限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力

(5)全量同步与增量同步的区别

简述全量同步和增量同步区别?

  • 全量同步: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时

2、Redis 哨兵

(1)哨兵作用

Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复

哨兵的结构和作用如下:

  • 监控:Sentinel 会不断检查master和slave是否按预期工作(主观下线、客观下线)

  • 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主

  • 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端

哨兵如何实现监控集群状态?

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

  • 主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
  • 客观下线:若超过指定数量 (quorum) 的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。

哨兵如何选举新的master?

  • 首先判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
  • 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
  • 如果slave-priority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
  • 最后判断slave节点的运行id大小,越小优先级越高(offset值若一致说明这些slave节点都一样新了,随便选一个都可以)

哨兵如何实现故障转移?

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

  • 设立新主 :sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master
  • 通知其他人谁是新 主:sentinel给所有其它slave发送slaveof 新主IP命令,让这些slave成为新master的从节点,开始从新的master上同步数据
  • 剥夺旧主权利使其成为从:sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点

(2)搭建哨兵集群

创建3个哨兵在同一IP的不同端口

要在同一台虚拟机开启3个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。我们创建三个文件夹,名字分别叫s1、s2、s3

bash 复制代码
# 进入/tmp目录
cd /tmp
# 创建目录
mkdir s1 s2 s3

然后我们在s1目录创建一个sentinel.conf文件,添加下面的内容:

bash 复制代码
port 27001  #指定 Sentinel 服务运行的端口号

sentinel announce-ip 192.168.150.101 #声明 Sentinel 对外公布的 IP 地址

sentinel monitor mymaster 192.168.150.101 7001 2  #监控主 Redis 服务器
    # mymaster:主服务器名称(自定义标识)
    # 192.168.150.101:主 Redis 服务器的 IP
    # 7001:主 Redis 服务器的端口
    # 2:客观下线,quorum,需要至少 2 个 Sentinel 同意才能触发故障转移

sentinel down-after-milliseconds mymaster 5000 #主观下线:判断主节点不可用的超时时间

sentinel failover-timeout mymaster 60000 #故障转移超时时间

dir "/tmp/s1" #指定 Sentinel 的工作目录

然后将s1/sentinel.conf文件拷贝到s2、s3两个目录中(在/tmp目录执行下列命令)

bash 复制代码
# 方式一:逐个拷贝
cp s1/sentinel.conf s2
cp s1/sentinel.conf s3
# 方式二:管道组合命令,一键拷贝
echo s2 s3 | xargs -t -n 1 cp s1/sentinel.conf

修改s2、s3两个文件夹内的配置文件,将端口分别修改为27002、27003

bash 复制代码
sed -i -e 's/27001/27002/g' -e 's/s1/s2/g' s2/sentinel.conf
sed -i -e 's/27001/27003/g' -e 's/s1/s3/g' s3/sentinel.conf

为了方便查看日志,我们打开3个ssh窗口,分别启动3个redis实例,启动命令:

bash 复制代码
# 第1个
redis-sentinel s1/sentinel.conf
# 第2个
redis-sentinel s2/sentinel.conf
# 第3个
redis-sentinel s3/sentinel.conf

启动后:

最后我们尝试让7001主节点宕机

此时7003被选为新主

此时7002重新认新主,并进行同步

(3)RedisTemplate的哨兵模式

  • 服务启动:应用通过 Sentinel 节点发现 Redis 主从结构
  • 故障转移:当主节点宕机时,Sentinel 自动选举新主节点
  • 客户端感知:RedisTemplate 自动感知新的主节点并切换连接
  • 读写分离:根据配置的策略将读请求分发到不同节点

3、Redis 分片集群

(1)分片集群作用

主从和哨兵模式解决了:

✅ 高可用(故障自动转移)

✅ 高并发读(读写分离)

但未解决:

海量数据存储问题 - 单机内存容量有限

高并发写的问题 - 写压力集中在一个主节点

分片集群的核心特征

  • 多主节点架构: 集群中有多个 master 节点,每个 master 保存不同的数据子集

  • **高可用保障:**每个 master 都可以有多个 slave 节点,实现数据冗余和故障自动切换

  • ping互相检测: master 之间通过 ping 命令互相监测健康状

  • 智能路由: 客户端可以连接集群中任意节点, 集群会自动将请求**转发到正确的节点,**对客户端透明,无需关心数据具体位置

(2)搭建分片集群

分片集群需要的节点数量较多,这里我们搭建一个最小的分片集群,包含3个master节点,每个master包含一个slave节点,结构如下:

这里我们会在同一台虚拟机中开启6个redis实例,模拟分片集群,信息如下:

删除之前的7001、7002、7003这几个目录,重新创建出7001、7002、7003、8001、8002、8003目录:

bash 复制代码
# 进入/tmp目录
cd /tmp
# 删除旧的,避免配置干扰
rm -rf 7001 7002 7003
# 创建目录
mkdir 7001 7002 7003 8001 8002 8003

在/tmp下准备一个新的redis.conf文件,内容如下:

bash 复制代码
port 6379

cluster-enabled yes # 开启集群功能

cluster-config-file /tmp/6379/nodes.conf # 集群的配置文件名称,不需要我们创建,由redis自己维护

cluster-node-timeout 5000 # 节点心跳失败的超时时间

dir /tmp/6379 # 持久化文件存放目录

bind 0.0.0.0 # 绑定地址

daemonize yes # 让redis后台运行

replica-announce-ip 192.168.150.101 # 注册的实例ip

protected-mode no # 保护模式

databases 1 # 数据库数量

logfile /tmp/6379/run.log # 日志

将这个文件拷贝到每个目录下:

bash 复制代码
# 进入/tmp目录
cd /tmp
# 执行拷贝
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf

修改每个目录下的redis.conf,将其中的6379修改为与所在目录一致:

bash 复制代码
# 进入/tmp目录
cd /tmp
# 修改配置文件
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf

因为已经配置了后台启动模式,所以可以直接启动服务:

bash 复制代码
# 进入/tmp目录
cd /tmp
# 一键启动所有服务
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf

通过ps查看状态:

bash 复制代码
ps -ef | grep redis

如果要关闭所有进程,可以执行命令:

bash 复制代码
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown

虽然服务启动了,但是目前每个服务之间都是独立的,没有任何关联。

我们需要执行命令来创建集群,在Redis5.0之前创建集群比较麻烦,5.0之后集群管理命令都集成到了redis-cli中。

1)Redis5.0之前

Redis5.0之前集群命令都是用redis安装包下的src/redis-trib.rb来实现的。因为redis-trib.rb是有ruby语言编写的所以需要安装ruby环境。

bash 复制代码
 # 安装依赖

 yum -y install zlib ruby rubygems

 gem install redis

然后通过命令来管理集群:

bash 复制代码
# 进入redis的src目录

cd /tmp/redis-6.2.4/src

# 创建集群

./redis-trib.rb create --replicas 1 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

2)Redis5.0以后

我们使用的是Redis6.2.4版本,集群管理以及集成到了redis-cli中,格式如下:

bash 复制代码
redis-cli --cluster create --cluster-replicas 1 
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
  • 【redis-cli --cluster】代表集群操作命令
  • 【create】代表是创建集群
  • 【--cluster-replicas 1】指定集群中每个master的副本个数为1,此时 节点总数 ÷ (replicas + 1) 得到的就是master的数量。因此节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master

这里终端告诉你,它打算这么分主从结构:

通过命令可以查看集群状态:

bash 复制代码
redis-cli -p 7001 cluster nodes

(3)散列插槽

数据 key 与插槽绑定,而不是与节点绑定

Redis 根据 key 的【有效部分】计算插槽值:

slot = CRC16(有效部分) % 16384

为什么key与插槽绑定,而不是与节点绑定?

核心目的是为了实现集群的无缝扩缩容

通过管理固定的16384个插槽与节点的映射关系,在增加或减少节点时,仅需将部分插槽(及其对应的数据)在节点间迁移即可,而无需重新计算和迁移所有数据

判断 key 存储位置的流程:

  • 将 16384 个插槽分配到各个实例节点

  • 根据 key 的有效部分计算哈希值

  • 对 16384 取余得到插槽编号

  • 找到该插槽所在的实例节点

确保同类数据存储在相同实例的方法:

  • 为同类数据设置相同的有效部分

  • 例如在所有 key 前添加统一的前缀:{typeId}key1{typeId}key2

通过这种机制,Redis 集群既能实现数据的均匀分布,又能保证相关数据集中存储在同一节点。

(4)集群伸缩

扩容或缩容的本质是重新分配槽位,并迁移其对应的数据。

扩容流程

  • 添加新节点:将新的空 Master 节点加入集群。

  • 转移槽位 :从现有各 Master 节点中匀出一部分槽位分配给新节点。

  • 数据迁移 :槽位分配后,集群会自动将槽位内对应的数据迁移至新节点。

缩容流程

  • 转移槽位与数据 :将要移除的节点负责的所有槽位,全部迁移到其他现有节点上。

  • 移除空节点:当节点不再负责任何槽位且无数据后,即可从集群中安全移除。

(5)故障转移

故障转移 = 客观下线触发 + 从节点竞选 + 主节点投票 + 槽位继承

  • 集群模式无需额外哨兵进程 ,故障检测和转移功能内置于每个主节点中。

  • 选举由所有主节点投票完成,而非专门的哨兵节点。

一般情况下,某节点宕机,集群会自动进行故障转移,而当我们需要进行节点替换时,就会用到主动故障转移:

CLUSTER FAILOVER:在目标从节点上执行此命令,可手动、安全地将其提升为主节点

(6)RedisTemplate 访问分片集群

三、多级缓存

1、什么是多级缓存?

传统缓存存在的问题

传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,存在下面的问题:

  • 请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈

  • Redis缓存失效时,会对数据库产生冲击

多级缓存

多级缓存是一个由 【Nginx本地缓存 → Redis分布式缓存 → JVM进程缓存 → 数据库】 组成的多层次缓存系统

第一级:Nginx(反向代理/本地缓存)

  • 位置:请求最先到达的地方。

  • 作用 :Nginx作为反向代理和web服务器,可以利用自身机制实现本地缓存。如果请求的资源在这里命中,Nginx直接返回响应,请求不会到达后端Tomcat

第二级:Redis(分布式缓存)

  • 位置 :当请求穿透Nginx,到达Tomcat后,应用程序首先查询Redis

  • 作用:它的目的是保护数据库,避免大量请求直接冲击数据库。

第三级:JVM进程缓存

  • 位置 :当Redis中未命中(或根据特定策略)时,查询JVM进程缓存

  • 作用 :这是在Tomcat进程内部的内存缓存(如使用Caffeine)。它用于缓存极其热点 的数据,其目的是为了减轻Redis的负担并追求极致的访问速度。

第四级:数据库

  • 位置:整个链条的最后。

  • 作用:数据的最终存储。当所有缓存都未命中时,才会查询这里。

2、JVM进程缓存

博主偷懒,导入Demo的过程参考视频教学~ 这里只记一些知识点~

nginx负载均衡在nginx.conf中配置

缓存一般分为【分布式缓存】和【进程本地缓存】

(1)Caffeine

Caffeine 是一个高性能的、用于 Java 8+ 的进程内缓存库(即 JVM 本地缓存)。

它在业界被广泛认为是 Guava Cache 的现代替代品,提供了更出色的性能和高度的灵活性。

可以把它理解为一个功能强大、速度极快的 HashMap

bash 复制代码
void testBasicOps() {
    // 构建cache对象
    Cache<String, String> cache = Caffeine.newBuilder().build();

    // 存数据
    cache.put("gf", "迪丽热巴");

    // 取数据
    String gf = cache.getIfPresent("gf");
    System.out.println("gf = " + gf);

    // 取数据,如果未命中,则查询数据库
    String defaultGF = cache.get("defaultGF", key -> {
        // 根据key去数据库查询数据
        return "柳岩";
    });
    System.out.println("defaultGF = " + defaultGF);
}

Caffeine提供了三种缓存驱逐策略:

  • 基于容量:设置缓存的数量上限
java 复制代码
// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
  .maximumSize(1) // 设置缓存大小上限为 1
  .build();
  • 基于时间:设置缓存的有效时间
java 复制代码
// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
  .expireAfterWrite(Duration.ofSeconds(10)) // 设置缓存有效期为 10 秒,从最后一次写入开始计时
  .build();
  • 基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。

在默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后,或者在空闲时间完成对失效数据的驱逐。

3、Lua语法

(1)数据类型

(2)变量

(3)循环

(4)函数

(5)条件控制

4、多级缓存

(1)OpenResty 入门

  • OpenResty 本质上不是一个全新的 Web 服务器,而是一个强大的、基于 Nginx 的应用程序平台
  • 它通过捆绑 Nginx 核心、大量的第三方模块以及一组关键的 Lua 库,扩展了 Nginx 的功能
  • 允许开发者使用 Lua 脚本语言,在 Nginx 的各个处理阶段(如访问阶段、内容生成阶段、日志阶段等)嵌入业务逻辑,而无需编写传统的 C 模块。

虚拟机里的 OpenResty 服务(192.168.150.101:8081)还没有准备好返回真实的商品数据,那么我们就先让它**返回一段假的商品数据,**保证前端不报错502

【步骤1】修改nginx.conf文件

  • 在nginx.conf的http下面,添加对OpenResty的Lua模块的加载
bash 复制代码
# 加载lua 模块
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
# 加载c模块
lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
  • 在nginx.conf的server下面,添加对/api/item这个路径的监听
bash 复制代码
location /api/item {
    # 响应类型,这里返回json
    default_type application/json;
    # 响应数据由 lua/item.lua这个文件来决定
    content_by_lua_file lua/item.lua;
}

【步骤二】编写item.lua文件

这时再返回前端,发现该接口成功接收到数据,也不报错

(2)OpenResty 获取请求参数

现在我们需要获取【查询商品接口】的请求参数,发现该接口是【路径参数】,则用正则表达式匹配获取

这样我们前端不管路径切换成什么(10001、10002......都可以),都能访问到查询商品页面

(3)通过http请求查询Tomcat

redis缓存的数据需要从数据库先取出来再存入redis,因此我们首先要通过nginx向tomcat发起http请求查询tomcat

我们需要实现的功能如下:

【1】nginx发送http请求,封装http请求工具

nginx提供了内部API用以发送http请求:

bash 复制代码
local resp = ngx.location.capture("/path", {
    method = ngx.HTTP_GET,  -- 请求方式
    args = {a=1, b=2},      -- get方式传参数
    body = "c=3&d=4"        -- post方式传参数
})

返回的响应内容包括:

  • resp.status: 响应状态码
  • resp.header: 响应头,是一个table
  • resp.body: 响应体,就是响应数据

注意:这里的path是路径,并不包含IP和端口。这个请求会被nginx内部的server监听并处理

也就是说nginx发起http请求,收到的响应内容仍然在nginx内部

但是我们希望这个请求发送到Tomcat服务器,所以还需要编写一个server来对这个路径做反向代理:

bash 复制代码
location /path {
    # 这里是windows电脑的ip和Java服务端口,需要确保windows防火墙处于关闭状态
    proxy_pass http://192.168.150.1:8081;
}

这样就会将返回的响应内容转发给tomcat

1、第一步先在nginx.conf中配置负载均衡

2、把http查询的请求封装为一个函数,放到OpenResty函数库中,方便后期使用

  • 在/usr/local/openresty/lualib目录下创建common.lua文件:
bash 复制代码
vi /usr/local/openresty/lualib/common.lua
  • 在common.lua中封装http查询的函数:
Lua 复制代码
-- 封装函数,发送http请求,并解析响应
local function read_http(path, params)
    local resp = ngx.location.capture(path, {
        method = ngx.HTTP_GET,
        args = params,
    })
    if not resp then
        -- 记录错误信息,返回404
        ngx.log(ngx.ERR, "http not found, path: ", path, ", args: ", args)
        ngx.exit(404)
    end
    return resp.body
end

-- 将方法导出
local _M = {
    read_http = read_http
}
return _M

3、查询商品及库存,组合数据并返回

(4)Tomcat集群的负载均衡

负载均衡策略:hash $request_url - 根据请求URL进行哈希计算,实现相同URL总是路由到同一台Tomcat

  • 如果 hash $request_uri
    • Nginx 会根据客户端请求的 URL(比如 /item/10001 计算一个哈希值。同一个 URL 的请求,永远会被转发到同一台后端 Tomcat 服务器上。
    • **同一个URL请求,**可以直接从自己的本地缓存中返回数据,速度极快,无需再查询数据库。
  • 如果 没有 hash $request_uri(默认是轮询策略):
    • Nginx 会使用默认的 轮询 策略。第一个请求发给 Tomcat-1,第二个请求发给 Tomcat-2,第三个又发给 Tomcat-1,以此类推。
    • **本地进程缓存几乎失效,**对于同一个热点商品10001,请求被均匀地分散到了所有Tomcat服务器上,导致每个Tomcat服务器都需要独立地查询一次数据库来建立缓存

(5)OpenResty中查询Redis

为什么在OpenResty中操作Redis?

这是一种架构上的优化 ,其核心目标是在业务逻辑简单的读场景 下,绕过Tomcat,用最短的路径获取数据,以实现极致的性能。

(6)nginx本地缓存

5、缓存同步 - Canal 异步通知

6、多级缓存总结

四、Redis的最佳实践

相关推荐
b***59431 小时前
分布式WEB应用中会话管理的变迁之路
前端·分布式
Z_Easen1 小时前
RabbitMQ 技术深度解析:从核心概念到可靠性实践
分布式·rabbitmq
爬山算法2 小时前
Redis(135)Redis的网络模型是什么?
网络·数据库·redis
7***37452 小时前
HarmonyOS分布式能力的核心技术
分布式·华为·harmonyos
L.EscaRC2 小时前
Redis大Key与内存不足问题深度解析与应对策略
数据库·redis·缓存
q***75182 小时前
RabbitMQ 客户端 连接、发送、接收处理消息
分布式·rabbitmq·ruby
q***42053 小时前
PHP使用Redis实战实录2:Redis扩展方法和PHP连接Redis的多种方案
开发语言·redis·php
天选之女wow3 小时前
【Hard——Day8】65.有效数字、68.文本左右对齐、76.最小覆盖子串
linux·运维·redis·算法·leetcode
chushiyunen3 小时前
redis命令 geo(对地理坐标的支持)
数据库·redis·缓存