文章目录
- 缓存问题
- 缓存穿透
- 缓存击穿
- 缓存雪崩
- 缓存与数据库一致性
缓存问题
缓存穿透
如果要获取的数据在缓存和数据库中都不存在,就可能导致每次获取该数据都需要查询数据库,造成不必要的开销。
解决方案一:缓存空值
如果数据库中没有的数据也为其缓存一个空值,那么就可以避免缓存穿透问题。
public void getMessage(Long id) {
// 查询缓存中是否有key,无论value是否为null都返回结果。
Message message = null;
if (jedis.exists(id.toString())) {
message = JSONObject.parseObject(jedis.get(id.toString()), Message.class);
System.out.println("缓存数据:" + message);
return;
}
// 如果缓存中没有key,说明数据未被缓存,开始查询数据库。
try (SqlSession session = sqlSessionFactory.openSession();) {
MessageService messageService = session.getMapper(MessageService.class);
message = messageService.getMessageById(id);
jedis.set(id.toString(), JSONObject.toJSONString(message));
System.out.println("数据库数据" + message);
}
}
缓存击穿
如果某数据还未被缓存,并有多个线程同时获取该数据,这些线程就可能同时开始查询数据库,造成额外开销。
解决方案
通过互斥锁等串行化机制,使得同时只能有一个线程进行数据库的访问。
public void getMessage(Long id) {
// 尝试在缓存中获取数据
Message message = null;
if (jedis.exists(id.toString())) {
message = JSONObject.parseObject(jedis.get(id.toString()), Message.class);
return;
}
// 如果缓存中没有,等待持有锁
synchronized (this) {
// 再次尝试在缓存中获取数据
if (jedis.exists(id.toString())) {
message = JSONObject.parseObject(jedis.get(id.toString()), Message.class);
return;
}
// 开始查询数据库
try (SqlSession session = sqlSessionFactory.openSession();) {
MessageService messageService = session.getMapper(MessageService.class);
message = messageService.getMessageById(id);
jedis.set(id.toString(), JSONObject.toJSONString(message));
}
}
}
缓存雪崩
多个缓存数据在同一时间段内由于服务器故障等原因同时失效,称作缓存雪崩。解决方法:通过集群提高服务的可用性,避免单机故障引起的服务不可用。
解决方案
- 主从复制
- 哨兵模式
- Cluster模式
缓存与数据库一致性
数据不一致问题出现的场景:
- 当数据需要更新,无论先更新数据库或是缓存,如果中途发生意外,都有可能导致数据不一致。
- 两个线程同时进行数据更新,由于错误的执行时序,数据库数据与缓存数据出现不一致问题。
解决方案一:延时双删
先删除缓存数据,再更新数据库,为了避免缓存在操作期间被更新,等待一段时间后再次删除缓存。这样便可以很大程度解决一致性问题,但仍有出现问题的可能性。
public void updateMessage(Message message) throws InterruptedException {
// 删除缓存数据
jedis.del(message.getId().toString());
// 更新数据库
try (SqlSession session = sqlSessionFactory.openSession();) {
MessageService messageService = session.getMapper(MessageService.class);
messageService.updateMessage(message);
session.commit();
}
// 延时再次删除缓存
Thread.sleep(200);
jedis.del(message.getId().toString());
}
解决方案二:串行化
使查询操作与更新操作以串行的方式执行,避免更新数据期间缓存被写入,从而解决一致性问题。
public synchronized void getMessage() {
// do something
}
public synchronized void updateMessage(Message message) throws InterruptedException {
// 删除缓存数据
jedis.del(message.getId().toString());
// 更新数据库
try (SqlSession session = sqlSessionFactory.openSession();) {
MessageService messageService = session.getMapper(MessageService.class);
messageService.updateMessage(message);
session.commit();
}
}