赞
踩
使用redis缓存的初衷:缓解数据库压力,提高性能。
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
超卖问题:多线程存在安全问题
如下图所示
当线程1查询完库存大于0 还未扣减库存的时候,线程2也查询库存大于0 然后一起扣减 就会出现库存为负数的情况。
超卖问题是典型的多线程安全问题,针对这一问题的常见解决方案就是加锁:
悲观锁:添加同步锁,让线程串行执行
优点:简单粗暴
缺点:性能一般
乐观锁:不加锁,在更新时判断是否有其它线程在修改
优点:性能好
缺点:存在成功率低的问题
需求:修改秒杀业务,要求同一个优惠券,一个用户只能下一单
流程图变更如下
可通过加锁解决(单机情况):
同一个用户一把锁 ,注意:userId.toString() 每次都会new出来,需要使用userId.toString().intern(),才能锁住。
在大多数情况下 都是集群部署 通过负载均衡
通过加锁可以解决在单机情况下的一人一单安全问题,但是在集群模式下就不行了。
每个服务都会有单独的JVM,JVM中的锁监视器只能锁本JVM。导致两个服务没有被同一把锁锁住。
解决方法
分布式锁(经典加一层)
分布式锁的核心是实现多进程之间互斥,而满足这一点的方式有很多,常见的有三种:
这里将使用Redis进行分布式锁
Redis中SetNX命令 SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
故可以利用它来实现锁的效果。
当线程1获取到锁之后,业务阻塞,造成锁的超时释放,此时线程2进来便可以拿到锁,然后线程1继续执行,就可以把线程2的锁删除掉。
原来业务流程:
解决方法:在删除锁之前先看看锁是不是自己的。
改进Redis的分布式锁
但是上面误删问题仍可能在极端情况下发生;
例如:让线程1验证完是自己的锁的时候,被阻塞(垃圾回收机制),此时超时释放锁,然后线程2便进入了锁,线程1唤醒后,执行删除锁的业务,便也造成误删问题。
解决方法:只需要保证 验证锁是否是自己的和删除锁是一个原子性的操作即可。
这里我们将使用Lua脚本。
Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言,它的基本语法大家可以参考网站:https://www.runoob.com/lua/lua-tutorial.html
基于Redis的分布式锁实现思路:
利用set nx ex获取锁,并设置过期时间,保存线程标示
释放锁时先判断线程标示是否与自己一致,一致则删除锁
特性:
利用set nx满足互斥性
利用set ex保证故障时锁依然能释放,避免死锁,提高安全性
利用Redis集群保证高可用和高并发特性
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。