目录
[1、RDB(Redis 数据快照)](#1、RDB(Redis 数据快照))
[二、Redis 主从集群、哨兵、分片集群](#二、Redis 主从集群、哨兵、分片集群)
[1、Redis 主从集群](#1、Redis 主从集群)
[2、Redis 哨兵](#2、Redis 哨兵)
[3、Redis 分片集群](#3、Redis 分片集群)
[(6)RedisTemplate 访问分片集群](#(6)RedisTemplate 访问分片集群)
[(1)OpenResty 入门](#(1)OpenResty 入门)
[(2)OpenResty 获取请求参数](#(2)OpenResty 获取请求参数)
[5、缓存同步 - Canal 异步通知](#5、缓存同步 - Canal 异步通知)
一、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的核心原理是基于fork 和Copy-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 id 和 offset,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文件,添加下面的内容:
bashport 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
bashsed -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文件,内容如下:
bashport 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查看状态:
bashps -ef | grep redis如果要关闭所有进程,可以执行命令:
bashprintf '%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:80032)Redis5.0以后
我们使用的是Redis6.2.4版本,集群管理以及集成到了redis-cli中,格式如下:
bashredis-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
这里终端告诉你,它打算这么分主从结构:
通过命令可以查看集群状态:
bashredis-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。
bashvoid 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这个路径的监听
bashlocation /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请求:
bashlocal 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来对这个路径做反向代理:
bashlocation /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文件:
bashvi /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 _M3、查询商品及库存,组合数据并返回
(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、多级缓存总结
































































