您的位置:首页 > 数据库 > Redis

使用springboot redis RabbitMQ实现商品秒杀

2020-01-15 10:58 836 查看

前言

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

使用技术

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

创建表

1、order(订单)表:
2、commodity_inventory(商品库存)表

配置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.yml

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

创建实体类

1、order

import java.io.Serializable;

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

public Integer getId() {
return id;
}

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

public String getCname() {
return cname;
}

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

public String getUserName() {
return userName;
}

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

public Integer getCiId() {
return ciId;
}

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

2、CommodityInventory

import java.io.Serializable;

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

public Integer getId() {
return id;
}

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

public String getcName() {
return cName;
}

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

public Integer getInventory() {
return inventory;
}

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

Mapper

1、OrderMapper

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

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

2、CommodityInventoryMapper

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

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

base

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

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

消息队列

MyRabbitMQConfig:

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

@Configuration
public class MyRabbitMQConfig {

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

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

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

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

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

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

Redis

RedisConfig:

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

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

开始秒杀

1、MQOrderService

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

2、MQCommodityInventoryService

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

Service

1、CommodityInventoryServiceImpl

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

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

2、OrderServiceImpl

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

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

Controller

SecController :

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

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

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

}

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

可看到控制台的日志打印

  • 点赞
  • 收藏
  • 分享
  • 文章举报
浮云心丶 发布了7 篇原创文章 · 获赞 3 · 访问量 164 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: