既然,面试常问此类问题。我们就认真的分析一下。
缓存穿透(没有key):
缓存击穿(一个key失效):
缓存雪崩(多个key失效):
单机部署的项目,简单的加jvm锁(也就是传说中的(synchronized)就行了。但是目前是分布式项目,加锁只能保证单机服务问题,就无法保证多机的数据安全问题。我们就需要一个 分布式锁
我们采用redis 来做分布式锁,当然分布式锁有很多方式,redis比较方便一点。
调用的是Redis的 API setnx key value 也就是K 不在 在设置V,K在,不设置V
setnx key value
# 当key 不存在的时候 才会set 值
在 stringRedisTemplate 中 调用的是.opsForValue().setIfAbsent("key",value); 返回一个布尔值
如果单纯这样涉及,就会有所谓的死锁情况,比如项目加锁后,停电,嘎,没有释放锁,锁的key永久都在,所以务必加上key的失效时间
在刁钻一点
加上失效时间 如果写法不对也不能保证100%的没问题
流程图图下
如果 在没有删除 标记之前,服务器挂了,重启后,所有的 请求都去设置标记,但每次都是获得,已经存在标记了,就会带来新的问题。
我们需要对一个key 设置一下 超时时间
// 格式: stringRedisTemplate.expire(key,value,time,时间单位)
// 设置 10 秒 失效时间
stringRedisTemplate.expire("flag",10,TimeUtils.Second);
上面的也有问题,如果 添加key 之后 在设置失效时间,就会带来新的问题,添加的时候,没设置失效之前挂了。我们就需要在设置key的时候,同时 指定时间
超高并发依旧有问题!!!!
超高并发依旧有问题!!!!
超高并发依旧有问题!!!!
// 添加标记10秒失效
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("key",value,10, TimeUtils.Second);
if(!result){
return "当前有其他线程正在操作";
}else{
//TODO 正常的减少库存
}finally{
// 删除key标记
stringRedisTemplate.delete("key")
}
我们设置标记的value 为我们当前操作的 UUID,我们删除的时候,都要去读取匹配 我们的UUID ,匹配成功,我们才取删除!
开辟新线程,去检验 主线程 是否持有锁,如果有所,重新设置 锁时间,手写太要命,用Redis 官方推荐的 Redisson
他是对 jedis 增强,虽然没有直接关系!!! redis son 即 redis的儿子 ,被官方承认的!
Redisson 里的lock 机制,是开启定时器 (根据存储键的时间/3),重新设置 redis 中键的失效时间,并嵌套调用
Maven依赖
<!-- Redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.0</version>
</dependency>
注入一个 Bean
@Bean
public Redisson redisson() {
Config config = new Config();
// Redis是单机服务
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
return (Redisson) Redisson.create(config);
}
快速使用 伪代码
@Autowired
Redisson redisson;
String lockKey = "自定义一个key(一般是用户Id锁自己的操作)";
RLock redissonLock = redisson.getLock(lockKey);
try{
redissonLock.lock();
//TODO 执行操作
}finally{
redissonLock.unlock();
}
完成!!!
更详细的代码
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.TimeUnit;
@Configuration
public class RedissLockUtil {
/* 尝试获取锁
*
* @param lockKey 锁名
* @param unit 时间单位
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
RedissonClient redissonClient = Redisson.create(config);
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
/*
* 释放锁
*
* @param lockKey 锁名
*/
public static void unlock(String lockKey) {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
RedissonClient redissonClient = Redisson.create(config);
RLock lock = redissonClient.getLock(lockKey);
// 在解锁之前先判断要解锁的key是否已被锁定并且是否被当前线程保持。 如果满足条件时才解锁
if (lock.isLocked()) {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 常规的业务逻辑代码
@Transactional
public String RedissonForDemo() throws Exception {
boolean flag = false;
// 定义一个锁
String LockName = "所有线程拿这个锁(比如商品数量)";
// 通过flag判断加锁是否成功
try {
flag = RedissLockUtil.tryLock(LockName, TimeUnit.SECONDS, 3, 10);
if (flag) {
//TODO 执行自己的业务逻辑 先查询订单数量,再下单。
return "下单成功!";
} else {
return "没有加锁成功,下单失败!";
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 一旦加锁成功,方法结束必须要释放锁
if (flag) {
RedissLockUtil.unlock(LockName);
}
}
return "方法结束";
}
}
Redlock
特殊说明: 解决问题的光鲜,藏着磕Bug的痛苦。 万物皆入轮回,谁也躲不掉! 以上文章,均是我实际操作,写出来的笔记资料,不会出现全文盗用别人文章!烦请各位,请勿直接盗用!