Redis优化秒杀
异步秒杀思路:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xd5i89Jh-1669207961602)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221120161810736.png)]](https://i-blog.csdnimg.cn/blog_migrate/2de091edda28dcfb402f5d5931597dc6.png)
思路,在多线程同时秒杀时,由于判断秒杀库存和校验一人一单用时短,而减库存和创建订单时间耗时长。
所以采用同步思路就浪费时间。如果采用异步思路就能:在redis中判断秒杀库存和校验一人一单,在tomcat中读取队列中的信息对数据库进行操作。在redis返回结果,Tomcat判断生成id并返回。大大提高用户体验。
Redis的操作通过Lua脚本保证原子性
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8xsz5DU9-1669207961604)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221120165237766.png)]](https://i-blog.csdnimg.cn/blog_migrate/43e2ec35adc896fac68e600d26af5d29.png)
改进秒杀业务,提高并发性能:
需求:
新增秒杀优惠券的同时,将优惠券保存到Redis中
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_KEY + voucher.getId(),voucher.getStock().toString());
}
基于Lua脚本,判断秒杀库存,一人一单,决定用户是否抢购成功,
如果抢购成功,将优惠劵id和用户id封装后存入阻塞队列
**基于Redis完成秒杀资格判断:**
//执行lua脚本
private static final DefaultRedisScript<Long> SECKILL_SCRIPT ;
static {
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(
SECKILL_SCRIPT,
Collections.emptyList(),
voucherId.toString(),
userId.toString()
);
//2.判断结果是为0
int r = result.intValue();
if (r !=0 ){
//2.1.不为0,代表没有购买资格
return Result.fail(r==1 ? "库存不足":"不能重复下单");
}
//2.2.为0,有购买资格,把下单信息保存到阻塞队列
long orderId = redisIdWorker.nextId("order");
//todo 保存阻塞队列
//3.返回订单id
return Result.ok(orderId);
}
seckill.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
-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if(tonumber(redis.call('get',stockKey)) <= 0 ) then
--3.2.库存不足,返回1
return 1
end
-- 3.2.判断用户是否下单 sismember orderKey userId
if(redis.call('sismember', orderKey,userId) ==1 ) then
--3.3.存在, 说明是重复下单,返回2
return 2
end
-- 3.4.扣库存 incrby stockKey -1
redis.call('incrby',stockKey,-1)
-- 3.5.下单(保存用户)sadd orderKey userId
redis.call('sadd',orderKey,userId)
return 0
开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能
//阻塞队列
private BlockingQueue<VoucherOrder> orderTasks =new ArrayBlockingQueue<>(1024*1024);
//线程池
private static final ExecutorService seckill_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
//当前类初始化完了就执行init方法
@PostConstruct
private void init(){
seckill_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
}
//线程任务
private class VoucherOrderHandler implements Runnable{
@Override
public void run() {
while (true){
try {
//1.获取队列中的订单信息
VoucherOrder voucherOrder = orderTasks.take();
//2.创建订单
handleVoucherOrder(voucherOrder);
} catch (Exception e) {
log.error("处理订单异常",e);
}
}
}
}
private void handleVoucherOrder(VoucherOrder voucherOrder) {
//1.获取用户
Long userId =voucherOrder.getUserId();
//2.创建锁对象
RLock lock = redissonClient.getLock("lock:order:"+userId);
//3.获取锁
boolean isLock =lock.tryLock();
//4.判断是否获取锁成功
if (!isLock){
//获取锁失败,返回错误或重试
log.error("不允许重复下单");
}
try {
proxy.createVoucherOrderYiBu(voucherOrder);
}finally {
//释放锁
lock.unlock();
}
}
public void createVoucherOrderYiBu(VoucherOrder voucherOrder){
//6.一个人一单
Long userId = voucherOrder.getUserId();
//6.1查询订单
int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
//6.2判断是否存在
if (count > 0) {
//用户以及购买过
log.error("用户已经购买过一次");
return;
}
//7.扣减库存
boolean success = iSeckillVoucherService.update()
.setSql("stock =stock -1")
.eq("voucher_id", voucherOrder.getVoucherId())
.gt("stock", 0).update();
if (!success) {
//扣减失败
log.error("库存不足");
return ;
}
save(voucherOrder);
}
1144

被折叠的 条评论
为什么被折叠?



