使用springboot redis RabbitMQ实现商品秒杀

前言

在电商项目中,会经常出现各种活动,如限时秒杀,秒杀活动的并发量特别高,会导致访问变慢、商品超卖等问题。学会以下知识将会解决这些部分问题。

使用技术

开发工具:idea、navicat、RedisDesktopManager
开发环境:JDK1.8、MySql8.0、Maven、SpringBoot、redis、RabbitMQ
点击将会进入相关安装教程

创建表

1、order(订单)表:
在这里插入图片描述2、commodity_inventory(商品库存)表
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191224145033498.png

配置pom.xml


    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintagegroupId>
                    <artifactId>junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-jpaartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-lang3artifactId>
            <version>3.8.1version>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-amqpartifactId>
        dependency>
        <dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            <version>0.7.0version>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.1.0version>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-txartifactId>
        dependency>
        <dependency>
            <groupId>tk.mybatisgroupId>
            <artifactId>mapper-spring-boot-starterartifactId>
            <version>2.0.3-beta1version>
        dependency>
        <dependency>
            <groupId>tk.mybatisgroupId>
            <artifactId>mapperartifactId>
            <version>4.0.0version>
        dependency>
    dependencies>

配置application.yml

spring:
  devtools:
   restart:
    enabled: false
  datasource:
   username: root
   password: root
   url: jdbc:mysql://localhost:3306/seconds_kill?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true
   driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
   host: localhost
   port: 6379
   password: 123456
   jedis:
    pool:
     max-active: 1024
     max-wait: -1s
     max-idle: 200
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
server:
  port: 1224

创建实体类

1、order


import java.io.Serializable;

public class Order implements Serializable {
    private Integer id;
    private String cname;
    private String userName;
    private Integer ciId;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getCiId() {
        return ciId;
    }

    public void setCiId(Integer ciId) {
        this.ciId = ciId;
    }
}

2、CommodityInventory


import java.io.Serializable;

public class CommodityInventory implements Serializable {
    private Integer id;
    private String cName;
    private Integer inventory;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getcName() {
        return cName;
    }

    public void setcName(String cName) {
        this.cName = cName;
    }

    public Integer getInventory() {
        return inventory;
    }

    public void setInventory(Integer inventory) {
        this.inventory = inventory;
    }
}

Mapper

1、OrderMapper

import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;

public interface GenericMapper<T> extends Mapper<T>, MySqlMapper<T> {
}

2、CommodityInventoryMapper


import com.example.demo.base.GenericMapper;
import com.example.demo.pojo.CommodityInventory;

public interface CommodityInventoryMapper extends GenericMapper<CommodityInventory> {
    void insertCommodityInventory(CommodityInventory commodityInventory);
}

base


import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;

public interface GenericMapper<T> extends Mapper<T>, MySqlMapper<T> {
}

消息队列

MyRabbitMQConfig:


import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class MyRabbitMQConfig {

    //库存交换机
    public static final String STORY_EXCHANGE = "STORY_EXCHANGE";

    //订单交换机
    public static final String ORDER_EXCHANGE = "ORDER_EXCHANGE";

    //库存队列
    public static final String STORY_QUEUE = "STORY_QUEUE";

    //订单队列
    public static final String ORDER_QUEUE = "ORDER_QUEUE";

    //库存路由键
    public static final String STORY_ROUTING_KEY = "STORY_ROUTING_KEY";

    //订单路由键
    public static final String ORDER_ROUTING_KEY = "ORDER_ROUTING_KEY";
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
    //创建库存交换机
    @Bean
    public Exchange getStoryExchange() {
        return ExchangeBuilder.directExchange(STORY_EXCHANGE).durable(true).build();
    }
    //创建库存队列
    @Bean
    public Queue getStoryQueue() {
        return new Queue(STORY_QUEUE);
    }
    //库存交换机和库存队列绑定
    @Bean
    public Binding bindStory() {
        return BindingBuilder.bind(getStoryQueue()).to(getStoryExchange()).with(STORY_ROUTING_KEY).noargs();
    }
    //创建订单队列
    @Bean
    public Queue getOrderQueue() {
        return new Queue(ORDER_QUEUE);
    }
    //创建订单交换机
    @Bean
    public Exchange getOrderExchange() {
        return ExchangeBuilder.directExchange(ORDER_EXCHANGE).durable(true).build();
    }
    //订单队列与订单交换机进行绑定
    @Bean
    public Binding bindOrder() {
        return BindingBuilder.bind(getOrderQueue()).to(getOrderExchange()).with(ORDER_ROUTING_KEY).noargs();
    }
}

Redis

RedisConfig:


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    // 配置redis得配置详解
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

开始秒杀

1、MQOrderService


import com.example.demo.config.MyRabbitMQConfig;
import com.example.demo.pojo.Order;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class MQOrderService {
    private static Logger log = LoggerFactory.getLogger(MQOrderService.class);
    @Autowired
    private OrderService orderService;
    /**
     * 监听订单消息队列,并消费
     *
     * @param order
     */
    @RabbitListener(queues = MyRabbitMQConfig.ORDER_QUEUE)
    public void createOrder(Order order) {
        log.info("收到订单消息,订单用户为:{},商品名称为:{}", order.getUserName(), order.getCname());
        /**
         * 调用数据库orderService创建订单信息
         */
        orderService.createOreder(order);
    }
}

2、MQCommodityInventoryService


import com.example.demo.config.MyRabbitMQConfig;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class MQCommodityInventoryService {
    private static Logger log = LoggerFactory.getLogger(MQStockService.class);
    @Autowired
    private CommodityInventoryService commodityInventoryService;
    /**
     * 监听库存消息队列,并消费
     * @param stockName
     */
    @RabbitListener(queues = MyRabbitMQConfig.STORY_QUEUE)
    public void decrByStock(String stockName) {
        log.info("库存消息队列收到的消息商品信息是:{}", stockName);
        /**
         * 调用数据库service给数据库对应商品库存减一
         */
        commodityInventoryService.decrByStock(stockName);
    }
}

Service

1、CommodityInventoryServiceImpl


import com.example.demo.mapper.CommodityInventoryMapper;
import com.example.demo.pojo.CommodityInventory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.entity.Example;
import java.util.List;
import com.example.demo.service.CommodityInventoryService;

@Service
public class CommodityInventoryServiceImpl implements CommodityInventoryService {
    @Autowired
    private CommodityInventoryMapper commodityInventoryMapper;
    // 秒杀商品后减少库存
    @Override
    public void decrByStock(String cName) {
        Example example = new Example(CommodityInventory.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("name", cName);
        List<CommodityInventory> stocks = commodityInventoryMapper.selectByExample(example);
        if (!CollectionUtils.isEmpty(stocks)) {
            CommodityInventory commodityInventory = stocks.get(0);
            commodityInventory.setInventory(commodityInventory.getInventory() - 1);
            commodityInventoryMapper.updateByPrimaryKey(commodityInventory);
        }
    }
    // 秒杀商品前判断是否有库存
    @Override
    public Integer selectByExample(String cName) {
        Example example = new Example(CommodityInventory.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("name", cName);
        List<CommodityInventory> commodityInventories = commodityInventoryMapper.selectByExample(example);
        if (!CollectionUtils.isEmpty(commodityInventories)) {
            return commodityInventories.get(0).getInventory().intValue();
        }
        return 0;
    }
}

2、OrderServiceImpl


import com.example.demo.mapper.OrderMapper;
import com.example.demo.pojo.Order;
import com.example.demo.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Override
    public void createOreder(Order order) {
        orderMapper.insert(order);
    }
}

Controller

SecController :


import com.example.demo.config.MyRabbitMQConfig;
import com.example.demo.pojo.Order;
import com.example.demo.service.CommodityInventoryService;
import com.example.demo.service.MQCommodityInventoryService;
import com.example.demo.service.OrderService;
import com.example.demo.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@Slf4j
public class SecController {
    private static Logger log = LoggerFactory.getLogger(MQCommodityInventoryService.class);
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisService redisService;
    @Autowired
    private OrderService orderService;
    @Autowired
    private CommodityInventoryService commodityInventoryService;
    /**
     * 使用redis+消息队列进行秒杀实现
     *
     * @param username
     * @param cName
     * @return
     */
    @PostMapping( value = "/sec",produces = "application/json;charset=utf-8")
    @ResponseBody
    public String sec(@RequestParam(value = "username") String username, @RequestParam(value = "cName") String cName) {

        log.info("参加秒杀的用户是:{},秒杀的商品是:{}", username, cName);
        String message = null;
        //调用redis给相应商品库存量减一
        Long decrByResult = redisService.decrBy(cName);
        if (decrByResult >= 0) {
            /**
             * 说明该商品的库存量有剩余,可以进行下订单操作
             */
            log.info("用户:{}秒杀该商品:{}库存有余,可以进行下订单操作", username, cName);
            //发消息给库存消息队列,将库存数据减一
            rabbitTemplate.convertAndSend(MyRabbitMQConfig.STORY_EXCHANGE, MyRabbitMQConfig.STORY_ROUTING_KEY, cName);

            //发消息给订单消息队列,创建订单
            Order order = new Order();
            order.setCname(cName);
            order.setUserName(username);
            rabbitTemplate.convertAndSend(MyRabbitMQConfig.ORDER_EXCHANGE, MyRabbitMQConfig.ORDER_ROUTING_KEY, order);
            message = "用户" + username + "秒杀" + cName + "成功";
        } else {
            /**
             * 说明该商品的库存量没有剩余,直接返回秒杀失败的消息给用户
             */
            log.info("用户:{}秒杀时商品的库存量没有剩余,秒杀结束", username);
            message = "用户:"+ username + "商品的库存量没有剩余,秒杀结束";
        }
        return message;
    }

}

多条访问http://localhost:1224/sec?username&cName

可看到控制台的日志打印

使用springboot redis RabbitMQ实现商品秒杀_第1张图片

你可能感兴趣的:(使用springboot redis RabbitMQ实现商品秒杀)