public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedissonLock() {
//1、占分布式锁。去redis占坑
//(锁的粒度,越细越快:具体缓存的是某个数据,11号商品) product-11-lock
//RLock catalogJsonLock = redissonClient.getLock("catalogJson-lock");
//创建读锁
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("catalogJson-lock");
RLock rLock = readWriteLock.readLock();
Map<String, List<Catelog2Vo>> dataFromDb = null;
try {
rLock.lock();
//加锁成功...执行业务
dataFromDb = getDataFromDb();
} finally {
rLock.unlock();
}
//先去redis查询下保证当前的锁是自己的
//获取值对比,对比成功删除=原子性 lua脚本解锁
// String lockValue = stringRedisTemplate.opsForValue().get("lock");
// if (uuid.equals(lockValue)) {
// //删除我自己的锁
// stringRedisTemplate.delete("lock");
// }
return dataFromDb;
}
接下来又到了一个关键环节:如何解决缓存数据一致性?
有两种方案:
- 双写模式
- 失效模式
来画图分析下双写模式的工作流程:
再来画图分析下失效模式的工作流程:
其实这两种方案都会导致数据不一致性的问题;比如在双写模式下,两个写的请求先后打过来,处理后,在写缓存是由于网络延迟等原因导致后写的请求先写缓存,先写的请求后写入缓存,这就导致了数据不一致性,缓存中的数据不是最新的数据;再比如在失效模式下,看图可知道,当我在第二个写请求还没完成时,我去读缓存,没有读到,然后去数据库中查,当我读到之后假设第二请求还没完成,当第二个请求完成之后,删掉缓存,我再更新到缓存中,也会导致数据不一致性的问题。
针对上面的问题,我们怎么解决呢?
解决方案:
- 如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小的,就不用考虑数据不一致的问题,缓存数据加上过期时间,每隔一段时间触发读主动更新即可
- 如果是菜单、商品介绍等基础数据,也可以采用canal订阅binlog的方式,数据库中信息改变,canal采集这些信息,再做些处理然后同步到redis当中即可
- 缓存数据+过期时间足够解决大部分业务对于缓存的要求
- 如果写入操作稍多的话,我们可以通过加锁的方式去保证并发读写,写写的时候排好队,保证顺序,读的时候不加锁,所以适用读写锁(业务不关心脏数据,允许临时脏数据可忽略)
总结
对于我们能够放入缓存的数据就不应该是实时性、数据一致性要求高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新的数据即可。我们不应该过度的设计,增加系统的复杂性,遇到那些实时性、一致性要求高的数据,就应该去查询数据库,慢点就慢点。