首页 > 其他分享 >史上最全秒杀+优化——两万字

史上最全秒杀+优化——两万字

时间:2024-09-01 11:50:35浏览次数:14  
标签:return 两万 最全 private public voucherId 秒杀 voucher id

秒杀

全局唯一id

@Component
public class RedisIdWorker {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 开始时间戳
     * public static void main(String[] args) {
     *         LocalDateTime beginTime = LocalDateTime.of(2024, 1, 1, 0, 0);
     *         long second = beginTime.toEpochSecond(ZoneOffset.UTC);
     *         System.out.println(second);
     *     }
     */
    private static final long BEGIN_TIMESTAMP = 1704067200;

    /**
     * 序列号位数
     */
    private static final long BITS_COUNT = 32;


    /**
     * 生成全局唯一id
     * @param keyPrefix
     * @return
     */
    public long nextId(String keyPrefix){
        // 1.生成时间戳
        LocalDateTime nowTime = LocalDateTime.now();
        long nowSecond = nowTime.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;
        // 2.生成序列号
        // 2.1.获取当前日期,精确到秒 (:是为了方便Redis使用)
        String date = nowTime.format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"));
        // 2.2.自增长
        Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
        // 3.拼接并返回
        return (timestamp << BITS_COUNT) | count;
    }
}

下单(未优化)

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private RedisIdWorker redisIdWorker;

    /**
     * 秒杀
     * @param voucherId
     * @return
     */
    @Override
    @Transactional
    public Result seckillVoucher(Long voucherId) {
        // 1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        // 2.判断秒杀是否已经开始
        LocalDateTime now = LocalDateTime.now();
        if(voucher.getBeginTime().isAfter(now)){
            return Result.fail("秒杀尚未开始!");
        }
        // 3.判断秒杀是否已经结束
        if(voucher.getEndTime().isBefore(now)){
            return Result.fail("秒杀已经结束");
        }
        // 4.判断库存是否充足
        if(voucher.getStock() <= 0){
            return Result.fail("库存不足!");
        }
        // 5.扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId).update();
        if(!success){
            return Result.fail("库存不足!");
        }
        // 6.生成订单
        VoucherOrder order = new VoucherOrder();
        // 6.1.生成订单id(全局唯一id)
        long orderId = redisIdWorker.nextId("order");
        order.setId(orderId);
        // 6.2.生成用户id
        Long userId = UserHolder.getUser().getId();
        order.setUserId(userId);
        // 6.3.生成优惠券id
        order.setVoucherId(voucherId);
        // 6.4.保存订单
        save(order);
        // 7.返回订单id(获取支付链接,用户支付)
        return Result.ok(orderId);
    }
}

解决超卖(乐观锁)

// 5.扣减库存
boolean success = seckillVoucherService.update()
        .setSql("stock = stock - 1")
        .eq("voucher_id", voucherId).eq("stock", voucher.getStock())
        .update();
if(!success){
    return Result.fail("库存不足!");
}
// 6.生成订单
VoucherOrder order = new VoucherOrder();

解决少卖

// 5.扣减库存
boolean success = seckillVoucherService.update()
        .setSql("stock = stock - 1")
        .eq("voucher_id", voucherId).gt("stock", 0)
        .update();
if(!success){
    return Result.fail("库存不足!");
}
// 6.生成订单
VoucherOrder order = new VoucherOrder();

实现一人一单

基于synchronized

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private RedisIdWorker redisIdWorker;

    /**
     * 秒杀
     * @param voucherId
     * @return
     */
    @Override
    public Result seckillVoucher(Long voucherId) {
        // 1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        // 2.判断秒杀是否已经开始
        LocalDateTime now = LocalDateTime.now();
        if(voucher.getBeginTime().isAfter(now)){
            return Result.fail("秒杀尚未开始!");
        }
        // 3.判断秒杀是否已经结束
        if(voucher.getEndTime().isBefore(now)){
            return Result.fail("秒杀已经结束");
        }
        // 4.判断库存是否充足
        if(voucher.getStock() <= 0){
            return Result.fail("库存不足!");
        }
        return createVoucherOrder(voucherId);
    }

    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        // 5.一人一单
        Long userId = UserHolder.getUser().getId();
        // 锁粒度降低,性能更好,intern()保证只锁相同的用户id值
        synchronized (userId.toString().intern()) {
            // 5.1.查询订单
            int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            // 5.2.判断该用户是否下过单
            if(count > 0) {
                return Result.fail("一人一购!");
            }
            // 6.扣减库存
            boolean success = seckillVoucherService.update()
                    .setSql("stock = stock - 1")
                    .eq("voucher_id", voucherId).gt("stock", 0)
                    .update();
            if(!success){
                return Result.fail("库存不足!");
            }
            // 7.生成订单
            VoucherOrder order = new VoucherOrder();
            // 7.1.生成订单id(全局唯一id)
            long orderId = redisIdWorker.nextId("order");
            order.setId(orderId);
            // 7.2.生成用户id
            order.setUserId(userId);
            // 7.3.生成优惠券id
            order.setVoucherId(voucherId);
            // 7.4.保存订单
            save(order);
            // 8.返回订单id(获取支付链接,用户支付)
            return Result.ok(orderId);
        }
    }
}

因为@Transactional是方法结束后才提交事务,而锁释放后,其他线程将会得到锁,继续执行,此时未提交事务,数据库可能未更新,还是会出现线程安全的,因此需要将调用的方法上锁

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private RedisIdWorker redisIdWorker;

    /**
     * 秒杀
     * @param voucherId
     * @return
     */
    @Override
    public Result seckillVoucher(Long voucherId) {
        // 1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        // 2.判断秒杀是否已经开始
        LocalDateTime now = LocalDateTime.now();
        if(voucher.getBeginTime().isAfter(now)){
            return Result.fail("秒杀尚未开始!");
        }
        // 3.判断秒杀是否已经结束
        if(voucher.getEndTime().isBefore(now)){
            return Result.fail("秒杀已经结束");
        }
        // 4.判断库存是否充足
        if(voucher.getStock() <= 0){
            return Result.fail("库存不足!");
        }

        Long userId = UserHolder.getUser().getId();
        // 锁粒度降低,性能更好,intern()保证只锁相同的用户id值
        synchronized (userId.toString().intern()) {
            return createVoucherOrder(voucherId);
        }
    }

    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        // 5.一人一单
        // 5.1.查询订单
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        // 5.2.判断该用户是否下过单
        if(count > 0) {
            return Result.fail("一人一购!");
        }
        // 6.扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId).gt("stock", 0)
                .update();
        if(!success){
            return Result.fail("库存不足!");
        }
        // 7.生成订单
        VoucherOrder order = new VoucherOrder();
        // 7.1.生成订单id(全局唯一id)
        long orderId = redisIdWorker.nextId("order");
        order.setId(orderId);
        // 7.2.生成用户id
        order.setUserId(userId);
        // 7.3.生成优惠券id
        order.setVoucherId(voucherId);
        // 7.4.保存订单
        save(order);
        // 8.返回订单id(获取支付链接,用户支付)
        return Result.ok(orderId);
    }
}

Spring AOP通过动态代理来管理事务,只有被Spring管理的bean才会被代理。如果事务方法在同一个类中被调用,而不是通过代理类调用,则事务不会生效。

synchronized (userId.toString().intern()) {
    IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
    return proxy.createVoucherOrder(voucherId);
}
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
</dependency>
@EnableAspectJAutoProxy(exposeProxy = true)

仍存在线程安全问题(跨JVM)

基于Redis分布式锁:

分布式锁:满足分布式系统或集群模式下多线程可见且互斥的锁

  1. 初级版本

public interface ILock {

    /**
     * 尝试获取锁
     * @param timeoutSecond
     * @return
     */
    boolean tryLock(long timeoutSecond);

    /**
     * 释放锁
     */
    void unlock();
}
public class SimpleRedisLock implements ILock{

    private StringRedisTemplate stringRedisTemplate;
    private String lockName;
    private static final String KEY_PREFIX = "lock:";
    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String lockName) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
    }

    /**
     * 尝试获取锁
     * @param timeoutSecond
     * @return
     */
    @Override
    public boolean tryLock(long timeoutSecond) {
        long threadId = Thread.currentThread().getId();
        Boolean success = stringRedisTemplate.opsForValue().
                setIfAbsent(KEY_PREFIX + lockName, threadId + "", timeoutSecond, TimeUnit.SECONDS);
        // 防止自动拆箱空指针的安全问题
        return BooleanUtil.isTrue(success);
    }

    /**
     * 释放锁
     */
    @Override
    public void unlock() {
        stringRedisTemplate.delete(KEY_PREFIX + lockName);
    }
}
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private RedisIdWorker redisIdWorker;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 秒杀
     * @param voucherId
     * @return
     */
    @Override
    public Result seckillVoucher(Long voucherId) {
        // 1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        // 2.判断秒杀是否已经开始
        LocalDateTime now = LocalDateTime.now();
        if(voucher.getBeginTime().isAfter(now)){
            return Result.fail("秒杀尚未开始!");
        }
        // 3.判断秒杀是否已经结束
        if(voucher.getEndTime().isBefore(now)){
            return Result.fail("秒杀已经结束");
        }
        // 4.判断库存是否充足
        if(voucher.getStock() <= 0){
            return Result.fail("库存不足!");
        }

        Long userId = UserHolder.getUser().getId();
        // 创建锁对象,注意锁的粒度,每个用户一把锁,所以拼接userId
        SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate, "order:" + userId);
        // 尝试获取锁
        boolean isLock = lock.tryLock(1200);
        // 判断是否获取锁成功
        if(!isLock){
            // 获取锁失败,返回错误或重试
            return Result.fail("一人一单!");
        }
        /*// 锁粒度降低,性能更好,intern()保证只锁相同的用户id值
        synchronized (userId.toString().intern()) {*/
        try {
            // 获取代理对象(事务)
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        } finally {
            // 释放锁
            lock.unlock();
        }
//        }
    }

    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        // 5.一人一单
        Long userId = UserHolder.getUser().getId();
        // 5.1.查询订单
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        // 5.2.判断该用户是否下过单
        if(count > 0) {
            return Result.fail("一人一购!");
        }
        // 6.扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId).gt("stock", 0)
                .update();
        if(!success){
            return Result.fail("库存不足!");
        }
        // 7.生成订单
        VoucherOrder order = new VoucherOrder();
        // 7.1.生成订单id(全局唯一id)
        long orderId = redisIdWorker.nextId("order");
        order.setId(orderId);
        // 7.2.生成用户id
        order.setUserId(userId);
        // 7.3.生成优惠券id
        order.setVoucherId(voucherId);
        // 7.4.保存订单
        save(order);
        // 8.返回订单id(获取支付链接,用户支付)
        return Result.ok(orderId);
    }
}

存在的问题:误删除

  1. 改进

/**
 * 尝试获取锁
 * @param timeoutSecond
 * @return
 */
@Override
public boolean tryLock(long timeoutSecond) {
    // 获取线程标识
    String threadId = ID_PREFIX + Thread.currentThread().getId();
    // 获取锁
    Boolean success = stringRedisTemplate.opsForValue().
            setIfAbsent(KEY_PREFIX + lockName, threadId, timeoutSecond, TimeUnit.SECONDS);
    // 防止自动拆箱空指针的安全问题
    return BooleanUtil.isTrue(success);
}

/**
 * 释放锁
 */
@Override
public void unlock() {
    // 获取线程标识
    String threadId = ID_PREFIX + Thread.currentThread().getId();
    // 获取锁中标识
    String lockId = stringRedisTemplate.opsForValue().get(KEY_PREFIX + lockName);
    // 判断标识是否一致
    if(threadId.equals(lockId)){
        // 释放锁
        stringRedisTemplate.delete(KEY_PREFIX + lockName);
    }
}

仍存在问题:

  1. 基于Lua脚本实现秒杀

-- 比较线程标识与锁中的线程标识是否一致
if(redis.call('get', KEYS[1]) == ARGV[1]) then
    -- 释放锁
    return redis.call('del', KEYS[1])
end
return 0
// 提前读取,提高性能
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {
    UNLOCK_SCRIPT = new DefaultRedisScript<>();
    UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
    UNLOCK_SCRIPT.setResultType(Long.class);
}
/**
* 释放锁
*/
@Override
public void unlock() {
    // 调用Lua脚本
    stringRedisTemplate.execute(
        UNLOCK_SCRIPT,
        Collections.singletonList(KEY_PREFIX + lockName),
        ID_PREFIX + Thread.currentThread().getId());
}

基于Redis的分布式锁存在的问题

☆基于Redisson的分布式锁

<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.35.0</version>
</dependency> 
@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient(){
        // 配置类
        Config config = new Config();
        // 添加Redis地址,单点,集群:config.useClusterServers()
        config.useSingleServer().setAddress("192.168.174.129:6379").setPassword("123456");
        return Redisson.create(config);
    }
}
// Redisson
RLock lock = redissonClient.getLock("lock:order:" + userId);
// 尝试获取锁
boolean isLock = lock.tryLock();
// 判断是否获取锁成功
if(!isLock){
    // 获取锁失败,返回错误或重试
    return Result.fail("一人一单!");
}
try {
    // 获取代理对象(事务)
    IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
    return proxy.createVoucherOrder(voucherId);
} finally {
    // 释放锁
    lock.unlock();
}

秒杀优化

基于JVM(BlockingQueue)优化:

存在的问题:OOM,消息一旦出故障

@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_KEY + voucher.getId(), voucher.getStock().toString());
}

@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private RedisIdWorker redisIdWorker;

    @Autowired
    private StringRedisTemplate stringRedisTemplate; 
    
    @Resource
    private RedissonClient redissonClient;

    // 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);
    }

    // 代理对象
    private IVoucherOrderService proxy;

    // 阻塞队列
    private  BlockingQueue<VoucherOrder> ORDER_TASKS = new ArrayBlockingQueue<>(1024 * 1024);

    // 完成异步下单的线程
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

    // 异步下单在初始化完就执行
    @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 = ORDER_TASKS.take();
                    // 2.创建订单
                    handleVoucherOrder(voucherOrder);
                } catch (InterruptedException e) {
                    log.error("处理订单异常", e);
                }
            }
        }
    }

    private void handleVoucherOrder(VoucherOrder voucherOrder) {
        // 创建锁对象
        RLock lock = redissonClient.getLock("lock:order:" + voucherOrder.getUserId());
        // 获取锁
        boolean isLock = lock.tryLock();
        // 判断是否获取锁成功
        if(!isLock){
            // 获取锁失败,重试或返回错误细信息
            log.error("一人一单!");
            return;
        }
        try {
            // 保存订单
            proxy.createVoucherOrder(voucherOrder);
        } finally {
            // 释放锁
            lock.unlock();
        }

    }

    /**
     * 秒杀
     * @param voucherId
     * @return
     */
    @Override
    public Result seckillVoucher(Long voucherId) {
        // 获取用户
        Long userId = UserHolder.getUser().getId();
        // 1.执行lua脚本
        Long longResult = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(), userId.toString());
        // 2.判断结果是否为0
        int result = longResult.intValue();
        // 2.1.结果不为0,无购买资格,返回错误信息
        if(result != 0){
            return Result.fail(result == 1 ? "库存不足!" : "一人一单!");
        }
        // 2.2.结果为0,有购买资格,把下单信息保存到阻塞队列
        // 2.3.生成订单
        VoucherOrder voucherOrder = new VoucherOrder();
        // 2.4.生成订单id(全局唯一id)
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        // 2.5.生成用户id
        voucherOrder.setUserId(userId);
        // 2.6.生成优惠券id
        voucherOrder.setVoucherId(voucherId);
        // 2.7.放入阻塞队列
        ORDER_TASKS.add(voucherOrder);
        // 3.获取代理对象
        proxy = (IVoucherOrderService) AopContext.currentProxy();
        // 4.返回订单id
        return Result.ok(orderId);
    }

    @Transactional
    public void createVoucherOrder(VoucherOrder voucherOrder) {
        // 5.一人一单
        Long userId = voucherOrder.getUserId();
        Long voucherId = voucherOrder.getVoucherId();
        // 5.1.查询订单
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        // 5.2.判断该用户是否下过单
        if(count > 0) {
            log.error("一人一单!");
        }
        // 6.扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId).gt("stock", 0)
                .update();
        if(!success){
            log.error("库存不足!");
        }
        // 7.4.保存订单
        save(voucherOrder);
    }
}

基于Redis-Stream优化

-- 1.参数列表
-- 1.1.优惠券id
local voucherId = ARGV[1]
-- 1.2.用户id
local userId = ARGV[2]
-- 1.3.订单id
local orderId = ARGV[3]

-- 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.1.1.库存不足,返回1
    return 1
end
-- 3.2.判断用户是否下单
if redis.call('sismember', orderKey, userId) == 1 then
    -- 3.3.存在,重复下单,返回2
    return 2
end
-- 3.4.扣库存
redis.call('incrby', stockKey, -1)
-- 3.5.下单(保存用户)
redis.call('sadd', orderKey, userId)
-- 3.6.向stream.orders中添加消息
redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)
--redis.call('xgroup', 'create', 'stream.orders', 'g1', '0', 'mkstream')
return 0
@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Resource
    private RedisIdWorker redisIdWorker;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;

    // 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);
    }

    // 代理对象
    private IVoucherOrderService proxy;

    // 完成异步下单的线程
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

    // 异步下单在初始化完就执行
    @PostConstruct
    private void init(){
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
    }

    private class VoucherOrderHandler implements Runnable{
        private String QUEUE_NAME = "stream.orders";
        // 完成异步下单
        @Override
        public void run() {
            // 检查并创建消费者组,避免流不存在的问题
            try {
                stringRedisTemplate.opsForStream().createGroup(QUEUE_NAME, ReadOffset.latest(), "g1");
            } catch (RedisSystemException e) {
                // 如果组已存在,忽略该错误
                if (e.getMessage().contains("BUSYGROUP")) {
                    log.info("Consumer group 'g1' already exists.");
                } else {
                    throw e;  // 其他异常继续抛出
                }
            }
            while (true){
                try {
                    // 1.获取消息队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.orders >
                    List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
                        Consumer.from("g1", "c1"),
                        StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
                        StreamOffset.create(QUEUE_NAME, ReadOffset.lastConsumed())
                    );
                    // 2.判断消息获取是否成功
                    if (list == null || list.isEmpty()){
                        // 2.1.获取失败,继续下一次循环
                        continue;
                    }
                    // 3.解析消息中的订单信息
                    MapRecord<String, Object, Object> record = list.get(0);
                    Map<Object, Object> values = record.getValue();
                    VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);
                    // 4.创建订单
                    handleVoucherOrder(voucherOrder);
                    // 5.ACK确认 SACK stream.orders g1 id
                    stringRedisTemplate.opsForStream().acknowledge(QUEUE_NAME, "g1", record.getId());
                }
                catch (Exception e) {
                    log.error("处理订单异常", e);
                    handlePendingList();
                }
            }
        }

        // 处理PendingList中消息
        private void handlePendingList() {
            // 检查并创建消费者组,避免流不存在的问题
            try {
                stringRedisTemplate.opsForStream().createGroup(QUEUE_NAME, ReadOffset.latest(), "g1");
            } catch (RedisSystemException e) {
                // 如果组已存在,忽略该错误
                if (e.getMessage().contains("BUSYGROUP")) {
                    log.info("Consumer group 'g1' already exists.");
                } else {
                    throw e;  // 其他异常继续抛出
                }
            }
            while (true){
                try {
                    // 1.获取PendingList中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 STREAMS streams.order 0
                    List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
                            Consumer.from("g1", "c1"),
                            StreamReadOptions.empty().count(1),
                            StreamOffset.create(QUEUE_NAME, ReadOffset.from("0"))
                    );
                    // 2.判断消息获取是否成功
                    if (list == null || list.isEmpty()){
                        // 2.1.获取失败,PendingList没有异常消息,结束循环
                        break;
                    }
                    // 3.解析消息中的订单信息
                    MapRecord<String, Object, Object> record = list.get(0);
                    Map<Object, Object> values = record.getValue();
                    VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);
                    // 4.创建订单
                    handleVoucherOrder(voucherOrder);
                    // 5.ACK确认 SACK stream.orders g1 id
                    stringRedisTemplate.opsForStream().acknowledge("s1", "g1", record.getId());
                } catch (Exception e) {
                    log.error("处理PendingList订单异常", e);
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    }


    private void handleVoucherOrder(VoucherOrder voucherOrder) {
        // 创建锁对象
        RLock lock = redissonClient.getLock("lock:order:" + voucherOrder.getUserId());
        // 获取锁
        boolean isLock = lock.tryLock();
        // 判断是否获取锁成功
        if(!isLock){
            // 获取锁失败,重试或返回错误细信息
            log.error("一人一单!");
            return;
        }
        try {
            // 保存订单
            proxy.createVoucherOrder(voucherOrder);
        } finally {
            // 释放锁
            lock.unlock();
        }

    }

    /**
     * 秒杀
     * @param voucherId
     * @return
     */
    // Redis中的Stream实现消息队列
    @Override
    public Result seckillVoucher(Long voucherId) {
        // 获取用户
        Long userId = UserHolder.getUser().getId();
        // 生成订单id(全局唯一id)
        long orderId = redisIdWorker.nextId("order");
        // 1.执行lua脚本
        Long longResult = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(), userId.toString(), String.valueOf(orderId)
        );
        // 2.判断结果是否为0
        int result = longResult.intValue();
        // 2.1.结果不为0,无购买资格,返回错误信息
        if(result != 0){
            return Result.fail(result == 1 ? "库存不足!" : "一人一单!");
        }
        // 3.获取代理对象
        proxy = (IVoucherOrderService) AopContext.currentProxy();
//        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
        // 4.返回订单id
        return Result.ok(orderId);
    }
    
    @Transactional
    public void createVoucherOrder(VoucherOrder voucherOrder) {
        // 5.一人一单
        Long userId = voucherOrder.getUserId();
        Long voucherId = voucherOrder.getVoucherId();
        // 5.1.查询订单
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        // 5.2.判断该用户是否下过单
        if(count > 0) {
            log.error("一人一单!");
        }
        // 6.扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId).gt("stock", 0)
                .update();
        if(!success){
            log.error("库存不足!");
        }
        // 7.保存订单
        save(voucherOrder);
    }
}

标签:return,两万,最全,private,public,voucherId,秒杀,voucher,id
From: https://blog.csdn.net/qq_63275583/article/details/141576292

相关文章

  • 【信息收集】网络空间测绘FOFA,查询语法最全使用方法(图文解析)
    高级搜索基础类别标记类(SpecialLabel)协议类(type=service)网站类(type=subdomain)证书类地理位置(Location)时间类(Lastupdatetime)独立IP语法(独立IP系列语法,不可和上面其他语法共用)查询基础语法案例html搜索title标题搜索domain域名搜索host主机名搜索o......
  • 最全!嵌入式STM32单片机开发环境配置教学Win/Mac!!!DA
    合集-环境配置(1)1.最全!嵌入式STM32单片机开发环境配置教学Win/Mac!!!08-28收起嵌入式STM32单片机开发环境配置教学Win/Mac·本教程支持Windows和Mac·Windows可选的开发软件为Keil、Clion、STM32CubeMX,可自由选择开发方式·Mac的开发环境为(Clion+OpenOCD+STM32CubeMX),仅支......
  • 设计秒杀系统应该注意的 5 个架构原则
    架构原则:“4要1不要”如果你是一个架构师,你首先要勾勒出一个轮廓,想一想如何构建一个超大流量并发读写、高性能,以及高可用的系统,这其中有哪些要素需要考虑。我把这些要素总结为“4要1不要”。1.数据要尽量少所谓“数据要尽量少”,首先是指用户请求的数据能少就少。请求......
  • 24年最全面的AI绘画变现途径总结!
    前言4.7制作红包封面中国的节日和传统文化元素仍然可以成为创作者们的创作灵感,创造出更多的变现机会。比如元宵节,可以制作大型元宵图案,进行引流并卖出元宵。而春分、谷雨等节气也可以成为创作的灵感来源,创作出与之相关的图案,吸引更多粉丝。这样不仅可以吸引新粉丝,也可以......
  • 最全!嵌入式STM32单片机开发环境配置教学Win/Mac!!!
    嵌入式STM32单片机开发环境配置教学Win/Mac    ·本教程支持Windows和Mac    ·Windows可选的开发软件为Keil、Clion、STM32CubeMX,可自由选择开发方式    ·Mac的开发环境为(Clion+OpenOCD+STM32CubeMX),仅支持HAL库Windows配置教程        在Windows......
  • 面试必考问题:Android APP耗电最全解析和优化指南
    目录1AndroidAPP耗电原因分析1.1后台应用持续运行1.2高CPU使用率1.3网络使用不当1.4错误代码实现2Android不同版本的耗电优化功能2.1JobSchedulingAPI与BatteryHistorian2.2JobSchedulingAPI的深入解析2.3BatteryHistorian的实际应用2.4结合JobSch......
  • Redis秒杀场景
    秒杀秒杀开始前前端提前使用cdn缓存页面信息,防止客户端频繁刷新,把所有请求都落在数据库导致数据库崩掉。数据方面,要提前把秒杀要用的数据存在Redis中。秒杀进行中Redis方面,因为用户请求多,但是商品少。把商品以hash的形式存储在Redis中。查询命令都落在Redis,防止数据库命令......
  • 【Leetcode 2032 】 至少在两个数组中出现的值 —— 哈希表与按位运算符(最全的注解)
    给你三个整数数组 nums1、nums2 和 nums3 ,请你构造并返回一个 元素各不相同的 数组,且由 至少 在 两个 数组中出现的所有值组成。数组中的元素可以按 任意 顺序排列。示例1:输入:nums1=[1,1,3,2],nums2=[2,3],nums3=[3]输出:[3,2]解释:至少在两个数组中出......
  • 在深度学习程序中显示美观的进度条(史上最全的tqdm使用大全)
    tqdm用例大全下载tqdm库基础用法跟列表结合使用带提示的进度条自己设置进度条和自己定义更新自定义进度条处理单位动态显示损失值对于跑深度学习的人来说,如何更直观的观察训练进度和模型损失是十分重要的事。好的深度学习代码,在训练过程中能够直观的给人呈现出进度和......
  • 【florr】全网最全花瓣图鉴
    可能更卡但是更齐全的食用体验可能更卡但是更齐全的食用体验可能更卡但是更齐全的食用体验......