Redis和MySQL数据一致性的实现:缓存主动更新

采用缓存主动更新来解决数据一致性问题,同时也选择使用双写方案的删除缓存模式(增加TTL超时剔除兜底),同时将缓存和数据库的操作放到同一个事务来保障操作的原子性。

需求

  1. 根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间。
  2. 根据id修改店铺时,先修改数据库,再删除缓存

实现

1. 修改店铺时,先修改数据库,再删除缓存(是用来事务注解,确保原子性)

/*
    主要功能是更新店铺信息,并确保数据库与缓存中的数据一致。
    1. 方法签名与注解:
    @Override:表明该方法重写了父类或接口中的方法。
    @Transactional:这是Spring框架提供的声明式事务管理注解,
    表示该方法中的所有数据库操作要么全部成功提交,要么全部失败回滚,以确保原子性
     */
@Override
@Transactional
public Result update(Shop shop) {
	//  首先先判一下空
	/*
        2. 参数检查:
        在执行更新操作前,首先检查传入的shop对象的id是否为空。
        如果为空,说明无法确定要更新的店铺,方法返回一个失败的结果,提示“店铺id不能为空!!”。
         */
	if (shop.getId() == null) {
		return Result.fail("店铺id不能为空!!");
	}

	/*
        3. 先修改数据库
        调用该方法根据shop对象的id在数据库中找到对应的记录,并更新其信息。
         */
	updateById(shop);

	/*
        4. 再删除缓存:
        使用StringRedisTemplate对象删除Redis中对应店铺的缓存数据。
        这里缓存的键是通过常量CACHE_SHOP_KEY与店铺id拼接而成的字符串。
        删除缓存的目的是在下次获取店铺信息时,避免使用过期的数据,从而确保数据的实时性和一致性。
         */
	stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());
	// 5. 如果上述操作全部成功,返回一个表示成功的结果。
	return Result.ok();
}

2. 查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间。

// 解决缓存穿透的代码
/*
querywithchuantou 方法旨在解决缓存穿透问题。缓存穿透是指对一个在缓存和数据库中都不存在的数据进行频繁查询,
导致每次请求都直接访问数据库,增加了数据库的压力。
*/
public Shop querywithchuantou(Long id) {
	/*
        1. 从 Redis 查询缓存:
        首先,通过 stringRedisTemplate 从 Redis 中获取键为 CACHE_SHOP_KEY + id 的缓存数据。
        这里,CACHE_SHOP_KEY 是缓存键的前缀,id 是店铺的唯一标识符。
         */
	String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
	/*
        2. 判断缓存中是否存在数据:
        如果 shopJson 不为空且内容有效(即 shopJson 不是空字符串),则将其反序列化为 Shop 对象并返回,避免了对数据库的访问。
         */
	if (StrUtil.isNotBlank(shopJson)) {
		Shop shop = JSONUtil.toBean(shopJson, Shop.class);
		return shop;
	}

	/*
        3. 处理缓存中存在空字符串的情况:
        如果 shopJson 为 null,说明缓存中没有该键;
        但如果 shopJson 不为 null(即存在该键),但其值为空字符串,
        说明之前已经缓存了该店铺不存在的信息,因此直接返回 null,避免再次访问数据库。
         */
	if (shopJson != null) {
		return null;
	}

	/*
        4. 查询数据库:
        如果缓存中没有相关数据(即 shopJson 为 null),则查询数据库获取店铺信息。
         */
	Shop shop = getById(id);

	/*
        5. 处理数据库中不存在的情况:
        如果数据库中也没有该店铺的信息,则将空字符串存入 Redis,并设置一个较短的过期时间(CACHE_NULL_TTL,例如 2 分钟)。
        这样可以在短时间内避免对同一不存在的数据频繁查询数据库,减轻数据库压力。
         */
	if (shop == null) {
		// 这里的常量值是2分钟
		stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, null, CACHE_NULL_TTL, TimeUnit.MINUTES);
		return null;
	}

	/*
        6. 缓存数据库中存在的数据:
        如果数据库中存在该店铺的信息,则将其序列化为 JSON 字符串,存入 Redis,
        并设置一个较长的过期时间(CACHE_SHOP_TTL,例如 30 分钟)。这样可以在后续相同的查询中直接从缓存获取数据,提高查询效率。
         */
	String jsonStr = JSONUtil.toJsonStr(shop);
	// 并存入redis,并设置TTL,防止存了错的缓存
	stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonStr, CACHE_SHOP_TTL, TimeUnit.MINUTES);
	// 7. 最终把查询到的商户信息返回给前端
	return shop;
}
posted @ 2025-04-08 21:15  kuki'  阅读(91)  评论(0)    收藏  举报