一台 mysql数据库 一秒 最多承受4000并发 上万请求 造成的 数据库崩溃
redis
穿透 多个请求查询一个(不存在)值 先查 数据库 再查 redis 都不存在
击穿 查一个值 redis缓存过期了 没有了 但是 去查数据库有
雪崩 redis大量缓存数据过期 大量请求去操作数据库 造成数据库压力 严重时造成崩溃
1解决 穿透
大量数据请求进入 redis 查询一个null值
2解决 击穿
分布式锁setnx 进入redis缓存 里面查 没有 需要 到 数据库里面查询key 查询到了直接存入 redis缓存
3解决 雪崩
大量缓存过期
(1)穿透 分布式锁 setnx命令 解决
多个请求查询一个(不存在)key
redis单线程 原子性
setkey setvalue
(每次setnx只能设置一个key) value=空字符串 设置成功为true 不成功为false
其他请求 为false 等待时
进入 自旋状态 不断请求setnx
直到 请求到 key值
(2)击穿 分布式锁 setnx命令 解决
1.setnx
2获取到锁 查询库 放到redis 释放锁
3拿不到 锁 进行自旋 查询redis发现存在这个值 释放锁
(自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
获取锁的线程一直处于活跃状态,
不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。) 自旋是 自旋锁的一种状态
库存(卖超) 问题 1000人 买10个库存
{监听 加 事务解决 }
多个请求 进入redis 查询 key 假设为a value 10个商品
此时的线程 监听的 都是 10 10 10 10
线程 A 进行 -1
线程 B 发现 不是我监听的那个 值了 重新 自旋setnx 获取 value
线程 C 监听的值可能还是 10
监听 完了 每次判断是否为0 为0 则售空
不是0 继续监听 发现 不是我监听的那个 值了 重新事务提交 自旋setnx 获取 value
CAS算法
自己总结
缓存 穿透
原因
是指查询一个数据库一定不存在的数据 redis也不存在
问题
查缓存 这个 key 为空 查数据库 也为空 每次都null 就不会 存入缓存 大量请求的查询 就会造成 数据库 崩溃
如果传入的参数为-1,会是怎么样?这个-1,就是一定不存在的对象。就会每次都去查询数据库,而每次查询都是空,每次又都不会进行缓存。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。即便是采用UUID,也是很容易找到一个不存在的KEY,进行攻击。
解决
查完数据库为空 查完缓存为空 这时可以给redis缓存一个 空值 设置过期时间为60秒解决问题
缓存 击穿
原因
是指一个缓存key非常热点,在不停的扛着大并发,大并发集中对这一个缓存点进行访问,
当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞
例子 {
通常使用 缓存 + 过期时间的策略来帮助我们加速接口的访问速度,减少了后端负载,同时保证功能的更新,一般情况下这种模式已经基本满足要求了。
但是有两个问题如果同时出现,可能就会对系统造成致命的危害:
(1) 这个key是一个热点key(例如一个重要的新闻,一个热门的八卦新闻等等),所以这种key访问量可能非常大。
(2) 缓存的构建是需要一定时间的。(可能是一个复杂计算,例如复杂的sql、多次IO、多个依赖(各种接口)等等)
于是就会出现一个致命问题:在缓存失效的瞬间,有大量线程来构建缓存(见下图),造成后端负载加大,甚至可能会让系统崩溃
}
问题
大量请求查询 一个缓存的key key缓存过期失效 直接击穿缓存 并发查询数据库
解决
做电商项目的时候 称它 为爆款
让缓存永不过期。即便某些商品自己发酵成了爆款,也是直接设为永不过期就好了。
缓存 雪崩
原因
缓存雪崩,是指在某一个时间段,缓存集中过期失效。
问题
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰
解决
一般是采取不同分类商品,缓存不同周期。在同一分类中的商品,加上一个随机因子。这样能尽可能分散缓存过期时间,而且,热门类目的商品缓存时间长一些,冷门类目的商品缓存时间短一些,也能节省缓存服务的资源
严重性
集中过期,倒不是非常致命,比较致命的缓存雪崩,是 服务器某个节点宕机或断网。
因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,那么那个时候数据库能顶住压力
,这个时候,数据库也是可以顶住压力的。
无非就是对数据库产生周期性的压力而已。
而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮
库存问题
这个肯定不能直接操作数据库的,会挂的。直接读库写库对数据库压力太大,要用缓存。
把你要卖出的商品比如10个商品放到缓存中;
然后在memcache里设置一个计数器来记录请求数,这个请求书你可以以你要秒杀卖出的商品数为基数,比如你想卖出10个商品,只允许100个请求进来。那当计数器达到100的时候,后面进来的就显示秒杀结束,这样可以减轻你的服务器的压力。然后根据这100个请求,先付款的先得后付款的提示商品以秒杀完。
2、首先,多用户并发修改同一条记录时,肯定是后提交的用户将覆盖掉前者提交的结果了。
这个直接可以使用加锁机制去解决,乐观锁或者悲观锁。
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
两种锁各有优缺点,不能单纯的定义哪个好于哪个。乐观锁比较适合数据修改比较少,读取比较频繁的场景,即使出现了少量的冲突,这样也省去了大量的锁的开销,故而提高了系统的吞吐量。但是如果经常发生冲突(写数据比较多的情况下),上层应用不不断的retry,这样反而降低了性能,对于这种情况使用悲观锁就更合适。
3、不建议在数据库层面加锁,建议通过服务端的内存锁(锁主键)。
当某个用户要修改某个id的数据时,把要修改的id存入memcache,若其他用户触发修改此id的数据时,读到memcache有这个id的值时,就阻止那个用户修改。