黑马程序员Redis教程学习笔记(第3部分):多级缓存与缓存同步策略
课程概述
本篇笔记涵盖黑马程序员Redis教程中关于多级缓存架构的高级内容,主要包括Tomcat集群负载均衡、Redis缓存预热、OpenResty本地缓存以及缓存同步策略等核心知识点。
1. Tomcat集群负载均衡策略
问题背景
在多级缓存架构中,当有多台Tomcat服务器时,传统的轮询负载均衡算法会导致JVM进程缓存无法有效利用的问题。因为轮询算法会将相同商品ID的请求分配到不同的服务器上,导致缓存命中率降低。
基于URL的负载均衡
使用Nginx的hash算法实现基于请求URL的负载均衡:
upstream tomcat_cluster {
hash $request_uri; # 基于请求URL进行hash计算
server 192.168.150.101:8081;
server 192.168.150.101:8082;
}实现原理
- 对请求路径进行hash运算,得到一个数值
- 对Tomcat服务器数量取模,确定目标服务器
- 相同路径的请求总是路由到同一台服务器
- 确保JVM进程缓存能够持续命中
2. Redis缓存预热
问题与解决方案
- 问题:服务冷启动时Redis缓存为空,大量请求直接访问数据库
- 解决方案:在项目启动时预先加载热点数据到Redis
实现方式
- 依赖引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>- 配置Redis连接:
spring:
redis:
host: 192.168.150.101
port: 6379- 预热实现:
@Component
public class RedisHandler implements InitializingBean {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ItemService itemService;
@Autowired
private ItemStockService stockService;
@Override
public void afterPropertiesSet() throws Exception {
// 查询所有商品信息
List<Item> items = itemService.list();
// 将商品信息存入Redis
for (Item item : items) {
// 序列化为JSON
String json = mapper.writeValueAsString(item);
// 存储到Redis,key格式为 item:id
redisTemplate.opsForValue().set("item:" + item.getId(), json);
}
// 查询并存储库存信息
List<ItemStock> stocks = stockService.list();
for (ItemStock stock : stocks) {
String json = mapper.writeValueAsString(stock);
// 存储到Redis,key格式为 stock:id
redisTemplate.opsForValue().set("stock:" + stock.getId(), json);
}
}
}3. OpenResty查询Redis
Redis模块引入
OpenResty提供了Redis模块,使用方式:
-- 引入Redis模块
local redis = require "resty.redis"
local red = redis:new()
-- 设置连接超时时间(单位:毫秒)
red:set_timeout(1000) -- 连接超时
red:set_timeout(1000) -- 发送超时
red:set_timeout(1000) -- 接收超时连接管理与数据操作
-- 连接Redis
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "failed to connect redis: ", err)
return
end
-- 查询数据
local res, err = red:get(key)
if not res then
ngx.log(ngx.ERR, "failed to query redis: ", err)
return
end
if res == ngx.null then
res = nil
end
-- 关闭连接(实际上是放回连接池)
local ok, err = red:set_keepalive(10000, 100) -- 最大空闲时间10秒,连接池大小100封装Redis查询函数
-- 封装Redis查询函数
local function readRedis(ip, port, key)
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect(ip, port)
if not ok then
ngx.log(ngx.ERR, "failed to connect redis: ", err)
return nil
end
local resp, err = red:get(key)
if not resp then
ngx.log(ngx.ERR, "failed to query redis: ", err)
return nil
end
if resp == ngx.null then
resp = nil
end
-- 放回连接池
local ok, err = red:set_keepalive(10000, 100)
return resp
end4. OpenResty本地缓存
共享字典配置
在nginx.conf中配置共享字典:
# 开启本地缓存
lua_shared_dict item_cache 150m;本地缓存操作
-- 获取本地缓存对象
local item_cache = ngx.shared.item_cache
-- 存储数据(带过期时间)
item_cache:set(key, value, expire_time)
-- 获取数据
local value = item_cache:get(key)多级缓存查询逻辑
-- 修改readData函数,实现多级缓存
local function readData(ip, port, key, path, params, expire_time)
-- 1. 优先查询本地缓存
local value = item_cache:get(key)
if not value then
ngx.log(ngx.ERR, "本地缓存查询失败,尝试查询Redis")
-- 2. 本地缓存未命中,查询Redis
value = readRedis(ip, port, key)
if not value then
ngx.log(ngx.ERR, "Redis查询失败,尝试查询Tomcat")
-- 3. Redis未命中,查询Tomcat
local res = ngx.location.capture(
path,
{
method = ngx.HTTP_GET,
args = params
}
)
value = res.body
end
-- 4. 查询成功后,写入本地缓存
item_cache:set(key, value, expire_time)
end
return value
end5. 缓存同步策略
常见同步策略对比
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 有效期控制 | 简单方便,无代码入侵 | 实时性差 | 更新频率低、实时性要求低的业务 |
| 同步双写 | 实时性强,数据一致性高 | 代码侵入度高,耦合度高 | 对一致性要求极高的业务 |
| 异步通知 | 低耦合,代码侵入少 | 实时性相对较差 | 对实时性要求不高、多服务同步场景 |
Canal实现原理
MySQL主从同步原理
- Master节点:执行数据增删改查操作,记录到binary log文件
- Slave节点:开启线程不断读取binary log,重放操作以保持数据一致
Canal工作原理
Canal通过伪装成MySQL的Slave节点,监听MySQL的binary log变化:
# MySQL主从同步流程
Master -> binary log -> Slave (读取并重放操作)
↓
Canal (伪装成Slave读取binary log)Canal安装配置
1. 开启MySQL主从同步
修改MySQL配置文件:
# binlog文件位置和名称
log-bin=mysql-bin
# 监控的数据库
binlog-do-db=hmdp2. 创建Canal用户并授权
-- 创建Canal用户
CREATE USER 'canal'@'%' IDENTIFIED BY 'canal';
-- 授权
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
-- 刷新权限
FLUSH PRIVILEGES;3. 创建Docker网络
# 创建网络
docker network create hmdp
# 将MySQL容器加入网络
docker network connect hmdp mysql4. 运行Canal容器
docker run -p 11111:11111 --name canal \
-e canal.destinations=hmdp \
-e canal.instance.master.address=mysql:3306 \
-e canal.instance.dbUsername=canal \
-e canal.instance.dbPassword=canal \
-e canal.instance.connectionCharset=UTF-8 \
-e canal.instance.tsdb.enable=true \
-e canal.instance.gtidon=false \
--network=hmdp \
-d canal/canal-server:v1.1.5总结
本篇笔记涵盖了多级缓存架构的高级实现,包括: - 通过基于URL的负载均衡算法解决JVM进程缓存失效问题 - 通过Redis缓存预热避免冷启动时的数据库压力 - 通过OpenResty的Redis模块和本地缓存实现多级缓存体系 - 通过Canal实现数据库变更的实时监听和缓存同步
这些技术共同构成了一个高性能、高可用的多级缓存系统,能够有效提升系统性能并保证数据一致性。