【微服务】优惠券(有限)秒杀的实现思路--使用Redis预扣减优惠券库存,结合Redisson锁保证并发安全,通过异步消息队列更新数据库库存

介绍

即在订单微服务中使用Redis预扣减优惠券库存,并结合Redisson分布式锁防止超卖情况,然后通过异步消息队列最终扣减数据库中的库存,我们可以按照以下步骤进行代码实现。这里假设你已经配置好了Redis、RabbitMQ(或其他消息队列)以及Redisson。

1. 引入必要的依赖

确保你的pom.xml文件中包含以下依赖:

xml 复制代码
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.16.4</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. Redisson分布式锁与库存预扣减

创建一个服务类来处理优惠券的预扣减逻辑,使用Redisson分布式锁确保并发安全。

java 复制代码
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class CouponService {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public boolean grabCoupon(String couponId) {
        String lockKey = "lock:coupon:" + couponId;
        RLock lock = redissonClient.getLock(lockKey);

        try {
            if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
                // 获取当前库存和逻辑过期时间
                String key = "coupon_stock:" + couponId;
                String stockStr = redisTemplate.opsForValue().get(key);
                if (stockStr == null || isExpired(stockStr)) {
                    return false; // 库存不足或已过期
                }

                // 解析库存信息
                CouponStockWithExpire couponStockWithExpire = parseCouponStock(stockStr);
                if (couponStockWithExpire.getStock() <= 0) {
                    return false; // 库存不足
                }

                // 扣减库存
                couponStockWithExpire.setStock(couponStockWithExpire.getStock() - 1);
                updateCouponStock(couponId, couponStockWithExpire);

                // 发送消息到队列以最终扣减数据库库存
                sendDeductStockMessage(couponId);
                return true;
            } else {
                return false; // 获取锁失败
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    private boolean isExpired(String stockStr) {
        // 假设stockStr格式为"stock|expireTime"
        String[] parts = stockStr.split("\\|");
        long expireTime = Long.parseLong(parts[1]);
        return System.currentTimeMillis() > expireTime;
    }

    private CouponStockWithExpire parseCouponStock(String stockStr) {
        String[] parts = stockStr.split("\\|");
        int stock = Integer.parseInt(parts[0]);
        long expireTime = Long.parseLong(parts[1]);
        return new CouponStockWithExpire(stock, expireTime);
    }

    private void updateCouponStock(String couponId, CouponStockWithExpire couponStockWithExpire) {
        redisTemplate.opsForValue().set("coupon_stock:" + couponId,
                couponStockWithExpire.getStock() + "|" + couponStockWithExpire.getExpireTime());
    }

    private void sendDeductStockMessage(String couponId) {
        rabbitTemplate.convertAndSend("coupon.deduct.exchange", "coupon.deduct.routingKey", couponId);
    }
}

3. 消息消费者

创建一个消息消费者来监听队列并执行数据库中的库存扣减操作。

java 复制代码
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class CouponDeductListener {

    @RabbitListener(queues = "coupon.deduct.queue")
    public void handleDeductStock(String couponId) {
        // 在这里执行实际的数据库库存扣减逻辑
        System.out.println("Deducting stock for coupon: " + couponId);
        // 假设有一个方法deductStockInDatabase来扣减数据库中的库存
        deductStockInDatabase(couponId);
    }

    private void deductStockInDatabase(String couponId) {
        // 实现具体的数据库库存扣减逻辑
    }
}

4. 配置RabbitMQ

确保你的application.yml中正确配置了RabbitMQ连接信息和交换机、队列等信息:

yaml 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

  # 定义交换机和队列
rabbitmq:
  exchanges:
    coupon:
      name: coupon.deduct.exchange
      type: direct
  queues:
    coupon:
      name: coupon.deduct.queue
  bindings:
    coupon:
      destination: coupon.deduct.queue
      exchange: coupon.deduct.exchange
      routing-key: coupon.deduct.routingKey

5. 控制器层调用

在控制器中定义API接口供客户端调用:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private CouponService couponService;

    @PostMapping("/grabCoupon")
    public String grabCoupon(@RequestParam String couponId) {
        if (couponService.grabCoupon(couponId)) {
            return "Coupon grabbed successfully!";
        } else {
            return "Failed to grab coupon.";
        }
    }
}

6.定时任务清理过期优惠券Key

根据业务设置定时任务

使用Spring的@Scheduled注解创建一个定时任务,定期扫描并删除已过期的优惠券键。

java 复制代码
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Set;

@Component
public class ExpiredCouponCleaner {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Scheduled(fixedRate = 604800000) // 每周执行一次
    public void cleanExpiredCoupons() {
        Set<String> keys = redisTemplate.keys("coupon_stock:*");
        for (String key : keys) {
            String stockStr = redisTemplate.opsForValue().get(key);
            if (isExpired(stockStr)) {
                redisTemplate.delete(key); // 删除过期的键
            }
        }
    }

    private boolean isExpired(String stockStr) {
        // 同grabCoupon方法中的isExpired实现
        String[] parts = stockStr.split("\\|");
        long expireTime = Long.parseLong(parts[1]);
        return System.currentTimeMillis() > expireTime;
    }
}

通过这种方式,我们实现了在订单微服务中使用Redis预扣减优惠券库存,并结合Redisson分布式锁确保并发安全性,随后通过异步消息队列最终扣减数据库中的库存。这种方法不仅提高了系统的响应速度和处理高并发能力,同时也确保了数据的一致性和避免了超卖问题的发生。

相关推荐
●VON1 小时前
重生之我在暑假学习微服务第二天《MybatisPlus-下篇》
java·学习·微服务·架构·mybatis-plus
老华带你飞1 小时前
口腔助手|口腔挂号预约小程序|基于微信小程序的口腔门诊预约系统的设计与实现(源码+数据库+文档)
java·数据库·微信小程序·小程序·论文·毕设·口腔小程序
hqxstudying1 小时前
J2EE模式---服务层模式
java·数据库·后端·spring·oracle·java-ee
Yu_Lijing1 小时前
MySQL进阶学习与初阶复习第四天
数据库·学习·mysql
大熊程序猿1 小时前
net8.0一键创建支持(Orm-Sqlite-MySql-SqlServer)
数据库·mysql·sqlite
武子康8 小时前
Java-80 深入浅出 RPC Dubbo 动态服务降级:从雪崩防护到配置中心秒级生效
java·分布式·后端·spring·微服务·rpc·dubbo
-SGlow-10 小时前
MySQL相关概念和易错知识点(2)(表结构的操作、数据类型、约束)
linux·运维·服务器·数据库·mysql
明月56611 小时前
Oracle 误删数据恢复
数据库·oracle
♡喜欢做梦12 小时前
【MySQL】深入浅出事务:保证数据一致性的核心武器
数据库·mysql
遇见你的雩风13 小时前
MySQL的认识与基本操作
数据库·mysql