20240721
- 一、分布式锁
- 1. 什么是分布式锁
- 2. 分布式锁的实现
- 3. 基于redis的分布式锁
- 4 总结
- 二、对于lua脚本可以保证事务,要么成功要么失败。
- 1. 在redis中调用lua脚本
- 三、Redisson
- 1 步骤
- 2. Redisson的总结
- 3. 几种分布式锁的区别
- 三、优化我们的秒杀
- 1. 我们在创建优惠券的时候,就把他存入redis中,然后对于判断库存和id是否重复下单的操作,我们就在redis中操作
- 2. 编写lua脚本
- 3. 在代码里面运行lua脚本
(来源于黑马—太困了,有一些忘了,每天补)
一、分布式锁
1. 什么是分布式锁
因为我们会有多个线程,比如我们的后端服务器有多个,通过nginx进行负载均衡,就会导致我们锁失效,多个服务器都能访问到。
2. 分布式锁的实现
3. 基于redis的分布式锁
4 总结
二、对于lua脚本可以保证事务,要么成功要么失败。
1. 在redis中调用lua脚本
三、Redisson
1 步骤
也可以在配置文件中配置,但可能会导致和redis的配置冲突,所以写成配置类。
2. Redisson的总结
3. 几种分布式锁的区别
三、优化我们的秒杀
1. 我们在创建优惠券的时候,就把他存入redis中,然后对于判断库存和id是否重复下单的操作,我们就在redis中操作
@Override
@Transactional
public void addSeckillVoucher(Voucher voucher) {
// 保存优惠券
save(voucher);
// 保存秒杀信息
SeckillVoucher seckillVoucher = new SeckillVoucher();
seckillVoucher.setVoucherId(voucher.getId());
seckillVoucher.setStock(voucher.getStock());
seckillVoucher.setBeginTime(voucher.getBeginTime());
seckillVoucher.setEndTime(voucher.getEndTime());
seckillVoucherService.save(seckillVoucher);
//保存秒杀优惠券的库存到Redis中
stringRedisTemplate.opsForValue().set("secKill:stock:" + voucher.getId(), voucher.getStock().toString());
}
2. 编写lua脚本
-- 1.参数列表
-- 1.1 优惠券id
local voucherId= ARGV[1]
-- 1.2 用户id
local userId=ARGV[2]
-- 2.数据key
-- 2.1 库存key
local stockKey='secKill:stock:'..voucherId -- 连接的时候,是使用..来连接 里面存的是库存
-- 2.2 订单key
local orderKey='secKill:order'..voucherId -- 里面存的是下单人的id
-- 3.脚本业务
-- 3.1 判断库存是否充足,get stockKey
if(tonumber(redis.call('get',stockKey)) <=0) then -- redis.call('get',stockKey)返回的是一个string类型的,无法和0进行比较,所以,转换
-- 3.2 库存不足,返回1
return 1 -- 什么意思
end
-- 3.3 判断用户是否已经购买过,SISMEMBER orderKye userId 对于set类型 sadd是添加,SISMEMBER是判断是否存在
if(tonumber(redis.call('SISMEMBER',orderKey,userId)) ==1) then
--3.4 重复下单,返回2
return 2
end
-- 3.5 扣减库存,incrby stockKey -1
redis.call('incrby',stockKey,-1)
-- 3.6 下单,sadd orderKye userId
redis.call('sadd',orderKey,userId)
return 0
3. 在代码里面运行lua脚本
/**
* * 使用lua脚本实现秒杀
*/
private static final DefaultRedisScript<Long> SECKILL_SCRIPT;//DefaultRedisScript 是 Spring Data Redis 提供的一个类,用于封装 Redis Lua 脚本。在这个上下文中
static {
//static { ... }
//这是一个静态初始化块,用于在类加载时初始化 SECKILL_SCRIPT 实例。
//在这个块中,设置了 SECKILL_SCRIPT 的具体属性,包括脚本的位置和结果类型。
SECKILL_SCRIPT = new DefaultRedisScript<>();
SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
SECKILL_SCRIPT.setResultType(Long.class);
}
/**
* 使用lua
* @param voucherId
* @return
*/
@Override
public Result seckillVoucher(Long voucherId) {
//获取用户
Long userId= UserHolder.getUser().getId();
//1.执行lua脚本
Long result = stringRedisTemplate.execute(//总共三个参数,第一个参数是我们的lua脚本,第二个是KEY的参数,第三个是ARGV的参数(有两个)
SECKILL_SCRIPT,
Collections.emptyList(),//KEY的参数,传一个空集合
voucherId.toString(),//ARGV的参数
userId.toString()
);
//2.判断结果是否为0
int i = result.intValue();//转型
if(result!=0){
//2.1不为零,没有购买资格
return Result.fail(result == 1 ? "库存不足" : "不能重复下单");
}
//2.2为0,有购买资格,把下单信息保存到阻塞队列
long orderId=redisIdWorker.nextId("order");//用我们之前的生成全局唯一的订单id方法生成订单
//TODO 保存到阻塞队列
//3.返回订单id
return Result.ok(orderId);
}