MySql-5 缓存

MySql 缓存

本章主要介绍缓存的一些场景,如何解决MySql缓存双写一致性的问题。

1. 常见的缓存问题

1.1 缓存穿透

  • 什么是缓存穿透?

    一般的缓存系统都是按照key-value的形式去缓存查询的,如果不存在对应的value,就会去对应的系统查(比如MySql,Oracle,ES)。如果key对应的value是一定不存在的,并且对该key的请求量非常大,就会对后端系统造成很大的压力。也就是说,对不存在的key的value,进行高并发访问,导致数据库的压力瞬间增大,这种场景叫做缓存穿透

  • 如何避免,解决?

  1. 对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Map中,查询时通过该Map过滤(该方法不通用)。
// 伪代码
  final Map<String, String> keys = new HashMap<>();
  public boolean existKey(String key) { 
  		// true,进行查询流程
        if(keys.containsKey(key)) {
            return true;
        } else {
        	// 不存在对应key的value,直接返回空数据给客户端
            return false;
        }
    }
  1. 对查询结果为空的情况也进行缓存,对空的数据进行判断,缓存时间设置短一点;对该key对应的数据insert了之后清理缓存,防止存在key对应的value,查询结果为空(此方法通用)。

1.2 缓存雪崩

  • 缓存雪崩
    当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
  • 如何解决
  • 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
  • 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
    **推荐:**方式一与方式二结合使用。

1.3 缓存击穿

  • 什么是缓存击穿?
    对于一些设置了过期时间的key,如果这些key可能会在某个时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

  • 如何解决?
    使用锁,建议redis分布式锁,兼容程序在后续的分布式部署

// 使用redis的setnx互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数据库。
// 伪代码
if(redis.sexnx()==1) {
	// 1. 先查缓存
	Object obj = getCashByKey(key);
	if(obj == null) {
		// 2. 缓存无数据,查询数据
		obj = selectDb(key);
		// 3. 加入缓存
		setCash(key, obj);
	}
	return obj;
	
}

1.3 缓存双写一致性

一般来说,在读取缓存方面,我们都是先读取缓存,再读取数据库的。但是,在更新缓存方面,我们是需要先更新缓存,再更新数据库?还是先更新数据库,再更新缓存?

1.3.1 先更新数据量再更新缓存(不建议使用)
  • 场景:线程A和线程B同时对同一数据进行操作
 - 线程A修改了数据量
 - 线程B修改了数据库
 - 线程B更新的缓存
 - 线程A更新了缓存
  • 现象
    数据库的数据的B线程,缓存中的数据是A线程的。

  • 问题

  1. 脏读。
  2. 浪费性能。
1.3.2 先删除缓存再更新数据库(不推荐)
  • 场景:线程A更新数据,线程B读取数据
- 线程A删除缓存,更新数据,此刻未提交事务(即未写操作)
- 线程B读取数据,缓存无数据,将数据写入缓存
- 线程A提交事务(写入数据库)
  • 现象
    数据库是新数据,线程B读取旧数据,缓存中是旧数据

  • 问题

  1. 脏读。
1.3.3 先更新数据库再删除缓存(推荐)
  • 场景:线程A更新,线程B读,线程C读。
- 线程A进行更新操作,此时线程A还未提交事务(即未写操作)
- 线程B进行读操作,此时缓存无数据
- 线程B去数据库查询,得到旧值,并将旧值写入缓存
- 线程A提交事务,新数据写入数据库,删除缓存
- 线程C读操作,此时缓存无数据,查询数据库得到新值,并将其写入缓存
  • 现象
    数据库是新的数据,线程B是旧的数据,线程C是新的数据,缓存中是新的数据。无论几个线程对同一数据读写操作,最终缓存的数据要么是空的,要么是新数据。
// 伪代码
try {
   updateDate(data);
} catch(Exception e) {
	// 回滚事务
	db.rollback();
} finally {
	// 无论更新成功或者是失败,都删除缓存,等待下一次查询在存储
	delCash();
}

2 结论

  1. 深刻认识缓存穿透,缓存雪崩,缓存击穿,三者的区别、场景。
    缓存穿透:对不存在key的value,在某一时间点被高并发访问,数据库压力瞬间增大;不存在指的是value为空,即数据库查询出来的结果也是空。
    缓存雪崩: 大量缓存集中在某一个时间段失效,这样在失效的时候,对这些失效的key高并发访问会给后端系统(比如DB)带来很大压力。
    缓存击穿:与缓存雪崩的区别在于,前者针对的是一批key,后者仅针对一个key。

  2. 缓存双写一致性解决方案:先更新数据库,finally删除缓存,等待下次查询重新写入缓存。

  3. 现象:目前的大部分企业不再使用MySql内置的缓存,更多的偏向于使用缓存数据库,例如redis。

有误区,欢迎大牛指正交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值