springboot rabbitmq redis秒杀
yy1209357299 人气:0实现说明
这里的核心在于如何在大并发的情况下保证数据库能扛得住压力,因为大并发的瓶颈在于数据库。如果用户的请求直接从前端传到数据库,显然,数据库是无法承受几十万上百万甚至上千万的并发量的。因此,我们能做的只能是减少对数据库的访问。例如,前端发出了100万个请求,通过我们的处理,最终只有10个会访问数据库,这样就会大大提升系统性能。再针对秒杀这种场景,因为秒杀商品的数量是有限的,因此采用上述实现方案。
假如,某个商品可秒杀的数量是10,那么在秒杀活动开始之前,把商品的ID和数量加载到Redis缓存。当服务端收到请求时,首先预减Redis中的数量,如果数量减到小于0时,那么随后的访问直接返回秒杀失败的信息。也就是说,最终只有10个请求会去访问数据库。
如果商品数量比较多,比如1万件商品参与秒杀,那么就有1万*10=10万个请求并发去访问数据库,数据库的压力还是会很大。这里就用到了另外一个非常重要的组件:消息队列。我们不是把请求直接去访问数据库,而是先把请求写到消息队列中,做一个缓存,然后再去慢慢的更新数据库。这样做之后,前端用户的请求可能不会立即得到响应是成功还是失败,很可能得到的是一个排队中的返回值,这个时候,需要客户端去服务端轮询,因为我们不能保证一定就秒杀成功了。当服务端出队,生成订单以后,把用户ID和商品ID写到缓存中,来应对客户端的轮询就可以了。这样处理以后,我们的应用是可以很简单的进行分布式横向扩展的,以应对更大的并发。当然,秒杀系统还有很多要处理的事情,比如限流防刷、分布式Session等等。
1、工具准备
rabbitmq安装:https:
界面地址:http://localhost:15672/#/
用户名 guest
密码 guest
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、代码结构
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、测试
新建线程组——新建取样器(http请求)——新建查看结果树
12、测试结果
加载全部内容