【黑马点评 - 高级篇】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的最佳实践

相关推荐
冰冰菜的扣jio1 小时前
Redis基础数据结构
数据结构·数据库·redis
musenh2 小时前
redis和jedis
数据库·redis·缓存
win x3 小时前
Redis 主从复制
java·数据库·redis
巧克力味的桃子3 小时前
Spark 课程核心知识点复习汇总
大数据·分布式·spark
Java 码农3 小时前
RabbitMQ集群部署方案及配置指南05
分布式·rabbitmq
小马爱打代码4 小时前
ZooKeeper:五种经典应用场景
分布式·zookeeper·云原生
brave_zhao4 小时前
javaFx清空缓存动画特效
缓存
廋到被风吹走5 小时前
【Spring】IoC容器深度解析:Bean生命周期与循环依赖三级缓存
java·spring·缓存
Knight_AL5 小时前
Redis ZSet 实现排行榜(支持分数相同按时间顺序排序)
数据库·redis·缓存
冰冰菜的扣jio6 小时前
入门redis——让你的查询快到起飞
数据库·redis·缓存