springboot 模拟秒杀 分布式锁 以及 延时取消未支付订单

简易描述秒杀系统的几个主要特点,分布式情况下使用锁,订单超时未支付使用mq的延时队列取消

maven依赖

<dependency>
   <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
    <version>2.6.3version>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.27version>
dependency>
<dependency>
    <groupId>com.baomidougroupId>
    <artifactId>mybatis-plus-boot-starterartifactId>
    <version>3.4.2version>
dependency>
<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druid-spring-boot-starterartifactId>
    <version>1.2.8version>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-amqpartifactId>
    <version>2.5.6version>
dependency>
<dependency>
   <groupId>org.redissongroupId>
   <artifactId>redisson-spring-boot-starterartifactId>
   <version>3.17.0version>
dependency>

application.yml 配置文件

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.34.7:3306/order?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # 下面为连接池的补充设置,应用到上面所有数据源中
      # 初始化大小,最小,最大
      initial-size: 15
      min-idle: 15
      max-active: 50
      # 配置获取连接等待超时的时间
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
  redis:
    port: 6379
    database: 0
    host: 192.168.34.7
  rabbitmq:
    host: 192.168.34.7
    port: 5672
    virtualHost: /
    username: admin
    password: admin
    listener:
      direct:
        #手动ask回复
        acknowledge-mode: manual
#mybatis plus 设置
mybatis-plus:
  mapper-locations: classpath*:com/**/mapper/xml/*.xml
  global-config:
    # 关闭MP3.0自带的banner
    banner: false
    db-config:
      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
      id-type: uuid
      # 默认数据库表下划线命名
      table-underline: true
  configuration:
    #这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

RedisSon RabbitMQ的配置

@Configuration
public class RabbitMQConfig {

    @Bean//支付路由交换机
    public DirectExchange payOrderDirectExchange(){
        return new DirectExchange("payOrderDirectExchange",true,false);
    }

    @Bean//支付队列
    public Queue payOrderQueue(){
        return new Queue("payOrderQueue",true);
    }

    @Bean//支付绑定
    public Binding payOrderBinding(){
        return BindingBuilder.bind(payOrderQueue()).to(payOrderDirectExchange()).with("payOrder");
    }

    @Bean//取消订单路由交换机
    public DirectExchange cancelOrderDirectExchange(){
        return new DirectExchange("cancelOrderDirectExchange",true,false);
    }

    @Bean//取消订单队列,TTL DLX DLK
    public Queue cancelOrderQueue(){
        Map<String,Object> map = new HashMap<>();
        map.put("x-message-ttl",30*60*1000);//过期时间30分钟
        map.put("x-dead-letter-exchange","cancelOrderDeadDirectExchange");
        map.put("x-dead-letter-routing-key","cancelOrderDead");
        return new Queue("cancelOrderQueue",true,false,false,map);
    }

    @Bean//取消订单绑定
    public Binding cancelOrderBinding(){
        return BindingBuilder.bind(cancelOrderQueue()).to(cancelOrderDirectExchange()).with("cancelOrder");
    }

    @Bean//配合取消订单ttl结合的死信路由交换机
    public DirectExchange cancelOrderDeadDirectExchange(){
        return new DirectExchange("cancelOrderDeadDirectExchange",true,false);
    }

    @Bean//配合取消订单ttl结合的死信队列
    public Queue cancelOrderDeadQueue(){
        return new Queue("cancelOrderDeadQueue",true);
    }

    @Bean//配合取消订单ttl结合的死信绑定
    public Binding cancelOrderDeadBinding(){
        return BindingBuilder.bind(cancelOrderDeadQueue()).to(cancelOrderDeadDirectExchange()).with("cancelOrderDead");
    }

}
@Configuration
public class RedisConfig {

    @Bean
    public LettuceConnectionFactory lettuceConnectionFactory(){
        return new LettuceConnectionFactory();
    }
    

    @Bean
    public Redisson redisson(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.34.7:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }


    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        // 配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory());
        // 设置 RedisConnection 工厂。 它就是实现多种 Java Redis 客户端接入的秘密工厂
        // 使用 String 序列化方式,序列化 KEY 。
        redisTemplate.setKeySerializer(RedisSerializer.string());
        // 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
        redisTemplate.setValueSerializer(RedisSerializer.string());
        return redisTemplate;
    }
}

模拟分布式情况下秒杀订单生成

    @Autowired
    private OrderService orderService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private Redisson redisson;


    @PostMapping("/saveOrder")
    @Transactional
    public String saveOrder(@RequestBody Order order){
        String shopId = order.getShopId();
        //使用redisson加锁
        RLock lock = redisson.getLock(shopId);
        lock.lock();
        String id;
        try {
            //拿到了锁,库存减1,如果库存是0,就直接return,防止超卖
            //代码省略。。。。。。。。。
            id = UUID.randomUUID().toString();
            order.setOrderId(id);
            order.setCreateTime(new Date());
            orderService.save(order);
            //订单超时未支付自动取消,通过rabbitmq的TTL加死信队列实现
            rabbitTemplate.convertAndSend("cancelOrderDirectExchange","cancelOrder",id);
        } finally {
            lock.unlock();
        }
        return id;
    }

	/**
     * 取消订单的监听消费
     * @param message
     * @param channel
     * @throws Exception
     */
    @RabbitListener(queues = "cancelOrderDeadQueue")
    @Transactional
    public void cancelOrderListener(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String body = new String(message.getBody(), "UTF-8");
        try {
            //订单超时未支付取消
            //.....代码省略
            log.info("订单编号 {} 已取消 ",body);
            channel.basicAck(deliveryTag,false);
        }catch (Exception e){
            //手动ask回复,可以配合缓存增加重试次数
            channel.basicReject(deliveryTag,false);
            throw new RuntimeException("cancelOrderListener Exception",e);
        }
    }

模拟订单支付后操作

    @GetMapping("/pay")
    public String pay(String id){
        log.info("{} 订单支付了",id);
        //mq修改订单状态,并发送物流等信息
        rabbitTemplate.convertAndSend("payOrderDirectExchange","payOrder",id);
        return "success";
    }
    /**
     * 保存订单,通知物流
     * @param message
     * @param channel
     * @throws Exception
     */
    @RabbitListener(queues = "payOrderQueue")
    @Transactional
    public void payOrderListener(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String body = new String(message.getBody(), "UTF-8");
        try {
            //1.订单状态修改
            //2.发送物流信息
            //..............代码忽略
            channel.basicAck(deliveryTag,false);
        }catch (Exception e){
            //手动ask回复,可以配合缓存增加重试次数
            channel.basicReject(deliveryTag,false);
            throw new RuntimeException("payOrderListener Exception",e);
        }
    }

你可能感兴趣的:(SpringBoot,spring,boot,分布式,java)