【微服务】优惠券(有限)秒杀的实现思路--使用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分布式锁确保并发安全性,随后通过异步消息队列最终扣减数据库中的库存。这种方法不仅提高了系统的响应速度和处理高并发能力,同时也确保了数据的一致性和避免了超卖问题的发生。

相关推荐
m0_748250939 分钟前
SQL Server Management Studio的使用
数据库·oracle·性能优化
车载诊断技术10 分钟前
人工智能AI在汽车设计领域的应用探索
数据库·人工智能·网络协议·架构·汽车·是诊断功能配置的核心
没有十八岁14 分钟前
云创智城YunCharge 新能源二轮、四轮充电解决方案(云快充、万马爱充、中电联、OCPP1.6J等多个私有单车、汽车充电协议)之新能源充电行业系统说明书
java·数据库·spring·汽车
pitt19971 小时前
Redis 高可用性:如何让你的缓存一直在线,稳定运行?
redis·redis集群·哨兵·redis主从·redis高可用
爱搞技术的猫猫2 小时前
微店商品详情API接口实战指南:从零实现商品数据自动化获取
大数据·linux·运维·数据库·自动化
大地爱2 小时前
如何使用Spring Boot框架整合Redis:超详细案例教程
spring boot·redis·后端
若云止水2 小时前
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_init_cycle 函数 - 详解(1)
数据库·nginx·ubuntu
WannaRunning4 小时前
MySQL中的共享锁和排他锁
数据库·mysql
lusklusklusk4 小时前
Sqlserver安全篇之_启用TLS即配置SQL Server 数据库引擎以加密连接
数据库·安全·sqlserver
zyplayer-doc4 小时前
MySQL实现文档全文搜索,分词匹配多段落重排展示,知识库搜索原理分享
数据库·mysql