一、三种缓存同步基础方案
表格
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 设置有效期 | 给缓存设置 TTL,到期自动删除;查询时回写 | 最简单,无代码侵入 | 数据不一致窗口大,时效差 | 更新频率低、对一致性要求低 |
| 同步双写 | 修改数据库的同时,直接更新 / 删除缓存 | 实时性强,一致性好 | 业务代码耦合高,数据库和缓存双操作,存在失败风险 | 强一致性、高时效业务 |
| 异步通知 | 改库后发送事件,监听服务收到事件再更新缓存 | 低耦合,可同时通知多个下游服务 | 存在短暂数据不一致 | 多服务需要同步缓存,时效性要求一般 |
异步通知有两种实现:基于 MQ 、基于 Canal 监听 binlog
1. 基于 MQ 异步通知流程
- 用户修改商品,
item-service先写 MySQL - 写入成功后,item-service 向 MQ 发送数据变更消息
cache-service监听 MQ 消息- 收到消息后,更新 Redis 缓存 ✅优点:业务可控,消息可以做重试 ❌缺点:需要业务代码手动发消息,代码有侵入;如果数据库成功但是发 MQ 失败,会出现数据不一致
2. 基于 Canal 异步通知流程
- 用户修改商品,
item-service写入 MySQL - MySQL 写入 binlog 二进制日志
- Canal 伪装成 MySQL 的 Slave 节点,拉取 master 的 binlog,监听数据变更
- Canal 把变更行数据推送给 Canal 客户端(cache-service)
- 客户端收到变更,更新 Redis 缓存 ✅优点:完全无业务代码侵入,不需要业务服务发消息;可以监听数据库所有变更 ❌缺点:依赖 binlog,有一定延迟;需要单独部署 Canal 服务
Canal 核心原理:伪装成 MySQL 从库,向 Master 发送 dump 协议,拉取 binlog,解析 binlog 拿到 DML 变更(增 / 删 / 改),推送给客户端。 下游可以对接 Redis、ES、MQ、HBase 等。
二、SpringBoot 整合 Canal 客户端
-
引入 Maven 依赖(第三方 canal-starter)
top.javatool canal-spring-boot-starter 1.2.1-RELEASE -
yml 配置
canal:
destination: heima # 和canal-server配置的实例名称一致
server: 192.168.150.101:11111 # canal服务地址端口 -
实体类(JPA 注解做字段映射)
@Data
@TableName("tb_item")
public class Item {
@Id
@TableId(type = IdType.AUTO)
private Long id;
@Column(name = "name")
private String name;
private Date updateTime;// 数据库不存在的字段,忽略映射 @Transient @TableField(exist = false) private Integer stock; @Transient @TableField(exist = false) private Integer sold;}
-
编写监听器 Handler
@CanalTable("tb_item") // 监听哪张表
@Component
public class ItemHandler implements EntryHandler- {
// 新增
@Override
public void insert(Item item) {
// 新增数据写入Redis
}
// 更新:before修改前,after修改后
@Override
public void update(Item before, Item after) {
// 更新Redis,同步本地缓存
}
// 删除
@Override
public void delete(Item item) {
// 删除Redis,清理本地缓存
}
} - {
三、OpenResty Nginx 本地缓存(lua_shared_dict)
OpenResty 提供lua_shared_dict,在 Nginx 多个 worker 进程之间共享内存缓存,作为多级缓存最前端的本地缓存。
-
nginx.conf http 块配置
共享字典,缓存名称item_cache,大小150M
lua_shared_dict item_cache 150m;
-
Lua 脚本操作共享字典
-- 获取缓存对象
local item_cache = ngx.shared.item_cache
-- 写入:key,value,过期时间(秒),0永不过期
item_cache:set('key', 'value', 1000)
-- 读取
local val = item_cache:get('key')
多级缓存顺序:Nginx 本地共享字典缓存 → Redis 分布式缓存 → MySQL 数据库 当数据库变更,通过 Canal 监听 binlog,同时更新 Redis + 清理 Nginx 本地缓存。