SpringBoot +Redis +RabbitMQ 實作高并發限時秒殺
所謂秒殺,從業務角度看,是短時間內多個用戶“爭搶”資源,這里的資源在大部分秒殺場景里是商品;將業務抽象,技術角度看,秒殺就是多個執行緒對資源進行操作,所以實作秒殺,就必須控制執行緒對資源的爭搶,既要保證高效并發,也要保證操作的正確,
文章目錄
- SpringBoot +Redis +RabbitMQ 實作高并發限時秒殺
- 開發環境
- 測驗環境
- 環境安裝
- 安裝RabbitMQ
- 安裝Redis
- 安裝 Jmeter測驗工具
- maven安裝
- 代碼模塊
- 資料庫設計
- 商品庫存表:stock
- 秒殺訂單表:t_order
- pom.xml
- application.yml
- logback-spring.xml
- 物體類
- 服務層
- 介面實作類
- 配置rabbitmq的實作方式以及redis的實作方式
- 工具類
- 控制層
- 啟動專案
- 啟動完成,redis中上架10個貨物,
- 打開JMeter并發環境測驗
- log日志
開發環境
SpringBoot+mysql+maven+JDK8+RabbitMQ+Redis
測驗環境
Jmeter測驗工具
環境安裝
安裝RabbitMQ
docker安裝:https://blog.csdn.net/qq_33612228/article/details/103732890
windows安裝:https://blog.csdn.net/m0_37034294/article/details/82839494
需要安裝mq的可視化工具

安裝Redis
docker安裝:https://blog.csdn.net/qq_33612228/article/details/10360918
windows安裝:https://blog.csdn.net/qq_39135287/article/details/82686837
springboot整合redis:https://blog.csdn.net/qq_33612228/article/details/103700543
redis需要安裝客戶端,方便資料查看,

安裝 Jmeter測驗工具
windows安裝:https://blog.csdn.net/liuyanh2006/article/details/82494548
mac安裝: https://blog.csdn.net/zgy_boke/article/details/102689531
mac安裝需要的Homebrew:https://blog.csdn.net/o_o814222198/article/details/120185851?spm=1001.2014.3001.5502
maven安裝
mac安裝:https://www.jianshu.com/p/191685a33786
window安裝:https://www.cnblogs.com/eagle6688/p/7838224.html
代碼模塊
資料庫設計
商品庫存表:stock
CREATE TABLE `stock` (
`id` varchar(64) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`stock` varchar(255) DEFAULT NULL,
`remarks` varchar(255) NOT NULL DEFAULT '' COMMENT '備注',
`update_date` datetime DEFAULT NULL COMMENT '最后更新時間',
`create_date` datetime DEFAULT NULL COMMENT '創建時間',
`update_by` varchar(64) NOT NULL DEFAULT '',
`create_by` varchar(64) NOT NULL DEFAULT '',
`del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '0正常,1洗掉',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='商品庫存表';
秒殺訂單表:t_order
CREATE TABLE `t_order` (
`id` varchar(64) NOT NULL,
`order_name` varchar(255) DEFAULT NULL,
`order_user` varchar(255) DEFAULT NULL,
`remarks` varchar(255) NOT NULL DEFAULT '' COMMENT '備注',
`update_date` datetime DEFAULT NULL COMMENT '最后更新時間',
`create_date` datetime DEFAULT NULL COMMENT '創建時間',
`update_by` varchar(64) NOT NULL DEFAULT '',
`create_by` varchar(64) NOT NULL DEFAULT '',
`del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '0正常,1洗掉',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='秒殺訂單表';
pom.xml
- 集成MybatisPlus
- fastjosn依賴
- Druid
- MyBatis增強工具
- lombok
- websocket
- 前端頁面通過thymeleaf渲染
- 工具類包
- swagger-ui
- redis
- RabbitMQ
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>集成MybatisPlus</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<druid.version>1.1.14</druid.version>
<mysql-connector-java.version>8.0.11</mysql-connector-java.version>
<mybatis-plus-boot-starter.version>3.1.1</mybatis-plus-boot-starter.version>
<mybatis-plus-generator.version>3.1.1</mybatis-plus-generator.version>
<lombok.version>1.18.0</lombok.version>
<swagger.version>2.9.2</swagger.version>
<fastjson.version>1.2.47</fastjson.version>
<commons-text.version>1.6</commons-text.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--依賴管理-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.8.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mysql依賴-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
<!--阿里巴巴fastjosn依賴-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- MyBatis增強工具-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus-generator.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 前端頁面通過thymeleaf渲染 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--工具類包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons-text.version}</version>
</dependency>
<!--swagger-ui-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
- 資料源
- 埠號
- redis
- mq
- 掃描mapper檔案
- 檔案上傳
- 頁面模板
- log日志配置
spring:
datasource:
url: jdbc:mysql://182.92.210.212:3306/order_db?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
username: root
password: ***
# 使用Druid資料源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
filters: stat
maxActive: 20
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
data:
redis:
repositories:
enabled: false
redis:
database: 0 # redis資料庫索引(默認為0),我們使用索引為其他(0-15)的資料庫,避免和其他資料庫沖突
host: 182.92.210.212
port: 6379
password: ***
rabbitmq: #mq配置
host: localhost
port: 5672
username: guest
password: guest
resources:
static-locations: classpath:/static #這個配置項告訴springboot去哪找資源 最關鍵的地方
profiles:
active: dev
servlet:
multipart:
enabled: true
max-file-size: 10MB #允許上傳的檔案大小
max-request-size: 10MB
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
mode: HTML5
context-type: text/html
mvc:
view:
prefix: /
suffix: .html
static-path-pattern: /** #這個配置告訴springboot,應該以什么方式去找資源,默認是/*
mybatis-plus:
mapper-locations: classpath*:com.example.demo.mapper/*.xml
global-config:
db-config:
id-type: uuid
field-strategy: not_null
refresh: true
configuration:
map-underscore-to-camel-case: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
server:
port: 8090
logging:
config: classpath:logback-spring.xml
logback-spring.xml
- 各級別日志分開列印,一個等級一個日志檔案,
- 每天存檔,
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>logback</contextName>
<property name="path" value="./log"/>
<property name="maxHistory" value="30"/>
<property name="maxFileSize" value="50MB"/>
<!--輸出sql陳述句-->
<logger name="com.example.demo.mapper" level="debug"/>
<!--輸出到控制臺-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>-->
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!--debug_file-->
<appender name="debug_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${path}/logback_debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天一歸檔 -->
<fileNamePattern>${path}/logback_debug.log.%d{yyyy-MM-dd}-%i.zip</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--info_file-->
<appender name="info_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${path}/logback_info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天一歸檔 -->
<fileNamePattern>${path}/logback_info.log.%d{yyyy-MM-dd}-%i.zip</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--warn_file-->
<appender name="warn_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${path}/logback_warn.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天一歸檔 -->
<fileNamePattern>${path}/logback_warn.log.%d{yyyy-MM-dd}-%i.zip</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--error_file-->
<appender name="error_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${path}/logback_error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天一歸檔 -->
<fileNamePattern>${path}/logback_error.log.%d{yyyy-MM-dd}-%i.zip</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="debug_file"/>
<appender-ref ref="info_file"/>
<appender-ref ref="warn_file"/>
<appender-ref ref="error_file"/>
</root>
</configuration>
物體類
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author dispark
* @version 1.0
* @Description: 訂單
* @date 2019-12-27 15:54
*/
@TableName("t_order")
@Data
public class Order extends BasePlusEntity<Order>{
private static final long serialVersionUID = 1L;
/**
* 訂單名稱
*/
@TableField("order_name")
private String orderName;
/**
* 訂單用戶
*/
@TableField("order_user")
private String orderUser;
}
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author dispark
* @version 1.0
* @Description: 存貨
* @date 2019-12-27 15:54
*/
@TableName("stock")
@Data
public class Stock extends BasePlusEntity<Stock>{
private static final long serialVersionUID = 1L;
/**
* 產品名稱
*/
@TableField("name")
private String name;
/**
* 存貨
*/
@TableField("stock")
private String stock;
}
服務層
package com.example.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.entity.Order;
/**
* @author dispark
* @version 1.0
* @Description: 訂單服務層
* @date 2019-12-27 15:54
*/
public interface OrderService extends IService<Order> {
/**
* 訂單保存
* @param order 物體
*/
void saveOrder(Order order);
}
package com.example.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.entity.Stock;
/**
* @author dispark
* @version 1.0
* @Description: 存貨服務層
* @date 2019-12-27 15:54
*/
public interface StockService extends IService<Stock> {
/**
* 秒殺商品后-減少庫存
* @param name 商品名稱
*/
void decrByStock(String name);
/**
* 秒殺商品前判斷是否有庫存
* @param name 商品名稱
* @return
*/
Integer selectByName(String name);
/**
* 實作純資料庫操作實作秒殺操作
* @param userName 用戶名稱
* @param stockName 商品名稱
* @return String
*/
String secDataBase(String userName,String stockName);
}
介面實作類
package com.example.demo.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.Order;
import com.example.demo.exception.SXException;
import com.example.demo.exception.ServiceExceptionEnum;
import com.example.demo.mapper.OrderMapper;
import com.example.demo.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author dispark
* @version 1.0
* @Description: 訂單實作層
* @date 2019-12-27 15:54
*/
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
@Autowired
private OrderMapper orderMapper;
/**
* 訂單保存
* @param order 物體
*/
@Override
public void saveOrder(Order order) {
if(orderMapper.insert(order) <= 0){
throw new SXException(ServiceExceptionEnum.DATA_INSERT_EXCEPTION);
}
}
}
package com.example.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.Order;
import com.example.demo.entity.Stock;
import com.example.demo.mapper.StockMapper;
import com.example.demo.service.OrderService;
import com.example.demo.service.StockService;
import com.example.demo.utils.IdGenerate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
/**
* @author dispark
* @version 1.0
* @Description: 存貨實作層
* @date 2019-12-27 15:54
*/
@Service
@Slf4j
public class StockServiceImpl extends ServiceImpl<StockMapper, Stock> implements StockService {
@Autowired
private StockMapper stockMapper;
@Autowired
private OrderService orderService;
/**
* 秒殺商品后-減少庫存
* @param name 商品名稱
*/
@Override
public void decrByStock(String name) {
List<Stock> stockList = stockMapper.selectList(new QueryWrapper<Stock>().lambda().eq(Stock::getName, name));
stockList.forEach(stock -> {
//貨物購買
stock.setStock(String.valueOf(Integer.parseInt(stock.getStock())-1));
stockMapper.updateById(stock);
});
}
/**
* 秒殺商品前判斷是否有庫存
* @param name 商品名稱
* @return
*/
@Override
public Integer selectByName(String name) {
//查詢存貨數量
Integer stockNum = 0;
List<Stock> stockList = stockMapper.selectList(new QueryWrapper<Stock>().lambda().eq(Stock::getName, name));
if(stockList.size() > 0){
stockNum = Integer.parseInt(stockList.get(0).getStock());
}
return stockNum;
}
/**
* 實作純資料庫操作實作秒殺操作
* @param userName 用戶名稱
* @param stockName 商品名稱
* @return String
*/
@Override
public String secDataBase(String userName, String stockName) {
log.info("參加秒殺的用戶是:{},秒殺的商品是:{}", userName, stockName);
String message = null;
//查找該商品庫存
Integer stockCount = selectByName(stockName);
log.info("用戶:{}參加秒殺,當前商品庫存量是:{}", userName, stockCount);
if (stockCount > 0) {
/**
* 還有庫存,可以進行繼續秒殺,庫存減一,下訂單
*/
//1、庫存減一
decrByStock(stockName);
//2、下訂單
Order order = new Order();
order.setOrderUser(userName);
order.setOrderName(stockName);
order.setCreateBy(userName);
order.setCreateDate(new Date());
order.setUpdateBy(userName);
order.setUpdateDate(new Date());
order.setDelFlag("0");
order.setId(IdGenerate.generateId());
orderService.saveOrder(order);
log.info("用戶:{}.參加秒殺結果是:成功", userName);
message = userName + "參加秒殺結果是:成功";
} else {
log.info("用戶:{}.參加秒殺結果是:秒殺已經結束", userName);
message = userName + "參加秒殺活動結果是:秒殺已經結束";
}
return message;
}
}
配置rabbitmq的實作方式以及redis的實作方式
- 訂單的消費佇列
package com.example.demo.service.impl;
import com.example.demo.config.RabbitMqConfig;
import com.example.demo.entity.Order;
import com.example.demo.service.OrderService;
import com.example.demo.utils.IdGenerate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* @author dispark
* @version 1.0
* @Description: MQ訂單實作層
* @date 2019-12-27 15:54
*/
@Service
@Slf4j
public class MQOrderServiceImpl {
@Autowired
private OrderService orderService;
/**
* MQ監聽訂單訊息佇列,并消費
* @param order
*/
@RabbitListener(queues = RabbitMqConfig.ORDER_QUEUE)
public void saveOrder(Order order) {
log.info("收到訂單訊息,訂單用戶為:{},商品名稱為:{}", order.getOrderUser(), order.getOrderName());
/**
* 呼叫資料庫orderService創建訂單資訊
*/
order.setCreateBy(order.getOrderUser());
order.setCreateDate(new Date());
order.setUpdateBy(order.getOrderUser());
order.setUpdateDate(new Date());
order.setDelFlag("0");
order.setId(IdGenerate.generateId());
orderService.saveOrder(order);
}
}
- 庫存得消費佇列
package com.example.demo.service.impl;
import com.example.demo.config.RabbitMqConfig;
import com.example.demo.entity.Order;
import com.example.demo.service.StockService;
import com.example.demo.utils.IdGenerate;
import com.example.demo.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* @author dispark
* @version 1.0
* @Description: MQ存貨實作層
* @date 2019-12-27 15:54
*/
@Service
@Slf4j
public class MQStockServiceImpl {
@Autowired
private StockService stockService;
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private RedisUtil redisUtil;
/**
* 秒殺商品后-減少庫存
*
* @param name 商品名稱
* @author dispark
* @date 2021/9/10 9:51 上午
*/
@RabbitListener(queues = RabbitMqConfig.STORY_QUEUE)
public void decrByStock(String name) {
log.info("庫存訊息佇列收到的訊息商品資訊是:{}", name);
// 呼叫資料庫service給資料庫對應商品庫存減一
stockService.decrByStock(name);
}
/**
* 使用redis+訊息佇列進行秒殺實作
*
* @param userName 用戶
* @param stockName 商品
* @return {@link java.lang.String}
* @author dispark
* @date 2021/9/10 9:51 上午
*/
public String secKill(String userName, String stockName) {
log.info("參加秒殺的用戶是:{},秒殺的商品是:{}", userName, stockName);
String message = "";
// 1.呼叫redis給相應商品庫存量減1
Long decrByResult = redisUtil.decrBy(stockName);
if (decrByResult >= 0) {
// 2.說明該商品的庫存量有剩余,可以進行下訂單操作
log.info("用戶:{}秒殺該商品:{}庫存有余,可以進行下訂單操作", userName, stockName);
// 3.發訊息給庫存訊息佇列,將庫存資料減一
rabbitTemplate.convertAndSend(RabbitMqConfig.STORY_EXCHANGE, RabbitMqConfig.STORY_ROUTING_KEY, stockName);
// 4.發訊息給訂單訊息佇列,創建訂單
Order order = new Order();
order.setOrderName(stockName);
order.setOrderUser(userName);
rabbitTemplate.convertAndSend(RabbitMqConfig.ORDER_EXCHANGE, RabbitMqConfig.ORDER_ROUTING_KEY, order);
message = "用戶" + userName + "秒殺" + stockName + "成功";
} else {
// 5.說明該商品的庫存量沒有剩余,直接回傳秒殺失敗的訊息給用戶
log.info("用戶:{}秒殺時商品的庫存量沒有剩余,秒殺結束", userName);
message = "用戶:" + userName + "商品的庫存量沒有剩余,秒殺結束";
}
return message;
}
}
工具類
RabbitMQConfig插件配置
package com.example.demo.config;
import org.springframework.amqp.core.*;
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;
/**
* @author dispark
* @version 1.0
* @Description: RabbitMQConfig插件配置
* @date 2019-12-27 16:23
*/
@Configuration
public class RabbitMqConfig {
/**
* 庫存交換機
*/
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();
}
/**
* 創建庫存交換機
*
* @return
*/
@Bean
public Exchange getStoryExchange() {
return ExchangeBuilder.directExchange(STORY_EXCHANGE).durable(true).build();
}
/**
* 創建庫存佇列
*
* @return
*/
@Bean
public Queue getStoryQueue() {
return new Queue(STORY_QUEUE, true);
}
/**
* 庫存交換機和庫存佇列系結
*
* @return
*/
@Bean
public Binding bindStory() {
return BindingBuilder.bind(getStoryQueue()).to(getStoryExchange()).with(STORY_ROUTING_KEY).noargs();
}
/**
* 創建訂單佇列
*
* @return
*/
@Bean
public Queue getOrderQueue() {
return new Queue(ORDER_QUEUE);
}
/**
* 創建訂單交換機
*
* @return
*/
@Bean
public Exchange getOrderExchange() {
return ExchangeBuilder.directExchange(ORDER_EXCHANGE).durable(true).build();
}
/**
* 訂單佇列與訂單交換機進行系結
*
* @return
*/
@Bean
public Binding bindOrder() {
return BindingBuilder.bind(getOrderQueue()).to(getOrderExchange()).with(ORDER_ROUTING_KEY).noargs();
}
}
redis 快取配置
package com.example.demo.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @ClassName: RedisCacheConfig
* @Description: redis 快取配置;
* 注意:RedisCacheConfig這里也可以不用繼承:CachingConfigurerSupport,
* 也就是直接一個普通的Class就好了 這里主要我們之后要重新實作
* key的生成策略,只要這里修改KeyGenerator,其它位置不用修改就生效了,
* 普通使用普通類的方式的話,那么在使用@Cacheable的時候還需要指定KeyGenerator的名稱;
* 這樣編碼的時候比較麻煩,
* @author: dispark
* @date: 2019年12月25日 下午3:30:19
*/
@Configuration
@EnableCaching // 啟用快取,這個注解很重要;
public class RedisCacheConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 快取配置初始化一個cacheManager
* @param connectionFactory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory).build();
return redisCacheManager;
}
/**
* 防止redis入庫序列化亂碼的問題
* @param redisConnectionFactory
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用Jackson2JsonRedisSerialize 替換默認序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 設定value的序列化規則和 key的序列化規則
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 重寫hashOperations
* @param redisTemplate
* @return
*/
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
/**
* 重寫listOperations
* @param redisTemplate
* @return
*/
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
/**
* redisMessageListenerContainer
* @return
*/
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer() {
RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
return redisMessageListenerContainer;
}
}
package com.example.demo.utils;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* @author LST
* @version 1.0
* @Description: redis工具類
* @date 2019-12-25 16:52
*/
@Component
public class RedisUtil<T> {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private HashOperations<String, String, Object> hashOperations;
@Autowired
private ListOperations<String, Object> listOperations;
/**
* 默認過期時長,單位:秒/ 三十分鐘
*/
public final static long DEFAULT_EXPIRE = 180 * 10;
/**
* 不設定過期時長
*/
public final static long NOT_EXPIRE = -1;
/**
* 普通快取放入
*
* @param key 鍵
* @param value 值
*/
public void setValue(String key, T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 普通快取放入并設定時間
*
* @param key 鍵
* @param value 值
* @param time 時間(秒) time要大于0 如果time小于等于0 將設定無限期
*/
public void setValue(String key, T value, Long time) {
setValue(key, value);
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
}
/**
* 普通快取放入并設定時間和單位
*
* @param key 鍵
* @param value 值
* @param time 時間(秒) time要大于0 如果time小于等于0 將設定無限期
* @param unit 快取設定的時間單位
*/
public void setValue(String key, T value, Long time, TimeUnit unit) {
setValue(key, value);
redisTemplate.expire(key, time, unit);
}
/**
* 普通快取獲取
*
* @param key 鍵
* @return 值
*/
public T getValue(String key) {
ValueOperations<String, T> valueOperations = redisTemplate.opsForValue();
return valueOperations.get(key);
}
/**
* 洗掉快取
*
* @param key 鍵
*/
public void deleteValue(String key) {
redisTemplate.delete(key);
}
/**
* 判斷key是否存在
*
* @param key 鍵
* @return true 存在 false 不存在
*/
public boolean exists(String key) {
if (getValue(key) == null) {
return false;
} else {
return true;
}
}
/**
* 放入一個map物件
*
* @param key 鍵
* @param map 物件
* @return true 存在 false 不存在
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
return false;
}
}
/**
* HashSet 并設定時間
*
* @param key 鍵
* @param map 對應多個鍵值
* @param time 時間(秒)
* @return true成功 false失敗
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* Map快取獲取
*
* @param key 鍵
* @return map
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 重復提交
*
* @param key 鍵
* @return
*/
public long repeatSubmit(String key) {
ValueOperations<String, Integer> valueOperations = redisTemplate.opsForValue();
long ret = valueOperations.increment(key, 1);
redisTemplate.expire(key, 10L, TimeUnit.SECONDS);
return ret;
}
/**
* 將list放入快取
*
* @param key 鍵
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 獲取list快取的內容
*
* @param key 鍵
* @param start 開始
* @param end 結束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
return null;
}
}
/**
* 洗掉hash值
*
* @param key key
* @param hashKeys hashKeys
*/
public void delHash(String key, String... hashKeys) {
Arrays.stream(hashKeys).forEach(hashKey -> hashOperations.delete(key, hashKeys));
}
/**
* 先根據key洗掉
* 然后在把list放入快取中
*
* @param key key
* @param getHashKeyFunction 獲取hashKey值的方法
* @param list list物體類
* @param expire 時間
*/
public <T> void putHashList(String key, Function<T, String> getHashKeyFunction, List<T> list, Long expire) {
putHashList(key, getHashKeyFunction, list);
if (expire != NOT_EXPIRE) {
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
}
/**
* 先根據key洗掉
* 然后在把list放入快取中
*
* @param key 鍵
* @param getHashKeyFunction
* @param list
* @param <T>
*/
public <T> void putHashList(String key, Function<T, String> getHashKeyFunction, List<T> list) {
deleteValue(key);
list.stream().filter(distinctByValue(getHashKeyFunction)).forEach(model -> putHashModel(key, getHashKeyFunction, model));
}
/**
* 將物體類放到Hash里面,并設定時間
*
* @param key key
* @param getHashKeyFunction 獲取hashKey值的方法
* @param model 物體類
* @param <T> T
*/
public <T> void putHashModel(String key, Function<T, String> getHashKeyFunction, T model, Long expire) {
putHashModel(key, getHashKeyFunction, model);
if (expire != NOT_EXPIRE) {
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
}
/**
* 將物體類放到Hash里面
*
* @param key key
* @param getHashKeyFunction 獲取hashKey值的方法
* @param model 物體類
* @param <T> T
*/
public <T> void putHashModel(String key, Function<T, String> getHashKeyFunction, T model) {
hashOperations.put(key, getHashKeyFunction.apply(model), JSONObject.toJSON(model).toString());
}
/**
* 根據有效時間,key 獲取List
*
* @param key key
* @param clazz clazz
* @param <T> T
* @return List<T>
*/
public <T> List<T> getHashList(String key, Class<T> clazz, Long expire) {
if (expire != NOT_EXPIRE) {
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
return getHashList(key, clazz);
}
/**
* 獲取全部list
*
* @param key
* @param clazz
* @param <T>
* @return
*/
public <T> List<T> getHashList(String key, Class<T> clazz) {
Optional<List<Object>> objectList = Optional.ofNullable(hashOperations.values(key));
return objectList.map(objects -> objects.stream()
.map(t -> JSONObject.parseObject(JSONObject.toJSON(t).toString(), clazz))
.collect(Collectors.toList())).orElse(null);
}
/**
* 根據 key 和 hashKey 獲取值
*
* @param key key
* @param hashKey hashKey
* @param clazz clazz
* @param <T> T
* @return T
*/
public <T> T getHashObject(String key, String hashKey, Class<T> clazz) {
Optional<Object> object = Optional.ofNullable(hashOperations.get(key, hashKey));
if (object.isPresent()) {
return JSONObject.parseObject(JSONObject.toJSON(object).toString(), clazz);
}
return null;
}
/**
* 去除重復
*
* @param keyExtractor
* @param <T>
* @return
*/
public static <T> Predicate<T> distinctByValue(Function<? super T, ?> keyExtractor) {
ConcurrentHashMap<Object, Boolean> map = new ConcurrentHashMap<>();
return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
/**
* 判斷key是否存在
*
* @param key 鍵
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
return false;
}
}
/**
* 獲取有效全部list
*
* @param key key
* @param clazz clazz
* @param <T> T
* @param expire 超時時間
* @return return
*/
public <T> List<T> getList(String key, Class<T> clazz, Long expire) {
if (expire != NOT_EXPIRE) {
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
return getList(key, clazz);
}
/**
* 獲取全部list
*
* @param key
* @param clazz
* @param <T>
* @return
*/
public <T> List<T> getList(String key, Class<T> clazz) {
Optional<List<Object>> list = Optional.ofNullable(listOperations.range(key, 0, listOperations.size(key)));
return list.map(objects -> objects.stream()
.map(t -> JSONObject.parseObject(JSONObject.toJSON(t).toString(), clazz))
.collect(Collectors.toList())).orElse(null);
}
/**
* 先根據key洗掉
* 然后在把list放入快取中
*
* @param key 鍵
* @param getValueFunction 獲取value值的方法
* @param list list
* @param <T> T
* @param expire 超時時間
*/
public <T> void leftPushList(String key, List<T> list, Function<T, String> getValueFunction, Long expire) {
leftPushList(key, list, getValueFunction);
if (expire != NOT_EXPIRE) {
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
}
/**
* 物體類去重放入listOperations
*
* @param key
* @param list
* @param getValueFunction 獲取value值的方法
* @param <T>
*/
public <T> void leftPushList(String key, List<T> list, Function<T, String> getValueFunction) {
deleteValue(key);
list.stream().filter(distinctByValue(getValueFunction)).forEach(model -> leftPushModel(key, model, getValueFunction));
}
/**
* 物體類放入
*
* @param key key
* @param model model
* @param getValueFunction 獲取value值的方法
* @return <T>
* @author dispark
* @date 2021/9/10 9:54 上午
*/
public <T> void leftPushModel(String key, T model, Function<T, String> getValueFunction) {
listOperations.leftPush(key, getValueFunction.apply(model));
}
/**
* 對指定key的鍵值減1
*
* @param key key
* @return {@link java.lang.Long}
* @author dispark
* @date 2021/9/10 9:53 上午
*/
public Long decrBy(String key) {
return redisTemplate.opsForValue().decrement(key);
}
}
控制層
redis+訊息佇列進行秒殺實作
package com.example.demo.controller;
import com.example.demo.result.RestResponse;
import com.example.demo.result.ResultGenerator;
import com.example.demo.service.StockService;
import com.example.demo.service.impl.MQStockServiceImpl;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author dispark
* @version 1.0
* @Description: 秒殺
* @date 2019-12-28 20:59
*/
@RestController
@Api(value = "SecKillController", tags = "秒殺控制層")
@Slf4j
public class SecKillController {
@Autowired
private MQStockServiceImpl mqStockService;
@Autowired
private StockService stockService;
/**
* 使用redis+訊息佇列進行秒殺實作
*
* @param userName 用戶名稱
* @param stockName 商品名稱
* @return {@link com.example.demo.result.RestResponse}
* @author dispark
* @date 2021/9/10 9:47 上午
*/
@PostMapping(value = "sec-kill", produces = "application/json")
@ApiOperation(value = "redis+訊息佇列進行秒殺實作", notes = "redis+訊息佇列進行秒殺實作", produces = "application/json")
public RestResponse secKill(@RequestParam(value = "userName") String userName, @RequestParam(value = "stockName") String stockName) {
return ResultGenerator.genSuccessResult(mqStockService.secKill(userName, stockName));
}
/**
* 實作純資料庫操作實作秒殺操作
*
* @param userName 用戶名稱
* @param stockName 商品名稱
* @return {@link com.example.demo.result.RestResponse}
* @author dispark
* @date 2021/9/10 9:47 上午
*/
@PostMapping(value = "sec-data-base", produces = "application/json;")
@ApiOperation(value = "實作純資料庫操作實作秒殺操作", notes = "實作純資料庫操作實作秒殺操作", produces = "application/json")
public RestResponse secDataBase(@RequestParam(value = "userName") String userName, @RequestParam(value = "stockName") String stockName) {
return ResultGenerator.genSuccessResult(stockService.secDataBase(userName, stockName));
}
}
啟動專案
啟動完成,redis中上架10個貨物,

打開JMeter并發環境測驗
測驗計劃右鍵,添加一個執行緒組

給這個執行緒組的數量為40,這個執行緒組的作用就是模擬40個用戶發送請求,去秒殺;然后再在執行緒組右鍵,添加一個Http請求,這個就是我們用來發送請求的組件了



這個請求唯一要說得就是,隨機引數了,因為用戶名肯定不可能給40個相同得名字,這邊我們利用JMeter給用戶名得值為亂數
點擊上方得白色小書本,選擇random,1-99得亂數,

開始測驗

log日志
2021-09-13 17:35:43,304 INFO [http-nio-8090-exec-10] o.a.c.c.C.[Tomcat].[localhost].[/] [DirectJDKLog.java : 173] Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-09-13 17:35:43,308 INFO [http-nio-8090-exec-10] o.s.web.servlet.DispatcherServlet [FrameworkServlet.java : 524] Initializing Servlet 'dispatcherServlet'
2021-09-13 17:35:43,335 INFO [http-nio-8090-exec-10] o.s.web.servlet.DispatcherServlet [FrameworkServlet.java : 546] Completed initialization in 26 ms
2021-09-13 17:35:43,401 INFO [http-nio-8090-exec-10] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:10,秒殺的商品是:watch
2021-09-13 17:35:43,404 INFO [http-nio-8090-exec-7] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:29,秒殺的商品是:watch
2021-09-13 17:35:43,403 INFO [http-nio-8090-exec-6] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:48,秒殺的商品是:watch
2021-09-13 17:35:43,402 INFO [http-nio-8090-exec-13] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:74,秒殺的商品是:watch
2021-09-13 17:35:43,404 INFO [http-nio-8090-exec-9] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:66,秒殺的商品是:watch
2021-09-13 17:35:43,407 INFO [http-nio-8090-exec-14] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:59,秒殺的商品是:watch
2021-09-13 17:35:43,402 INFO [http-nio-8090-exec-16] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:58,秒殺的商品是:watch
2021-09-13 17:35:43,406 INFO [http-nio-8090-exec-5] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:83,秒殺的商品是:watch
2021-09-13 17:35:43,408 INFO [http-nio-8090-exec-17] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:12,秒殺的商品是:watch
2021-09-13 17:35:43,408 INFO [http-nio-8090-exec-1] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:75,秒殺的商品是:watch
2021-09-13 17:35:43,403 INFO [http-nio-8090-exec-2] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:42,秒殺的商品是:watch
2021-09-13 17:35:43,405 INFO [http-nio-8090-exec-3] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:72,秒殺的商品是:watch
2021-09-13 17:35:43,403 INFO [http-nio-8090-exec-11] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:52,秒殺的商品是:watch
2021-09-13 17:35:43,405 INFO [http-nio-8090-exec-8] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:32,秒殺的商品是:watch
2021-09-13 17:35:43,403 INFO [http-nio-8090-exec-12] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:21,秒殺的商品是:watch
2021-09-13 17:35:43,403 INFO [http-nio-8090-exec-4] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:67,秒殺的商品是:watch
2021-09-13 17:35:43,404 INFO [http-nio-8090-exec-15] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:63,秒殺的商品是:watch
2021-09-13 17:35:43,424 INFO [http-nio-8090-exec-18] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:48,秒殺的商品是:watch
2021-09-13 17:35:43,429 INFO [http-nio-8090-exec-10] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 66] 用戶:10秒殺該商品:watch庫存有余,可以進行下訂單操作
2021-09-13 17:35:43,429 INFO [http-nio-8090-exec-7] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 66] 用戶:29秒殺該商品:watch庫存有余,可以進行下訂單操作
2021-09-13 17:35:43,430 INFO [http-nio-8090-exec-6] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 66] 用戶:48秒殺該商品:watch庫存有余,可以進行下訂單操作
2021-09-13 17:35:43,430 INFO [http-nio-8090-exec-11] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 66] 用戶:52秒殺該商品:watch庫存有余,可以進行下訂單操作
2021-09-13 17:35:43,430 INFO [http-nio-8090-exec-14] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 66] 用戶:59秒殺該商品:watch庫存有余,可以進行下訂單操作
2021-09-13 17:35:43,431 INFO [http-nio-8090-exec-13] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 66] 用戶:74秒殺該商品:watch庫存有余,可以進行下訂單操作
2021-09-13 17:35:43,431 INFO [http-nio-8090-exec-2] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 66] 用戶:42秒殺該商品:watch庫存有余,可以進行下訂單操作
2021-09-13 17:35:43,431 INFO [http-nio-8090-exec-5] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 66] 用戶:83秒殺該商品:watch庫存有余,可以進行下訂單操作
2021-09-13 17:35:43,431 INFO [http-nio-8090-exec-17] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 66] 用戶:12秒殺該商品:watch庫存有余,可以進行下訂單操作
2021-09-13 17:35:43,431 INFO [http-nio-8090-exec-16] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 66] 用戶:58秒殺該商品:watch庫存有余,可以進行下訂單操作
2021-09-13 17:35:43,431 INFO [http-nio-8090-exec-1] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:75秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,431 INFO [http-nio-8090-exec-8] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:32秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,431 INFO [http-nio-8090-exec-4] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:67秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,435 INFO [http-nio-8090-exec-12] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:21秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,435 INFO [http-nio-8090-exec-3] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:72秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,436 INFO [http-nio-8090-exec-18] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:48秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,435 INFO [http-nio-8090-exec-15] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:63秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,435 INFO [http-nio-8090-exec-9] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:66秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,455 INFO [http-nio-8090-exec-19] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:10,秒殺的商品是:watch
2021-09-13 17:35:43,464 INFO [http-nio-8090-exec-19] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:10秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,480 INFO [http-nio-8090-exec-20] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:49,秒殺的商品是:watch
2021-09-13 17:35:43,487 INFO [http-nio-8090-exec-20] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:49秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,502 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 45] 庫存訊息佇列收到的訊息商品資訊是:watch
2021-09-13 17:35:43,798 INFO [http-nio-8090-exec-30] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:41,秒殺的商品是:watch
2021-09-13 17:35:43,798 INFO [http-nio-8090-exec-32] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:85,秒殺的商品是:watch
2021-09-13 17:35:43,799 INFO [http-nio-8090-exec-24] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:93,秒殺的商品是:watch
2021-09-13 17:35:43,799 INFO [http-nio-8090-exec-26] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:1,秒殺的商品是:watch
2021-09-13 17:35:43,800 INFO [http-nio-8090-exec-25] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:64,秒殺的商品是:watch
2021-09-13 17:35:43,802 INFO [http-nio-8090-exec-27] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:0,秒殺的商品是:watch
2021-09-13 17:35:43,804 INFO [http-nio-8090-exec-28] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:2,秒殺的商品是:watch
2021-09-13 17:35:43,804 INFO [http-nio-8090-exec-31] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:21,秒殺的商品是:watch
2021-09-13 17:35:43,804 INFO [http-nio-8090-exec-23] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:67,秒殺的商品是:watch
2021-09-13 17:35:43,804 INFO [http-nio-8090-exec-29] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:52,秒殺的商品是:watch
2021-09-13 17:35:43,811 INFO [http-nio-8090-exec-30] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:41秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,815 INFO [http-nio-8090-exec-24] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:93秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,816 INFO [http-nio-8090-exec-25] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:64秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,816 INFO [http-nio-8090-exec-27] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:0秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,816 INFO [http-nio-8090-exec-28] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:2秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,816 INFO [http-nio-8090-exec-31] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:21秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,816 INFO [http-nio-8090-exec-23] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:67秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,815 INFO [http-nio-8090-exec-32] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:85秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,815 INFO [http-nio-8090-exec-26] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:1秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,816 INFO [http-nio-8090-exec-29] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:52秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,818 INFO [http-nio-8090-exec-22] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:45,秒殺的商品是:watch
2021-09-13 17:35:43,818 INFO [http-nio-8090-exec-33] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:21,秒殺的商品是:watch
2021-09-13 17:35:43,831 INFO [http-nio-8090-exec-34] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:99,秒殺的商品是:watch
2021-09-13 17:35:43,846 INFO [http-nio-8090-exec-22] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:45秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,848 INFO [http-nio-8090-exec-33] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:21秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,848 INFO [http-nio-8090-exec-21] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:34,秒殺的商品是:watch
2021-09-13 17:35:43,848 INFO [http-nio-8090-exec-4] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:1,秒殺的商品是:watch
2021-09-13 17:35:43,855 INFO [http-nio-8090-exec-34] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:99秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,857 INFO [http-nio-8090-exec-21] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:34秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,862 INFO [http-nio-8090-exec-4] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:1秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,877 INFO [http-nio-8090-exec-31] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:32,秒殺的商品是:watch
2021-09-13 17:35:43,887 INFO [http-nio-8090-exec-31] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:32秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,900 INFO [http-nio-8090-exec-20] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:68,秒殺的商品是:watch
2021-09-13 17:35:43,907 INFO [http-nio-8090-exec-20] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:68秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,925 INFO [http-nio-8090-exec-13] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:26,秒殺的商品是:watch
2021-09-13 17:35:43,929 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#1-1] c.e.d.s.impl.MQOrderServiceImpl [MQOrderServiceImpl.java : 33] 收到訂單訊息,訂單用戶為:58,商品名稱為:watch
2021-09-13 17:35:43,933 INFO [http-nio-8090-exec-13] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:26秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,948 INFO [http-nio-8090-exec-9] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:52,秒殺的商品是:watch
2021-09-13 17:35:43,955 INFO [http-nio-8090-exec-9] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:52秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:43,973 INFO [http-nio-8090-exec-6] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 60] 參加秒殺的用戶是:96,秒殺的商品是:watch
2021-09-13 17:35:43,981 INFO [http-nio-8090-exec-6] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 78] 用戶:96秒殺時商品的庫存量沒有剩余,秒殺結束
2021-09-13 17:35:44,118 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 45] 庫存訊息佇列收到的訊息商品資訊是:watch
2021-09-13 17:35:44,118 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#1-1] c.e.d.s.impl.MQOrderServiceImpl [MQOrderServiceImpl.java : 33] 收到訂單訊息,訂單用戶為:52,商品名稱為:watch
2021-09-13 17:35:44,126 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 45] 庫存訊息佇列收到的訊息商品資訊是:watch
2021-09-13 17:35:44,134 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 45] 庫存訊息佇列收到的訊息商品資訊是:watch
2021-09-13 17:35:44,135 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#1-1] c.e.d.s.impl.MQOrderServiceImpl [MQOrderServiceImpl.java : 33] 收到訂單訊息,訂單用戶為:29,商品名稱為:watch
2021-09-13 17:35:44,143 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 45] 庫存訊息佇列收到的訊息商品資訊是:watch
2021-09-13 17:35:44,151 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 45] 庫存訊息佇列收到的訊息商品資訊是:watch
2021-09-13 17:35:44,151 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#1-1] c.e.d.s.impl.MQOrderServiceImpl [MQOrderServiceImpl.java : 33] 收到訂單訊息,訂單用戶為:42,商品名稱為:watch
2021-09-13 17:35:44,160 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 45] 庫存訊息佇列收到的訊息商品資訊是:watch
2021-09-13 17:35:44,169 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#1-1] c.e.d.s.impl.MQOrderServiceImpl [MQOrderServiceImpl.java : 33] 收到訂單訊息,訂單用戶為:59,商品名稱為:watch
2021-09-13 17:35:44,169 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 45] 庫存訊息佇列收到的訊息商品資訊是:watch
2021-09-13 17:35:44,187 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 45] 庫存訊息佇列收到的訊息商品資訊是:watch
2021-09-13 17:35:44,192 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#1-1] c.e.d.s.impl.MQOrderServiceImpl [MQOrderServiceImpl.java : 33] 收到訂單訊息,訂單用戶為:83,商品名稱為:watch
2021-09-13 17:35:44,194 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.e.d.s.impl.MQStockServiceImpl [MQStockServiceImpl.java : 45] 庫存訊息佇列收到的訊息商品資訊是:watch
2021-09-13 17:35:44,208 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#1-1] c.e.d.s.impl.MQOrderServiceImpl [MQOrderServiceImpl.java : 33] 收到訂單訊息,訂單用戶為:10,商品名稱為:watch
2021-09-13 17:35:44,223 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#1-1] c.e.d.s.impl.MQOrderServiceImpl [MQOrderServiceImpl.java : 33] 收到訂單訊息,訂單用戶為:12,商品名稱為:watch
2021-09-13 17:35:44,239 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#1-1] c.e.d.s.impl.MQOrderServiceImpl [MQOrderServiceImpl.java : 33] 收到訂單訊息,訂單用戶為:48,商品名稱為:watch
2021-09-13 17:35:44,254 INFO [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#1-1] c.e.d.s.impl.MQOrderServiceImpl [MQOrderServiceImpl.java : 33] 收到訂單訊息,訂單用戶為:74,商品名稱為:watch
秒殺完成了,但是redis中出現超賣現象,

存在超賣問題,正在優化中,后續繼續更新…
原始碼:碼云倉庫
訪問地址:https://gitee.com/kai-java/sec-kill
git地址:https://gitee.com/kai-java/sec-kill.git
所有內容皆為個人總結或轉載別人的文章,只為學習技術, 若您覺得文章有用,歡迎點贊分享! 若無意對您的文章造成侵權,請您留言,博主看到后會及時處理,謝謝,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/299946.html
標籤:其他
