springboot +rabbitmq+redis实现秒杀示例

作者:yy1209357299 时间:2022-04-21 02:29:28 

实现说明

springboot +rabbitmq+redis实现秒杀示例

这里的核心在于如何在大并发的情况下保证数据库能扛得住压力,因为大并发的瓶颈在于数据库。如果用户的请求直接从前端传到数据库,显然,数据库是无法承受几十万上百万甚至上千万的并发量的。因此,我们能做的只能是减少对数据库的访问。例如,前端发出了100万个请求,通过我们的处理,最终只有10个会访问数据库,这样就会大大提升系统性能。再针对秒杀这种场景,因为秒杀商品的数量是有限的,因此采用上述实现方案。

假如,某个商品可秒杀的数量是10,那么在秒杀活动开始之前,把商品的ID和数量加载到Redis缓存。当服务端收到请求时,首先预减Redis中的数量,如果数量减到小于0时,那么随后的访问直接返回秒杀失败的信息。也就是说,最终只有10个请求会去访问数据库。

如果商品数量比较多,比如1万件商品参与秒杀,那么就有1万*10=10万个请求并发去访问数据库,数据库的压力还是会很大。这里就用到了另外一个非常重要的组件:消息队列。我们不是把请求直接去访问数据库,而是先把请求写到消息队列中,做一个缓存,然后再去慢慢的更新数据库。这样做之后,前端用户的请求可能不会立即得到响应是成功还是失败,很可能得到的是一个排队中的返回值,这个时候,需要客户端去服务端轮询,因为我们不能保证一定就秒杀成功了。当服务端出队,生成订单以后,把用户ID和商品ID写到缓存中,来应对客户端的轮询就可以了。这样处理以后,我们的应用是可以很简单的进行分布式横向扩展的,以应对更大的并发。当然,秒杀系统还有很多要处理的事情,比如限流防刷、分布式Session等等。

1、工具准备

rabbitmq安装:https://www.jb51.net/article/253706.htm

界面地址:http://localhost:15672/#/
用户名 guest
密码 guest

springboot +rabbitmq+redis实现秒杀示例

redis安装:https://www.jb51.net/article/145704.htm
jmeter安装:https://www.jb51.net/article/232152.htm

2、数据表

商品表

-- ----------------------------
-- Table structure for stock
-- ----------------------------
DROP TABLE IF EXISTS `stock`;
CREATE TABLE `stock` (
 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
 `count` int(11) NOT NULL COMMENT '库存',
 `sale` int(11) NOT NULL COMMENT '已售',
 `version` int(11) NOT NULL COMMENT '乐观锁,版本号',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

订单表

-- ----------------------------
-- Table structure for stock_order
-- ----------------------------
DROP TABLE IF EXISTS `stock_order`;
CREATE TABLE `stock_order` (
 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 `sid` int(11) NOT NULL COMMENT '库存ID',
 `name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名称',
 `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3、pom

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

4、代码结构

springboot +rabbitmq+redis实现秒杀示例

5、配置config

mq配置

package com.yy.msserver.config;

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.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author code
* @Date 2022/6/27 14:03
* Description rabbitmq config
* Version 1.0
*/
@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配置

package com.yy.msserver.config;

/**
* @author code
* @Date 2022/6/27 14:06
* Description redis config
* Version 1.0
*/
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;
/**
* @author code
*@Date 2022/6/27 14:05
*Description redis config
*Version 1.0
*/
@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;
   }
}

6、订单业务层

接口层

package com.yy.msserver.service;

import com.yy.msserver.model.vo.Stock;

/**
* @author code
* @Date 2022/6/24 9:25
* Description 订单接口
* Version 1.0
*/
public interface StockOrderService {
   public Integer createOrder(Integer id);

public void decrByStock(Integer id);
}

实现层

package com.yy.msserver.service.impl;

import com.yy.msserver.dao.StockMapper;
import com.yy.msserver.dao.StockOrderMapper;
import com.yy.msserver.model.vo.Stock;
import com.yy.msserver.model.vo.StockOrder;
import com.yy.msserver.service.StockOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/**
* @author code
* @Date 2022/6/24 9:25
* Description 订单实现
* Version 1.0
*/
@Service
public class StockOrderServiceImpl implements StockOrderService {
   @Autowired
   private StockOrderMapper stockOrderMapper;

@Autowired
   private StockMapper stockMapper;

@Override
   @Transactional(rollbackFor = Exception.class)
   public Integer createOrder(Integer id) {
       //校验库存
       Stock stock = checkStock(id);
//        if(stock.getCount()>0){
//            System.out.println("当前库存:" + stock.getCount());
//            //扣库存
//            if(updateSale(stock) == 1){
//
//            }else {
//                return 0;
//            }
//        }
//        return 0;
       return createOrder(stock);
   }

@Override
   public void decrByStock(Integer id){
       //校验库存
       Stock stock = checkStock(id);
       if(stock.getCount()>0){
           System.out.println("当前库存:" + stock.getCount());
           //扣库存
           updateSale(stock);
       }
   }

//校验库存
   private Stock checkStock(Integer id) {
       return stockMapper.checkStock(id);
   }

//扣库存
   private int updateSale(Stock stock){
       return stockMapper.updateSale(stock);
   }

//下订单
   private Integer createOrder(Stock stock){
       StockOrder order = new StockOrder();
       order.setSid(stock.getId());
       order.setCreateTime(new Date());
       order.setName(stock.getName());
       stockOrderMapper.createOrder(order);
       return order.getId();
   }
}

7、redis实现层

package com.yy.msserver.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;

/**
* @author code
* @Date 2022/6/27 17:25
* Description redis
* Version 1.0
*/
@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);
   }
}

8、mq实现层

减库存

package com.yy.msserver.service;

import com.yy.msserver.config.MyRabbitMQConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author code
* @Date 2022/6/27 15:22
* Description mq商品信息
* Version 1.0
*/
@Slf4j
@Service
public class MQStockService {
   @Autowired
   private StockOrderService stockService;
   /**
    * 监听库存消息队列,并消费
    * @param id
    */
   @RabbitListener(queues = MyRabbitMQConfig.STORY_QUEUE)
   public void decrByStock(Integer id) {
       /**
        * 调用数据库service给数据库对应商品库存减一
        */
       log.info("减库存");
       stockService.decrByStock(id);
   }
}

下订单

package com.yy.msserver.service;

import com.yy.msserver.config.MyRabbitMQConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author code
* @Date 2022/6/27 15:19
* Description mq 订单队列
* Version 1.0
*/
@Service
@Slf4j
public class MQOrderService {
   @Autowired
   private StockOrderService orderService;
   /**
    * 监听订单消息队列,并消费
    *
    * @param id
    */
   @RabbitListener(queues = MyRabbitMQConfig.ORDER_QUEUE)
   public void createOrder(Integer id) {
       log.info("收到订单消息");
       orderService.createOrder(id);
   }
}

9、redis模拟初始化库存量

package com.yy.msserver.config;

import com.yy.msserver.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
* @author code
* @Date 2022/6/29 14:08
* Description 初始化
* Version 1.0
*/
@Component
public class InitConfig {
   @Autowired
   private RedisService redisService;
   /**
    * redis初始化商品的库存量和信息
    * @param
    * @throws Exception
    */
   @PostConstruct
   public void init() {
       redisService.put("1", 10, 20);
   }
}

10、controller控制层

/**
    * 使用redis+消息队列进行秒杀实现
    *
    * @param id 商品id
    * @return
    */
   @GetMapping( value = "/sec",produces = "application/json;charset=utf-8")
   @ResponseBody
   public String sec(@RequestParam(value = "id") int id) {

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

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

11、测试

新建线程组&mdash;&mdash;新建取样器(http请求)&mdash;&mdash;新建查看结果树

springboot +rabbitmq+redis实现秒杀示例

12、测试结果

springboot +rabbitmq+redis实现秒杀示例

springboot +rabbitmq+redis实现秒杀示例

springboot +rabbitmq+redis实现秒杀示例

来源:https://blog.csdn.net/yy1209357299/article/details/125523406

标签:springboot,rabbitmq,redis,秒杀
0
投稿

猜你喜欢

  • 关于maven全局配置文件settings.xml解析

    2023-01-08 09:04:04
  • kotlin java 混合代码 maven 打包实现

    2023-04-09 13:33:41
  • Java Jedis NOAUTH Authentication required问题解决方法

    2023-08-19 14:09:47
  • 详解Struts2中json 相互引用死循环解决办法

    2022-09-08 04:04:46
  • java新特性之for循环最全的用法总结

    2022-07-08 22:14:51
  • java实现Yaml转Json示例详解

    2023-12-06 22:58:34
  • Mybatis中连接查询和嵌套查询实例代码

    2021-08-23 13:24:31
  • Java面试synchronized偏向锁后hashcode存址

    2023-08-09 09:15:06
  • springboot实现配置两个parent的方法

    2022-04-05 07:54:43
  • Java 对HashMap进行排序的三种常见方法

    2022-11-04 19:02:10
  • java实现快速打字游戏

    2022-11-05 11:16:38
  • Java SpringBoot实现文件上传功能的示例代码

    2022-05-23 02:54:46
  • java 线程锁详细介绍及实例代码

    2022-03-16 13:50:45
  • SpringBoot自定义Starter实现流程详解

    2022-05-19 07:03:25
  • WPF TextBox和PasswordBox添加水印

    2021-09-30 16:06:02
  • Idea servlet映射方法优缺点对比

    2023-06-12 23:59:34
  • 基于Spring-Security自定义登陆错误提示信息

    2021-09-20 17:33:40
  • Java如何设置PDF文档背景色详解

    2022-07-08 08:17:19
  • 阿里云OSS域名配置及简单上传的示例代码

    2022-04-08 19:14:32
  • C语言实现扫雷游戏(含注释详解)

    2023-11-02 15:25:07
  • asp之家 软件编程 m.aspxhome.com