在多级缓存中的数据一致性问题,也就是缓存同步的问题
数据同步策略
要是使用消息队列的方式,我们还需要修改代码,至少需要发送一条通知吧。
canal可以监听数据库的变化,监听到数据库的变化,发送通知变更,侵入性更小。
安装Canal
canal地址: https://github.com/alibaba/canal
安装和配置参考:https://blog.csdn.net/shall_zhao/article/details/142147266
监听canal实现缓存同步
但是官方原生提供的canal客户端是比较麻烦的,我们使用第三方开源的整合了springboot的canal客户端。😍
配置好以后,canal就实现了自动装配。
<dependency>
<groupId>top.javatool</groupId>
<artifactId>canal-spring-boot-starter</artifactId>
<version>1.2.1-RELEASE</version>
</dependency>
canal:
destination: heima
server: 192.168.10.88:11111
然后去我们的Item上添加注解
@Data
@TableName("tb_item")
public class Item {
@TableId(type = IdType.AUTO)
@Id
private Long id;//商品id
private String name;//商品名称
private String title;//商品标题
private Long price;//价格(分)
private String image;//商品图片
private String category;//分类名称
private String brand;//品牌名称
private String spec;//规格
private Integer status;//商品状态 1-正常,2-下架
private Date createTime;//创建时间
private Date updateTime;//更新时间
@TableField(exist = false)
@Transient
private Integer stock;
@TableField(exist = false)
@Transient
private Integer sold;
}
因为我们在监听到数据库发生变动的时候,只会涉及到保存数据到redis和删除redis中的数据,我们可以把这两个函数封装到RedisHandler中,直接调用。
@Component
public class RedisHandler implements InitializingBean {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private IItemService itemService;
@Autowired
private IItemStockService stockService;
// spring中的默认json处理工具
private static final ObjectMapper MAPPER = new ObjectMapper();
// afterPropertiesSet()会在bean创建完,Autowired注入以后执行,实现缓存预热效果
@Override
public void afterPropertiesSet() throws Exception {
// 初始化缓存
// 1. 查询商品信息,我们查所有的,实际上应该查询热点数据
List<Item> itemList = itemService.list();
// 2. 放入缓存
for (Item item : itemList) {
// 2.1 item序列化为JSON
String json = MAPPER.writeValueAsString(item);
// 2.2 存入redis
stringRedisTemplate.opsForValue().set("item:id:" + item.getId(), json);
}
// 1. 查询库存信息,
List<ItemStock> stockList = stockService.list();
// 2. 放入缓存
for (ItemStock stock : stockList) {
// 2.1 item序列化为JSON
String json = MAPPER.writeValueAsString(stock);
// 2.2 存入redis
stringRedisTemplate.opsForValue().set("item:stock:id:" + stock.getId(), json);
}
}
public void saveItem(Item item){
try{
String json = MAPPER.writeValueAsString(item);
stringRedisTemplate.opsForValue().set("item:id:" + item.getId(), json);
} catch (JsonProcessingException e){
throw new RuntimeException(e);
}
}
public void deleteItemById(Long id){
stringRedisTemplate.delete("item:id:" + id);
}
}
然后编写我们的ItemHandler,要实现EntryHandler,重写其三个方法
@CanalTable("tb_item")
@Component
public class ItemHandler implements EntryHandler<Item> {
@Autowired
private RedisHandler redisHandler;
@Autowired
private Cache<Long, Item> itemCache;
@Override
public void insert(Item item) {
// 写数据到JVM进程缓存
itemCache.put(item.getId(),item);
// 写数据到redis
redisHandler.saveItem(item);
}
@Override
public void update(Item before, Item after) {
// 写数据到JVM进程缓存
itemCache.put(after.getId(), after);
// 写数据到redis
redisHandler.saveItem(after);
}
@Override
public void delete(Item item) {
// 删除JVM进程缓存数据
itemCache.invalidate(item.getId());
// 删除redis数据
redisHandler.deleteItemById(item.getId());
}
}
数据库连接遇到问题
最后重启项目测试,但是重启的时候遇到一个小问题,关于数据库的,报错内容:“Public Key Retrieval is not allowed”。
还是因为mysql8.0以后的密码验证方式发生变化了。
mysql 8.0 默认使用 caching_sha2_password 身份验证机制 (即从原来mysql_native_password 更改为 caching_sha2_password。)
从 5.7 升级 8.0 版本的不会改变现有用户的身份验证方法,但新用户会默认使用新的 caching_sha2_password 。 客户端不支持新的加密方式。 修改用户的密码和加密方式。
方式一(在这里不建议):
在命令行模式下进入mysql,输入以下命令:
ALTER USER ‘root’@‘localhost’ IDENTIFIED WITH mysql_native_password BY ‘root’;
或者
ALTER USER ‘root’@‘%’ IDENTIFIED WITH mysql_native_password BY ‘root’;
然后就可以正常连接了。
方式二:
可以去直接修改密码验证方式,但这样canal就连不上了。
所以我们采用直接在配置数据源的时候直接将属性allowPublicKeyRetrieval设置为true即可