作者:dfpkgih | 来源:互联网 | 2023-09-16 09:05
1、锁住对象,并且设置一个过期时间(业务逻辑操作时间一定小于超时时间)
原先能想到的就是这样的一个方案然后代码实现如下:
public static boolean lock(String key, Long expireTime) {
final long expires = Objects.nonNull(expireTime) ? expireTime : 10L;
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
byte[] locks = key.getBytes();
boolean acquire = connection.setNX(locks, key.getBytes());
// 如果设置过期时间为空就删除key
if (acquire && !connection.expire(locks, expires)) {
connection.del(locks);
acquire = false;
}
return acquire;
});
}
目前的案例里面分布式锁主要锁的是新增商品名称,业务就是新增商品而已
@PostMapping("addGoods")
@ApiOperation(value = "新增商品", notes = "新增商品")
public Result addGoods(@RequestBody Goods goods) {
final String key = "seckill-shopping:" + goods.getName();
boolean lock = RedisUtil.lock(key, 10L);
if (lock) {
log.info("n{} -->获取锁成功", Thread.currentThread().getName());
goods = this.goodsService.insert(goods);
RedisUtil.removelock(key);
return Result.success(goods);
}
log.info("n{} --> 获取锁失败", Thread.currentThread().getName());
return Result.failure("服务暂时无法加载。。。");
使用jmeter开启5个线程迭代两次,看代码其实结果应该都可以猜得出来每一轮迭代只会成功一条数据,其他的都会失败,看看结果确实是这样的:
我们设计的时候默认的过期时间设置为了10s,假如业务操作导致超过了过期时间,我们看看会有什么问题。假设第一个获取到锁的业务操作时间为29s,其他的都是正常执行的看一下结果,第一个请求获取的锁过期了但是业务还在执行中,其他请求可以获取到锁并且可以执行业务,这就出现了超过过期时间锁不住的问题。(但是如果业务逻辑百分之百不会超过过期时间那就没必要续期了)
如果不需要续期但是上面的都只是单次获取失败就失败了,如果有的业务应该是多少时间内尝试获取失败才算失败,所以加上了一个尝试获取时间(getTime单位:秒)
public static boolean lock(String key, Long getTime, Long expireTime) {
final long gets = Objects.nonNull(getTime) ? getTime : 10L;
LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(gets);
final long expires = Objects.nonNull(expireTime) ? expireTime : 10L;
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
byte[] locks = key.getBytes();
while (localDateTime.isAfter(LocalDateTime.now())) {
boolean acquire = connection.setNX(locks, key.getBytes());
// 如果设置过期时间失败就删除key
if (acquire && connection.expire(locks, expires)) {
return true;
} else if (acquire) {
connection.del(locks);
}
// 随机休眠几毫秒
int time = random.nextInt(10);
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return false;
});
}
2、续过期时间(续期)
续期很多都是使用了Redis的看门狗,那么这个看门狗要怎么使用呢?
首先引入Redisson依赖
org.redisson
redisson-spring-boot-starter
3.9.1
默认配置文件里面数据:
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password: 123456
并且使用了自动配置,看门狗就使用了默认的时间
@PostMapping("addGoods")
@ApiOperation(value = "新增商品", notes = "新增商品")
public Result addGoods(@RequestBody Goods goods) {
RLock rLock = RedisUtils.getRLock(goods.getName());
boolean islock = false;
try {
islock = rLock.tryLock(30, TimeUnit.SECONDS);
if (islock) {
log.info("n{} --> 获取锁成功", Thread.currentThread().getName());
goods = this.goodsService.insert(goods);
if (count == 0) {
++count;
try {
log.info("n{} --> 获取到锁的睡眠40s", Thread.currentThread().getName());
Thread.sleep(40000);
log.info("n{} --> 获取到锁的醒了", Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return Result.success(goods);
}
} catch (Exception e) {
log.info("n{} --> 获取锁失败:{}", Thread.currentThread().getName(), e);
} finally {
if (islock) {
rLock.unlock();
}
}
log.info("n{} --> 获取锁失败", Thread.currentThread().getName());
return Result.failure("服务暂时无法加载。。。");
}
对应有很多加锁的方法,只有tryLock方法才会续期的哦。
对应tryLock方法有:
rLock.tryLock(); 默认锁定30s,一次获取锁
rLock.tryLock(20, TimeUnit.SECONDS);默认锁定30s,20s内获取不到锁就返回失败
rLock.tryLock(20,60, TimeUnit.SECONDS); 默认锁定30s,20s内获取不到锁就返回失败,60s锁自动过期。
运行以后可以发现
分布式锁相关的问题还有很多,由于使用的是redis就要考虑一个问题了,就是分布式锁还要考虑redis部署的问题,要使用分布式锁的前提你要把相对应的问题考虑清楚,否则上线都是问题了。
以上的是小辉对分布式锁理解以及使用,如果代码要用到线上项目请自己测试后评估后使用,因为最近看到了使用开源代码引发的线上事故,如果不是很了解的东西尽量少用或者不用,除非它是一个成熟的东西
文章同时会更新到公众号,觉得对你有帮助或者有用的可以关注一下哦