黑马点评19——多级缓存-缓存同步


在多级缓存中的数据一致性问题,也就是缓存同步的问题

数据同步策略

在这里插入图片描述
在这里插入图片描述
要是使用消息队列的方式,我们还需要修改代码,至少需要发送一条通知吧。
在这里插入图片描述
canal可以监听数据库的变化,监听到数据库的变化,发送通知变更,侵入性更小。

安装Canal

在这里插入图片描述
canal地址: https://github.com/alibaba/canal

在这里插入图片描述
安装和配置参考:https://blog.youkuaiyun.com/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即可
在这里插入图片描述

### 关于黑马点评高级篇中的多级缓存 #### 进程内缓存实现 在Java应用中,特别是基于Tomcat的服务里,可以利用像Caffeine这样的库来构建高效的进程内缓存。通过这种方式可以在应用程序层面减少对外部存储系统的依赖,提高响应速度并降低延迟。例如,在导入商品的过程中可以通过配置Caffeine作为本地缓存层,用于临时保存最近使用的商品信息[^1]。 ```java // Caffeine Cache Example Cache<String, Object> cache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(5, TimeUnit.MINUTES) .build(); ``` #### Lua脚本支持 为了增强灵活性和性能优化,有时会在Nginx或OpenResty环境中嵌入Lua脚本来处理复杂的业务逻辑。这允许开发者编写自定义的HTTP处理器、过滤器以及更精细地控制请求流程。对于涉及多个条件判断或者复杂计算的任务来说非常有用。 ```lua -- Simple Lua Script to Handle Requests ngx.say("Hello from Lua!") local args = ngx.req.get_uri_args() for key,val in pairs(args) do ngx.say(key .. ": " .. val) end ``` #### 构建分布式多级缓存体系 考虑到单点故障的风险及扩展性的需求,通常会设计一个多层级别的缓存方案。这里不仅包含了前面提到的应用程序级别的内存缓存(Caffeine),还包括位于Web服务器前端的Nginx/Redis组合形成的边缘节点缓存,最后才是持久化的数据库层。这种结构有助于分担流量压力,并有效防止因大量并发访问而导致的后端资源耗尽问题[^3]。 #### 解决冷启动挑战 当服务刚上线时,由于没有任何预先加载的数据存在于Redis或其他形式的一级缓存之中,首次查询将会直接穿透到下游组件直至最终到达DB获取结果。为了避免这种情况造成瞬间高峰负载冲击后台系统,建议采取渐进式的预热机制——提前批量填充部分热门条目至各级别高速缓冲区;或者是设置合理的懒加载策略,让初次检索命中率逐步上升而不是骤然增加。 #### 应对缓存一致性难题 针对可能出现的不同步现象(比如先清除了缓存但还没完成对应的数据库更新),一种常见的做法是在执行任何可能导致状态变化的操作之前先锁定相关联的对象实例,确保整个事务期间内的独占使用权。另外也可以引入消息队列或者其他异步通知手段来进行跨服务间的协调沟通,从而保障数据源之间的一致性和准确性[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值