Redis代金卷(优惠卷)秒杀案例-多应用版

Redis代金卷(优惠卷)秒杀案例-单应用版-CSDN博客

上面这种方案,在多应用时候会出现问题,原因是你通过用户ID加锁

但是在多应用情况下,会出现两个应用的用户都有机会进去

让多个JVM使用同一把锁

这样就需要使用分布式锁

每个JVM都会有一个锁监视器,多个JVM就会有多个锁监视器

那么让所有JVM使用外部同一个锁监视器即可

不同的分布式锁实现方案

Redis分布式锁实现方案一

复制代码
package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.SimpleRedisLock;
import com.hmdp.utils.UserHolder;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {


    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private RedisIdWorker redisIdWorker;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券
        SeckillVoucher byId = seckillVoucherService.getById(voucherId);
        if (byId.getBeginTime().isAfter(LocalDateTime.now())) {
            //2.判断秒杀是否开始
            return Result.fail("秒杀尚未开始");
        }
        if (byId.getEndTime().isBefore(LocalDateTime.now())) {
            //3.判断秒杀是否结束
            return Result.fail("秒杀已经结束");
        }
        if (byId.getStock() < 1) {
            //4.判断库存是否充足
            return Result.fail("库存不足");
        }
        Long id = UserHolder.getUser().getId();//表示获取用户id
        SimpleRedisLock simpleRedisLock = new SimpleRedisLock("order:" + id, redisTemplate);
        //id.toString()实际是new了一个String对象,然后调用intern()方法,这个方法会去常量池中查找有没有这个对象,如果有就返回这个对象,如果没有就添加到常量池中,然后返回这个对象
        //synchronized (id.toString().intern()){
        boolean isLock = simpleRedisLock.tryLock(10L);//对于计算机来说10秒已经够了  除非业务非常复杂
        if(!isLock){
            return Result.fail("不允许重复下单");
        }
        //如果这样调用,前面有个this 事务使用代理对象去执行的
            //return createVoucherOrder(voucherId);
        try {
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }catch (Exception e){
            e.printStackTrace();
            return Result.fail(e.getMessage());
        }finally {
            simpleRedisLock.unlock();
        }

       // }
    }
    @Transactional(rollbackFor = Exception.class)
    public Result createVoucherOrder(Long voucherId) {
        Long id = UserHolder.getUser().getId();//
        //一人一单的查询
        Integer count = query().eq("user_id", id)
                .eq("voucher_id", voucherId)
                .count();
        if(count > 0){
            return Result.fail("您已经购买过一次了");
        }
        //5.扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .gt("stock", 0)
                .update();
        if(!success){
            return Result.fail("库存不足");
        }
        //6.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //6.1.订单id
        long order = redisIdWorker.nextId("order");
        voucherOrder.setId(order);
        voucherOrder.setUserId(id);//登录用户先写死
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        Result.ok(order);
    }
}

以上代码,在极端情况下会出现问题,例如 业务非常复杂,锁提前释放了,但是业务还没完成

可以通过线程标识 判断下是不是当前线程

原先用的是线程id,而线程ID往往在一个JVM中是递增的,但是考虑到多应用时候,可能出现线程ID相同的情况,因此,缓存UUID更好

改造代码

复制代码
package com.hmdp.utils;

import cn.hutool.core.lang.UUID;
import com.hmdp.service.ILock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;


import java.util.concurrent.TimeUnit;

/**
 * @author hrui
 * @date 2025/1/31 3:09
 */
public class SimpleRedisLock implements ILock {

    private String name;
    private RedisTemplate<String,Object> redisTemplate;//需要对RedisTemplate<String,Object>进行配置注入
    //import cn.hutool.core.lang.UUID;会把UUID中的"-"去掉
    private static final String ID_PREFIX=UUID.randomUUID().toString(true)+"-";//import cn.hutool.core.lang.UUID;
    private static final String KEY_PREFIX = "lock:";

    public SimpleRedisLock(RedisTemplate<String, Object> redisTemplate, String name) {
        this.redisTemplate = redisTemplate;
        this.name = name;
    }

    @Override
    public boolean tryLock(long timeoutSec) {
        Boolean success = redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name,
                ID_PREFIX + Thread.currentThread().getId(),//用线程ID做为value,可以更加直观些 其实放什么无所谓
                timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);//避免空指针
    }

    @Override
    public void unlock() {
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        String id = (String) redisTemplate.opsForValue().get(KEY_PREFIX + name);
        if (threadId.equals(id)) {
            //符合 则删除,不符合就删除不了
            redisTemplate.delete(KEY_PREFIX + name);
        }
    }
}

以上代码还是有问题

原因在于GC回收时候,还是有那么一点点可能因业务时长导致锁的自动删除

要保证加锁和解锁的原子性

相关推荐
MuYiLuck7 分钟前
【redis实战篇】第八天
数据库·redis·缓存
睡觉待开机8 分钟前
6. MySQL基本查询
数据库·mysql
�FENG34 分钟前
Redis 安装配置和性能优化
redis·持久化
大熊猫侯佩1 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(三)
数据库·swiftui·swift
大熊猫侯佩1 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(二)
数据库·swiftui·swift
大熊猫侯佩1 小时前
用异步序列优雅的监听 SwiftData 2.0 中历史追踪记录(History Trace)的变化
数据库·swiftui·swift
大熊猫侯佩1 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(一)
数据库·swiftui·swift
Ares-Wang1 小时前
负载均衡LB》》HAproxy
运维·数据库·负载均衡
AI.NET 极客圈1 小时前
.NET 原生驾驭 AI 新基建实战系列(四):Qdrant ── 实时高效的向量搜索利器
数据库·人工智能·.net
weixin_470880262 小时前
MySQL体系架构解析(二):MySQL目录与启动配置全解析
数据库·mysql·面试·mysql体系架构·mysql bin目录