在电商项目中,会经常出现各种活动,如限时秒杀,秒杀活动的并发量特别高,会导致访问变慢、商品超卖等问题。学会以下知识将会解决这些部分问题。
开发工具:idea、navicat、RedisDesktopManager
开发环境:JDK1.8、MySql8.0、Maven、SpringBoot、redis、RabbitMQ
点击将会进入相关安装教程
1、order(订单)表:
2、commodity_inventory(商品库存)表
<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>
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;
}
}
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);
}
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();
}
}
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);
}
}
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);
}
}
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