新手入门,Springboot+redis+rabbitmq+Jmeter实现高并发现时秒杀,idea+maven测试
文章前言
现在电商项目都有许多的秒杀活动,今天这一篇博客就是让大家,自己动手编程一个秒杀的过程,话不多说直接上手
所需工具
idea+redis+rabbitmq+Jmeter+RedisDesktopManager+tkmybatis
安装工具的路径
数据库
数据库使用 mysql,下面是表结构
搭建项目
启动器 pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</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> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </dependency> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.0.3-beta1</version> </dependency> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> <version>4.0.0</version> </dependency> </dependencies>
配置文件 application.properties
spring.devtools.restart.enabled=false spring.datasource.username=root spring.datasource.password=root server.port=8443 spring.datasource.url=jdbc:mysql://localhost:3306/myredis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest spring.redis.host=localhost spring.redis.port=6379 spring.redis.jedis.pool.max-active=1024 spring.redis.jedis.pool.max-wait=-1s spring.redis.jedis.pool.max-idle=200 spring.redis.password=123456
项目包目录
实体类(pojo) Order,Stock
因为本次数据库操作方面使用了tkmybatis框架,所以实体类我们需要用到JPA的注解,来实现映射关系
package com.example.myredis.pojo; import lombok.Data; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import java.io.Serializable; /** * 作者:** * 日期:2019/12/24 9:06 */ @Data @Table(name = "t_order") public class Order implements Serializable { private static final long serialVersionUID = -8867272732777764701L; @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "order_name") private String order_name; @Column(name = "order_user") private String order_user; }
package com.example.myredis.pojo; import lombok.Data; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import java.io.Serializable; /** * 作者:** * 日期:2019/12/24 9:08 */ @Table(name = "stock") @Data public class Stock implements Serializable { private static final long serialVersionUID = 2451194410162873075L; @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "name") private String name; @Column(name = "stock") private Long stock; }
mapper层
package com.example.myredis.mapper; /** * 作者:** * 日期:2019/12/24 9:10 */ import com.example.myredis.base.service.GenericMapper; import com.example.myredis.pojo.Order; import org.apache.ibatis.annotations.Mapper; @Mapper public interface OrderMapper extends GenericMapper<Order> { void insertOrder(Order order); }
package com.example.myredis.mapper; /** * 作者:** * 日期:2019/12/24 9:11 */ import com.example.myredis.base.service.GenericMapper; import com.example.myredis.pojo.Stock; import org.apache.ibatis.annotations.Mapper; @Mapper public interface StockMapper extends GenericMapper<Stock> { }
service层 (注:这里没有发接口,自己写一下)
package com.example.myredis.service.impl; import com.example.myredis.mapper.OrderMapper; import com.example.myredis.pojo.Order; import com.example.myredis.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * 作者:** * 日期:2019/12/24 9:14 */ @Service public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Override public void createOrder(Order order) { orderMapper.insert(order); } }
package com.example.myredis.service.impl; import com.example.myredis.mapper.StockMapper; import com.example.myredis.pojo.Stock; import com.example.myredis.service.StockSerivce; 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; /** * 作者:** * 日期:2019/12/24 9:14 */ @Service public class StockSerivceImpl implements StockSerivce { @Autowired private StockMapper stockMapper; // 秒杀商品后减少库存 @Override public void decrByStock(String stockName) { Example example = new Example(Stock.class); Example.Criteria criteria = example.createCriteria(); criteria.andEqualTo("name", stockName); List<Stock> stocks = stockMapper.selectByExample(example); if (!CollectionUtils.isEmpty(stocks)) { Stock stock = stocks.get(0); stock.setStock(stock.getStock() - 1); stockMapper.updateByPrimaryKey(stock); } } // 秒杀商品前判断是否有库存 @Override public Integer selectByExample(String stockName) { Example example = new Example(Stock.class); Example.Criteria criteria = example.createCriteria(); criteria.andEqualTo("name", stockName); List<Stock> stocks = stockMapper.selectByExample(example); if (!CollectionUtils.isEmpty(stocks)) { return stocks.get(0).getStock().intValue(); } return 0; } }
controller层(这里有俩种方式,一个纯数据库,一个redis,大家等下可以看看区别)
package com.example.myredis.controller; import com.example.myredis.config.RabbitMQ; import com.example.myredis.pojo.Order; import com.example.myredis.service.OrderService; import com.example.myredis.service.RedisService; import com.example.myredis.service.StockSerivce; import lombok.extern.slf4j.Slf4j; 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; /** * 作者:** * 日期:2019/12/24 9:23 */ @Controller @Slf4j public class SecController { @Autowired private RabbitTemplate rabbitTemplate; @Autowired private RedisService redisService; @Autowired private OrderService orderService; @Autowired private StockSerivce stockService; /** * 使用redis+消息队列进行秒杀实现 * * @param username * @param stockName * @return */ @PostMapping( value = "/sec",produces = "application/json;charset=utf-8") @ResponseBody public String sec(@RequestParam(value = "username") String username, @RequestParam(value = "stockName") String stockName) { log.info("参加秒杀的用户是:{},秒杀的商品是:{}", username, stockName); String message = null; //调用redis给相应商品库存量减一 Long decrByResult = redisService.decrBy(stockName); if (decrByResult >= 0) { /** * 说明该商品的库存量有剩余,可以进行下订单操作 */ log.info("用户:{}秒杀该商品:{}库存有余,可以进行下订单操作",username, stockName); //发消息给库存消息队列,将库存数据减一 rabbitTemplate.convertAndSend(RabbitMQ.STORY_EXCHANGE, RabbitMQ.STORY_ROUTING_KEY, stockName); //发消息给订单消息队列,创建订单 Order order = new Order(); order.setOrder_name(stockName); order.setOrder_user(username); rabbitTemplate.convertAndSend(RabbitMQ.ORDER_EXCHANGE, RabbitMQ.ORDER_ROUTING_KEY, order); message = "用户" + username + "秒杀" + stockName + "成功"; } else { /** * 说明该商品的库存量没有剩余,直接返回秒杀失败的消息给用户 */ log.info("用户:{}秒杀时商品的库存量没有剩余,秒杀结束", username); message = "用户:"+ username + "商品的库存量没有剩余,秒杀结束"; } return message; } /** * 实现纯数据库操作实现秒杀操作 * @param username * @param stockName * @return */ @RequestMapping("/secDataBase") @ResponseBody public String secDataBase(@RequestParam(value = "username") String username, @RequestParam(value = "stockName") String stockName) { log.info("参加秒杀的用户是:{},秒杀的商品是:{}", username, stockName); String message = null; //查找该商品库存 Integer stockCount = stockService.selectByExample(stockName); log.info("用户:{}参加秒杀,当前商品库存量是:{}", username, stockCount); if (stockCount > 0) { /** * 还有库存,可以进行继续秒杀,库存减一,下订单 */ //1、库存减一 stockService.decrByStock(stockName); //2、下订单 Order order = new Order(); order.setOrder_user(username); order.setOrder_name(stockName); orderService.createOrder(order); log.info("用户:{}.参加秒杀结果是:成功", username); message = username + "参加秒杀结果是:成功"; } else { log.info("用户:{}.参加秒杀结果是:秒杀已经结束", username); message = username + "参加秒杀活动结果是:秒杀已经结束"; } return message; } }
配置tkmybatis的接口新建名为base得包,在base下面新建service
package com.example.myredis.base.service; /** * 作者:** * 日期:2019/12/24 9:09 */ import tk.mybatis.mapper.common.Mapper; import tk.mybatis.mapper.common.MySqlMapper; public interface GenericMapper<T> extends Mapper<T>, MySqlMapper<T> { }
新建config包,新建redis和RabbitMQ的类
编写RabbitMQ和redis得配置类
redis
package com.example.myredis.config; 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; /** * 作者:** * 日期:2019/12/23 16:57 */ @Configuration public class Redis{ // 配置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; } }
RabbitMQ
package com.example.myredis.config; 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 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; /** * 作者:** * 日期:2019/12/23 16:56 */ @Configuration public class RabbitMQ { //库存交换机 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(); } }
配置rabbitmq得实现方式以及redis得实现方式
在 service包下面新建 MQOrderService.java
这个类属于订单得消费队列
package com.example.myredis.service; import com.example.myredis.config.RabbitMQ; import com.example.myredis.pojo.Order; import com.example.myredis.service.OrderService; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * 作者:刘怡 * 日期:2019/12/24 9:17 */ @Service @Slf4j public class MQOrderService { @Autowired private OrderService orderService; /** * 监听订单消息队列,并消费 * * @param order */ @RabbitListener(queues = RabbitMQ.ORDER_QUEUE) public void createOrder(Order order) { log.info("收到订单消息,订单用户为:{},商品名称为:{}", order.getOrder_user(), order.getOrder_name()); /** * 调用数据库orderService创建订单信息 */ orderService.createOrder(order); } }
MQStockService
这个属于库存得消费队列
package com.example.myredis.service; import com.example.myredis.config.RabbitMQ; import com.example.myredis.service.StockSerivce; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * 作者:** * 日期:2019/12/24 9:18 */ @Service @Slf4j public class MQStockService { @Autowired private StockSerivce stockService; /** * 监听库存消息队列,并消费 * @param stockName */ @RabbitListener(queues = RabbitMQ.STORY_QUEUE) public void decrByStock(String stockName) { log.info("库存消息队列收到的消息商品信息是:{}", stockName); /** * 调用数据库service给数据库对应商品库存减一 */ stockService.decrByStock(stockName); } }
RedisService.java
这个配置类,主要用来实现对redis得key和value初始化以及对value得操作
package com.example.myredis.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.Date; import java.util.concurrent.TimeUnit; /** * 作者:** * 日期:2019/12/24 9:21 */ @Service public class RedisService { @Autowired private RedisTemplate<String, Object> redisTemplate; /** * 设置String键值对 * @param key * @param value * @param millis */ public void put(String key, Object value, long millis) { redisTemplate.opsForValue().set(key, value, millis, TimeUnit.MINUTES); } public void putForHash(String objectKey, String hkey, String value) { redisTemplate.opsForHash().put(objectKey, hkey, value); } public <T> T get(String key, Class<T> type) { return (T) redisTemplate.boundValueOps(key).get(); } public void remove(String key) { redisTemplate.delete(key); } public boolean expire(String key, long millis) { return redisTemplate.expire(key, millis, TimeUnit.MILLISECONDS); } public boolean persist(String key) { return redisTemplate.hasKey(key); } public String getString(String key) { return (String) redisTemplate.opsForValue().get(key); } public Integer getInteger(String key) { return (Integer) redisTemplate.opsForValue().get(key); } public Long getLong(String key) { return (Long) redisTemplate.opsForValue().get(key); } public Date getDate(String key) { return (Date) redisTemplate.opsForValue().get(key); } /** * 对指定key的键值减一 * @param key * @return */ public Long decrBy(String key) { return redisTemplate.opsForValue().decrement(key); } }
编写springboot启动类
package com.example.myredis; import com.example.myredis.service.RedisService; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.example.myredis.mapper") public class MyredisApplication implements ApplicationRunner { public static void main(String[] args) { SpringApplication.run(MyredisApplication.class, args); } @Autowired private RedisService redisService; /** * redis初始化各商品的库存量 * * @param args * @throws Exception */ @Override public void run(ApplicationArguments args) throws Exception { redisService.put("watch", 10, 20); } }
最后的项目所有目录
测试之前
运行springboot项目,不能报错,打开redis服务和rabbitmq服务
redis
启动成功之后打开Redis Desktop Manager工具,查看是否新建了一个redis :watch
JMeter
然后打开JMeter工具
选择中文。。。
新建一个测试计划
然后添加一个线程组
给这个线程组得数量为40,这个线程组得作用就是模拟40个用户发送请求,去秒杀
然后再在线程组右键,添加一个Http请求,这个就是我们用来发送请求的组件了
配置
这个请求唯一要说得就是,随机参数了,因为用户名肯定不可能给40个相同得名字,这边我们利用JMeter给用户名得值为随机数
点击上方的白色小书本,选择random,1-99得随机数
输入最大值和最小值,点击生成,函数粘贴复制出去就ok了
最后我们在测试计划建一个结果树,查看我们发送请求返回得消息数据
成功
现在我们再打开redismanager,其中我们初始化为10,现在是-30,可以知道有40个线程去获取了它,现在为-30,每次前测试记得,手动清空缓存!!一定要记得
纯数据库方式秒杀结果
上面我们实现了redis+rabbitmq得秒杀,现在我们看看纯数据库方式得秒杀,看看有什么区别:
首先网stock库存表新增一条数据,类似于redis得初始化
在jmeter中修改原来得http请求信息,其中小米对应数据库得商品名
清空一下结果树,我们开始运行
run
控制台:
重要得是查看数据库得信息:
库存已经清空,再看order表
这样我们可以看到,明明只有10个库存得商品,抢到得人却不止10个,这样明细超卖了,请求树也可以看的超卖信息
- 点赞 7
- 收藏
- 分享
- 文章举报
- 入门基础必备,使用Idea实现SpringBoot+Mysql+Redis+RabbitMQ+Jmeter模拟实现高并发秒杀
- 使用idea实现SpringBoot+Mysql+Redis+RabbitMQ+Jmeter的高并发秒杀
- idea基础实现SpringBoot+Mysql+Redis+RabbitMQ+Jmeter模拟高并发秒杀
- SpringBoot +Redis +RabbitMQ 实现高并发限时秒杀
- #使用idea创建springboot +Redis +RabbitMQ 实现高并发限时秒杀
- 基于dubbo的分布式项目框架搭建 开发工具idea (springboot+dubbo+zookeeper+redis+rabbitmq+基于Swagger2的restful api) --(三)
- springboot rabbitmq 入门案例
- Spring Boot RabbitMQ 延迟消息实现完整版示例
- springboot+rabbitmq两小时入门(八):死信交换机
- SpringBoot2.x使用Redis实现缓存入门
- spring boot Rabbitmq集成,延时消息队列实现
- 基于dubbo的分布式项目框架搭建 开发工具idea (springboot+dubbo+zookeeper+redis+rabbitmq+基于Swagger2的restful api) --(五)
- springboot+rabbitmq两小时入门(六):延时交换机
- Springboot+rabbitmq实现延时队列的两种方式
- springboot+rabbitmq两小时入门(十):rabbitmq面试题
- Springboot+rabbitmq如何实现高并发的rpc调用
- Springboot+redis实现商品秒杀
- springboot+rabbitmq两小时入门(五):Topic交换机
- springboot+rabbitmq两小时入门(四):fanout交换机
- 基于dubbo的分布式项目框架搭建 开发工具idea (springboot+dubbo+zookeeper+redis+rabbitmq+基于Swagger2的restful api) --(一)