黑马店评-04缓存更新策略,保证MySQL数据库中的数据和Redis中缓存的数据一致性

缓存更新策略(数据一致)

更新策略

缓存更新是Redis为了节约内存而设计出来的机制,当我们向Redis插入太多数据时就会导致缓存中的数据过多,所以Redis会对部分数据进行更新即淘汰

  • 低一致性需求(数据长久不发生变化): 使用内存淘汰机制,例如店铺类型信息的查询缓存,因为这部分数据很长一段时间都不需要更新
  • 高一致性需求: 使用主动更新机制,写入缓存的时候需要设置超时时间,例如店铺详情查询的缓存

主动更新实现

主动更新策略一般有如下几种方案

人工编码的三大问题

更新数据库后,删除缓存还是更新缓存?

  • 更新缓存: 每次更新数据库都更新缓存,如果中间没有人执行查询操作(写多读少),那么这些对缓存的更新动做就是无效的(用户根本就不会去缓存中查询数据)
  • 删除缓存: 更新数据库时把缓存删掉,只有用户下次做查询操作时才会更新缓存(用户发现Redis中没有数据就会去查询数据库同时把查询到的数据写入缓存)

如何保证缓存与数据库的操作的同时成功或失败?

  • 在单体系统中将缓存与数据库操作放在一个事务
  • 在分布式系统中利用TCC等分布式事务方案

先操作缓存还是先操作数据库?

  • 虽然这二者都存在线程安全问题,但是由于删除缓存的操作很快,更新数据库的操作相对较慢, 所以先操作数据库再删除缓存出现线程安全问题的概率相对较低

实现商铺缓存与数据库一致

根据店铺Id查询店铺时如果缓存未命中则查询数据库,将查询到的店铺信息写入Redis的同时设置缓存的超时时间

java 复制代码
public static final Long CACHE_SHOP_TTL = 30L;
java 复制代码
@Override
public Result queryById(Long id) {
    // 先从Redis中查询对应的店铺缓存,这里的常量值是固定的前缀+店铺id
    String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
    // 如果在Redis中查询到了店铺信息(String类型的JSON字符串)则转为Shop类型直接返回
    if (StrUtil.isNotBlank(shopJson)) {
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);
        return Result.ok(shop);
    }
    // 在Redis中没查询到对应的店铺缓存信息则根据店铺Id去数据库中查询店铺信息
    Shop shop = getById(id);
    // 在数据库中也查不到就返回一个错误信息或者返回空(根据业务需求)
    if (shop == null){
        return Result.fail("店铺不存在!!");
    }
    // 在数据库中查到了店铺信息,则将shop对象转为json字符串存入redis同时设置缓存的超时时间(如30分钟)
    String jsonStr = JSONUtil.toJsonStr(shop);
    stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonStr,CACHE_SHOP_TTL, TimeUnit.MINUTES);
    // 最终把查询到的店铺信息返回给前端
    return Result.ok(shop);
}

根据店铺Id修改店铺时先修改数据库再删除缓存来解决双写问题,更新和删除通过事务去控制, 业务逻辑我们依旧是写在Service层的实现类中

java 复制代码
// 更新商铺信息
@PutMapping
public Result updateShop(@RequestBody Shop shop) {
    // 直接将请求体中JSON格式的店铺信息写入数据库,这样在缓存有效期内,即使数据库中的店铺信息修改了,下次获取的还是之前缓存中的店铺信息
    shopService.updateById(shop);
    return Result.ok();
}
java 复制代码
// 更新商铺信息
@PutMapping
public Result updateShop(@RequestBody Shop shop) {
    // 先将请求体中JSON格式的店铺信息写入数据库,再删除缓存,保证缓存中的店铺信息和数据库中的店铺信息一致性
    return shopService.update(shop);
}

public interface IShopService extends IService<Shop> {
    Result update(Shop shop);

}

@Override
@Transactional
public Result update(Shop shop) {
    // 首先先判一下店铺Id是否为空
    if (shop.getId() == null){
        return Result.fail("店铺id不能为空!!");
    }
    // 先修改数据库中对应的店铺信息
    updateById(shop);
    // 再将Redis中对应店铺的缓存删除
    stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());
    return Result.ok();
}

重启服务器进行测试,如访问餐厅时会将该餐厅的数据缓存到Redis中,然后使用POSTMAN携带JSON数据发送PUT请求http://localhost:8080/api/shop/更新餐厅信息

  • 店铺的信息修改后就不会再出现在Redis缓存中了, 只有我们再次访问时才会去数据库中查询店铺信息并写入缓存
JSON 复制代码
{
  "area": "大关",
  "openHours": "10:00-22:00",
  "sold": 4215,
  "address": "金华路锦昌文华苑29号",
  "comments": 3035,
  "avgPrice": 80,
  "score": 37,
  "name": "yunqing茶餐厅",
  "typeId": 1,
  "id": 1
}
相关推荐
爱怪笑的小杰杰19 小时前
浏览器端缓存地图请求:使用 IndexedDB + ajax-hook 提升地图加载速度
ajax·okhttp·缓存
TDengine (老段)19 小时前
TDengine 字符串函数 CHAR 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
qq74223498419 小时前
Python操作数据库之pyodbc
开发语言·数据库·python
姚远Oracle ACE20 小时前
Oracle 如何计算 AWR 报告中的 Sessions 数量
数据库·oracle
Dxy123931021620 小时前
MySQL的SUBSTRING函数详解与应用
数据库·mysql
码力引擎20 小时前
【零基础学MySQL】第十二章:DCL详解
数据库·mysql·1024程序员节
杨云龙UP21 小时前
【MySQL迁移】MySQL数据库迁移实战(利用mysqldump从Windows 5.7迁至Linux 8.0)
linux·运维·数据库·mysql·mssql
l1t21 小时前
利用DeepSeek辅助修改luadbi-duckdb读取DuckDB decimal数据类型
c语言·数据库·单元测试·lua·duckdb
睡前要喝豆奶粉21 小时前
在.NET Core Web Api中使用redis
redis·c#·.netcore
安当加密21 小时前
Nacos配置安全治理:把数据库密码从YAML里请出去
数据库·安全