1. 引言
缓存有啥用?
- 降低对数据库的请求,减轻服务器压力
- 提高了读写效率
缓存有啥缺点?
- 如何保证数据库与缓存的数据一致性问题?
- 维护缓存代码
- 搭建缓存一般是以集群的形式进行搭建,需要运维的成本
2. 将信息添加到缓存的业务流程
上图可以清晰的了解Redis在项目中所处的位置,是数据库与客户端之间的一个中间件,也是数据库的保护伞。有了Redis可以帮助数据库进行请求的阻挡,阻止请求直接打入数据库,提高响应速率,极大的提升了系统的稳定性。
3. 实现代码
下面将根据查询商铺信息来作为背景进行代码书写,具体的流程图如上所示。
3.1 代码实现(信息添加到缓存中)
public static final String SHOPCACHEPREFIX = "cache:shop:";
@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON工具
ObjectMapper objectMapper = new ObjectMapper();
@Override
public Result queryById(Long id) {
//从Redis查询商铺缓存
String cacheShop = stringRedisTemplate.opsForValue().get(SHOPCACHEPREFIX + id);
//判断缓存中数据是否存在
if (!StringUtil.isNullOrEmpty(cacheShop)) {
//缓存中存在则直接返回
try {
// 将子字符串转换为对象
Shop shop = objectMapper.readValue(cacheShop, Shop.class);
return Result.ok(shop);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
//缓存中不存在,则从数据库里进行数据查询
Shop shop = getById(id);
//数据库里不存在,返回404
if (null==shop){
return Result.fail("信息不存在");
}
//数据库里存在,则将信息写入Redis
try {
String shopJSon = objectMapper.writeValueAsString(shop);
stringRedisTemplate.opsForValue().set(SHOPCACHEPREFIX+id,shopJSon,30,TimeUnit.MINUTES);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
//返回
return Result.ok(shop);
}
3.2 缓存更新策略
数据库与缓存数据一致性问题,当数据库信息修改后,缓存的信息应该如何处理?
内存淘汰 | 超时剔除 | 主动更新 | |
---|---|---|---|
说明 | 不需要自己进行维护,利用Redis的淘汰机制进行数据淘汰 | 给缓存数据添加TTL | 编写业务逻辑,在修改数据库的同时更新缓存 |
一致性 | 差劲 | 一般 | 好 |
维护成本 | 无 | 低 | 高 |
这里其实是需要根据业务场景来进行选择
- 高一致性:选主动更新
- 低一致性:内存淘汰和超时剔除
3.3 实现主动更新
此时需要实现数据库与缓存一致性问题,在这个问题之中还有多个问题值得深思
-
删除缓存还是更新缓存?
当数据库发生变化时,我们如何处理缓存中无效的数据,是删除它还是更新它?
更新缓存:每次更新数据库都更新缓存,无效写操作较多
删除缓存:更新数据库时删除缓存,查询时再添加缓存
由此可见,选择删除缓存是高效的。 -
如何保证缓存与数据库的操作的同时成功或失败?
单体架构:单体架构中采用事务解决
分布式架构:利用分布式方案进行解决 -
先删除缓存还是先操作数据库?
在并发情况下,上述情况是极大可能会发生的,这样子会导致缓存与数据库数据库不一致。
先操作数据库,在操作缓存这种情况,在缓存数据TTL刚好过期时,出现一个A线程查询缓存,由于缓存中没有数据,则向数据库中查询,在这期间内有另一个B线程进行数据库更新操作和删除缓存操作,当B的操作在A的两个操作间完成时,也会导致数据库与缓存数据不一致问题。
完蛋!!!两种方案