一、常见的缓存更新策略以及存在问题
- 先更新数据库然后更新缓存,就像下面的代码:
public Product update(@Valid @NotNull(message = "商品更新对象不能为null") Product product){
Product productResult = productDao.update(product);
redisUtil.set(getCacheKey(productResult.getId()),productToJsonString(productResult),getCacheKeyTimeout(), TimeUnit.HOURS);
return productResult;
}
上面更新逻辑正常的执行顺序如下:
但是没有在这里做任何的同步也可能会出现下面的执行顺序:
如何解决上面的问题呢?要解决上面的问题首先需要分析问题出现的根本原因是操作数据库和操作redis这两个操作不是原子的,假如在服务是单节点的情况下可与直接通过java的内置锁或者基于AQS实现的锁保证只有一个线程可以进入可以接受上述问题。假如是在分布式的环境下呢,就需要加上分布式锁,实现的逻辑如下:
public Product update(@Valid @NotNull(message = "商品更新对象不能为null") Product product){
Product productResult = null;
RLock productUpdateLock = redisson.getLock(RedisKeyPrefixConst.PRODUCT_CACHE_UPDATE_PREFIX + product.getId());
productUpdateLock.lock();
try {
productResult = productDao.update(product);
redisUtil.set(getCacheKey(productResult.getId()),productToJsonString(productResult),getCacheKeyTimeout(), TimeUnit.HOURS);
}catch (Exception e){
//处理业务异常
}finally {
productUpdateLock.unlock();
}
return productResult;
}
通过上面的这种加锁逻辑保证每次只能有一个线程执行操作数据然后操作缓存就不会出现缓存数据不一致的情况。
2. 先更新数据库然后删除缓存,具体的实现逻辑如下:
public Product update(@Valid @NotNull(message = "商品更新对象不能为null") Product product){
Product productResult = productDao.update(product);
redisUtil.delete(getCacheKey(productResult.getId()));
return productResult;
}
正常的执行流程
异常的执行流程
虽然更新数据库然后删除缓存这种方式是使用的最多但是依然存在数据库和缓存不一致的情况,出现这种情况的原因是查询数据和更新数没有做同步在这里也是一样加上一把分布式锁就行:
public Product update(@Valid @NotNull(message = "商品更新对象不能为null") Product product){