使用spring boot + mybatis + RabbitMQ + redis模拟商城秒杀功能
谢谢观看,希望对读友有用,话不多说,直接讲解
首先我们需要安装RabbitMQ,在安装RabbitMQ之前需要先安装Erlang,因为RabbitMQ是基于Erlang的,具体的步骤点击下面的这个链接:
安装完RabbitMQ如下图:
安装完成后可以在浏览器的地址栏上输入:http://localhost:15672/#/users默认账户和密码都是 guset
环境搭好之后,接下来就可以开始撸代码了
由于我是maven环境的,这里就直接使用jar坐标
rabbitMQ
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>1.5.8.RELEASE</version>
</dependency>
redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
mybatis
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator</artifactId>
<version>1.3.5</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
创建测试数据库结构
– Table structure for stock
DROP TABLE IF EXISTS
stock
;
CREATE TABLEstock
(
id
int(11) NOT NULL AUTO_INCREMENT,
name
varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
stock
varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (id
) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
– Table structure for t_order
DROP TABLE IF EXISTS
t_order
;
CREATE TABLEt_order
(
id
int(11) NOT NULL AUTO_INCREMENT,
order_name
varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
order_user
varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (id
) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
创建对应的dao mapper xml
创建完成后,首先创建一个监听器初始化数据到redis中,这一步可以根据自己的业务逻辑进行相应的改动
package com.ceshi.demo.service;
import com.ceshi.demo.dao.StockMapper;
import com.ceshi.demo.pojo.Stock;
import com.ceshi.demo.pojo.StockExample;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Iterator;
import java.util.List;
/**
* @author nonpool
* @version 1.0
* @since 2020/2/11
*/
@Component
@Slf4j
public class ApplicationInitListener implements ApplicationListener<ContextRefreshedEvent> {
@Resource
private StockMapper stockMapper;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
// 先获取到 application 上下文
ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
log.info(">>>>>>>>>>>>项目初始化完成,执行监听器中逻辑");
//mapper中的sql,返回全部上架(支持秒杀)的商品集合
StockExample stockExample = new StockExample();
List<Stock> stocks = stockMapper.selectByExample(stockExample);
Iterator<Stock> it = stocks.iterator();
while(it.hasNext()) {
Stock p = it.next();
log.info("商品名称:"+p.getName()+"商品库存:"+p.getStock());
try {
stringRedisTemplate.opsForValue().set(String.valueOf(p.getName()), String.valueOf(p.getStock()));
} catch (Exception e) {
log.error("当前商品名称:"+p.getName()+"库存:"+p.getStock()+"放入Redis缓存异常<<<<<<<<<<<<<<<<<<<<");
e.printStackTrace();
}
}
}
}
配置rabbitMQConfig队列
package com.ceshi.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 配置rabbitMQConfig队列
* @version 1.0
* @since 2020/2/11
*/
@Configuration
public class RabbitMQCongig {
//库存交换机
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();
}
}
创建MQ监听订单消息列队,并消费
package com.ceshi.demo.service;
import com.ceshi.demo.config.RabbitMQCongig;
import com.ceshi.demo.dao.TOrderMapper;
import com.ceshi.demo.pojo.TOrder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author
* @version 1.0
* @since 2020/2/11
*/
@Service
@Slf4j
public class MQOrderService {
@Resource
private TOrderMapper orderMapper;
/**
* 监听订单消息队列,并消费
*
* @param order
*/
@RabbitListener(queues = RabbitMQCongig.ORDER_QUEUE)
public void createOrder(TOrder order) {
log.info("收到订单消息,订单用户为:{},商品名称为:{}", order.getOrderUser(), order.getOrderName());
/**
* 调用数据库orderService创建订单信息
*/
orderMapper.insert(order);
}
}
MQ创建监听库存消息列队,并消费
package com.ceshi.demo.service;
import com.ceshi.demo.config.RabbitMQCongig;
import com.ceshi.demo.dao.StockMapper;
import com.ceshi.demo.pojo.Stock;
import com.ceshi.demo.pojo.StockExample;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* @author
* @version 1.0
* @since 2020/2/11
* 监听消息队列里面的消费商品进行消费
*/
@Service
@Slf4j
public class MQStockService {
//消费数据库中的商品表
@Resource
private StockMapper stockMapper;
/**
* 监听库存消息队列,并消费
* @param stockName
*/
@RabbitListener(queues = RabbitMQCongig.STORY_QUEUE)
public void decrByStock(String stockName) {
log.info("库存消息队列收到的消息商品信息是:{}", stockName);
/**
* 调用数据库service给数据库对应商品库存减一
*/
//获取当前商品的数量
StockExample stockExample = new StockExample();
StockExample.Criteria criteria = stockExample.createCriteria();
criteria.andNameEqualTo(stockName);
List<Stock> stocks = stockMapper.selectByExample(stockExample);
//修改
Stock stock = new Stock();
stock.setId(stocks.get(0).getId());
stock.setName(stocks.get(0).getName());
stock.setStock((Integer.parseInt(stocks.get(0).getStock())-1)+"");
stockMapper.updateByPrimaryKey(stock);
}
}
创建reidsService
package com.ceshi.demo.service;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author chy
* @version 1.0
* @since 2020/2/11
* 设置redisService
*/
@Service
public class RedisService {
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 对指定key的键值减一
* @param key
* @return
*/
public Long decrBy(String key) {
return stringRedisTemplate.opsForValue().decrement(key);
}
}
最后创建controller
package com.ceshi.demo.controller;
import com.ceshi.demo.config.RabbitMQCongig;
import com.ceshi.demo.dao.StockMapper;
import com.ceshi.demo.dao.TOrderMapper;
import com.ceshi.demo.pojo.Stock;
import com.ceshi.demo.pojo.StockExample;
import com.ceshi.demo.pojo.TOrder;
import com.ceshi.demo.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* @author nonpool
* @version 1.0
* @since 2020/2/11
*/
@RestController
@RequestMapping("activity")
@ResponseBody
@Slf4j
public class Ceshi {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private RedisService redisService;
@Resource
private TOrderMapper tOrderMapper;
@Resource
private StockMapper stockMapper;
/**
* 使用redis+消息队列进行秒杀实现
*
* @param username
* @param stockName
* @return
*/
@RequestMapping("sec")
@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(RabbitMQCongig.STORY_EXCHANGE, RabbitMQCongig.STORY_ROUTING_KEY, stockName);
//发消息给订单消息队列,创建订单
TOrder order = new TOrder();
order.setOrderName(stockName);
order.setOrderUser(username);
rabbitTemplate.convertAndSend(RabbitMQCongig.ORDER_EXCHANGE, RabbitMQCongig.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;
//查找该商品库存
StockExample stockExample = new StockExample();
StockExample.Criteria criteria = stockExample.createCriteria();
criteria.andNameEqualTo(stockName);
List<Stock> stocks = stockMapper.selectByExample(stockExample);
Integer stockCount = Integer.parseInt(stocks.get(0).getStock());
log.info("用户:{}参加秒杀,当前商品库存量是:{}", username, stockCount);
if (stockCount > 0) {
/**
* 还有库存,可以进行继续秒杀,库存减一,下订单
*/
//1、库存减一
Stock stock = new Stock();
stock.setId(stocks.get(0).getId());
stock.setStock((stockCount-1)+"");
stock.setName(stocks.get(0).getName());
stockMapper.updateByPrimaryKey(stock);
//2、下订单
TOrder order = new TOrder();
order.setOrderUser(username);
order.setOrderName(stockName);
tOrderMapper.insert(order);
log.info("用户:{}.参加秒杀结果是:成功", username);
message = username + "参加秒杀结果是:成功";
} else {
log.info("用户:{}.参加秒杀结果是:秒杀已经结束", username);
message = username + "参加秒杀活动结果是:秒杀已经结束";
}
return message;
}
}
到此结束,各位读友有什么更好的,还请在共同交流下,不足之处还望各位大佬多多指导。