springboot?+rabbitmq+redis實(shí)現(xiàn)秒殺示例
實(shí)現(xiàn)說明

這里的核心在于如何在大并發(fā)的情況下保證數(shù)據(jù)庫能扛得住壓力,因?yàn)榇蟛l(fā)的瓶頸在于數(shù)據(jù)庫。如果用戶的請(qǐng)求直接從前端傳到數(shù)據(jù)庫,顯然,數(shù)據(jù)庫是無法承受幾十萬上百萬甚至上千萬的并發(fā)量的。因此,我們能做的只能是減少對(duì)數(shù)據(jù)庫的訪問。例如,前端發(fā)出了100萬個(gè)請(qǐng)求,通過我們的處理,最終只有10個(gè)會(huì)訪問數(shù)據(jù)庫,這樣就會(huì)大大提升系統(tǒng)性能。再針對(duì)秒殺這種場(chǎng)景,因?yàn)槊霘⑸唐返臄?shù)量是有限的,因此采用上述實(shí)現(xiàn)方案。
假如,某個(gè)商品可秒殺的數(shù)量是10,那么在秒殺活動(dòng)開始之前,把商品的ID和數(shù)量加載到Redis緩存。當(dāng)服務(wù)端收到請(qǐng)求時(shí),首先預(yù)減Redis中的數(shù)量,如果數(shù)量減到小于0時(shí),那么隨后的訪問直接返回秒殺失敗的信息。也就是說,最終只有10個(gè)請(qǐng)求會(huì)去訪問數(shù)據(jù)庫。
如果商品數(shù)量比較多,比如1萬件商品參與秒殺,那么就有1萬*10=10萬個(gè)請(qǐng)求并發(fā)去訪問數(shù)據(jù)庫,數(shù)據(jù)庫的壓力還是會(huì)很大。這里就用到了另外一個(gè)非常重要的組件:消息隊(duì)列。我們不是把請(qǐng)求直接去訪問數(shù)據(jù)庫,而是先把請(qǐng)求寫到消息隊(duì)列中,做一個(gè)緩存,然后再去慢慢的更新數(shù)據(jù)庫。這樣做之后,前端用戶的請(qǐng)求可能不會(huì)立即得到響應(yīng)是成功還是失敗,很可能得到的是一個(gè)排隊(duì)中的返回值,這個(gè)時(shí)候,需要客戶端去服務(wù)端輪詢,因?yàn)槲覀儾荒鼙WC一定就秒殺成功了。當(dāng)服務(wù)端出隊(duì),生成訂單以后,把用戶ID和商品ID寫到緩存中,來應(yīng)對(duì)客戶端的輪詢就可以了。這樣處理以后,我們的應(yīng)用是可以很簡(jiǎn)單的進(jìn)行分布式橫向擴(kuò)展的,以應(yīng)對(duì)更大的并發(fā)。當(dāng)然,秒殺系統(tǒng)還有很多要處理的事情,比如限流防刷、分布式Session等等。
1、工具準(zhǔn)備
rabbitmq安裝:http://www.dhdzp.com/article/253706.htm
界面地址:http://localhost:15672/#/
用戶名 guest
密碼 guest

redis安裝:http://www.dhdzp.com/article/145704.htm
jmeter安裝:http://www.dhdzp.com/article/232152.htm
2、數(shù)據(jù)表
商品表
-- ---------------------------- -- 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 '樂觀鎖,版本號(hào)', 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 '創(chuàng)建時(shí)間', 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、代碼結(jié)構(gòu)

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 {
//庫存交換機(jī)
public static final String STORY_EXCHANGE = "STORY_EXCHANGE";
//訂單交換機(jī)
public static final String ORDER_EXCHANGE = "ORDER_EXCHANGE";
//庫存隊(duì)列
public static final String STORY_QUEUE = "STORY_QUEUE";
//訂單隊(duì)列
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();
}
//創(chuàng)建庫存交換機(jī)
@Bean
public Exchange getStoryExchange() {
return ExchangeBuilder.directExchange(STORY_EXCHANGE).durable(true).build();
}
//創(chuàng)建庫存隊(duì)列
@Bean
public Queue getStoryQueue() {
return new Queue(STORY_QUEUE);
}
//庫存交換機(jī)和庫存隊(duì)列綁定
@Bean
public Binding bindStory() {
return BindingBuilder.bind(getStoryQueue()).to(getStoryExchange()).with(STORY_ROUTING_KEY).noargs();
}
//創(chuàng)建訂單隊(duì)列
@Bean
public Queue getOrderQueue() {
return new Queue(ORDER_QUEUE);
}
//創(chuàng)建訂單交換機(jī)
@Bean
public Exchange getOrderExchange() {
return ExchangeBuilder.directExchange(ORDER_EXCHANGE).durable(true).build();
}
//訂單隊(duì)列與訂單交換機(jī)進(jìn)行綁定
@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、訂單業(yè)務(wù)層
接口層
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);
}
實(shí)現(xiàn)層
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 訂單實(shí)現(xiàn)
* 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) {
//校驗(yàn)庫存
Stock stock = checkStock(id);
// if(stock.getCount()>0){
// System.out.println("當(dāng)前庫存:" + stock.getCount());
// //扣庫存
// if(updateSale(stock) == 1){
//
// }else {
// return 0;
// }
// }
// return 0;
return createOrder(stock);
}
@Override
public void decrByStock(Integer id){
//校驗(yàn)庫存
Stock stock = checkStock(id);
if(stock.getCount()>0){
System.out.println("當(dāng)前庫存:" + stock.getCount());
//扣庫存
updateSale(stock);
}
}
//校驗(yàn)庫存
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實(shí)現(xiàn)層
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;
/**
* 設(shè)置String鍵值對(duì)
* @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);
}
/**
* 對(duì)指定key的鍵值減一
* @param key
* @return
*/
public Long decrBy(String key) {
return redisTemplate.opsForValue().decrement(key);
}
}
8、mq實(shí)現(xiàn)層
減庫存
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;
/**
* 監(jiān)聽?zhēng)齑嫦㈥?duì)列,并消費(fèi)
* @param id
*/
@RabbitListener(queues = MyRabbitMQConfig.STORY_QUEUE)
public void decrByStock(Integer id) {
/**
* 調(diào)用數(shù)據(jù)庫service給數(shù)據(jù)庫對(duì)應(yīng)商品庫存減一
*/
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 訂單隊(duì)列
* Version 1.0
*/
@Service
@Slf4j
public class MQOrderService {
@Autowired
private StockOrderService orderService;
/**
* 監(jiān)聽訂單消息隊(duì)列,并消費(fèi)
*
* @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+消息隊(duì)列進(jìn)行秒殺實(shí)現(xiàn)
*
* @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;
//調(diào)用redis給相應(yīng)商品庫存量減一
Long decrByResult = redisService.decrBy(id+"");
if (decrByResult >= 0) {
/**
* 說明該商品的庫存量有剩余,可以進(jìn)行下訂單操作
*/
//發(fā)消息給庫存消息隊(duì)列,將庫存數(shù)據(jù)減一
rabbitTemplate.convertAndSend(MyRabbitMQConfig.STORY_EXCHANGE, MyRabbitMQConfig.STORY_ROUTING_KEY, id);
//發(fā)消息給訂單消息隊(duì)列,創(chuàng)建訂單
rabbitTemplate.convertAndSend(MyRabbitMQConfig.ORDER_EXCHANGE, MyRabbitMQConfig.ORDER_ROUTING_KEY, id);
message = "商品" + id + "秒殺成功";
} else {
/**
* 說明該商品的庫存量沒有剩余,直接返回秒殺失敗的消息給用戶
*/
message ="商品" + id + "秒殺商品的庫存量沒有剩余,秒殺結(jié)束";
}
return message;
}
11、測(cè)試
新建線程組——新建取樣器(http請(qǐng)求)——新建查看結(jié)果樹

12、測(cè)試結(jié)果



到此這篇關(guān)于springboot +rabbitmq+redis實(shí)現(xiàn)秒殺的文章就介紹到這了,更多相關(guān)springboot rabbitmq redis秒殺內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot+Redis隊(duì)列實(shí)現(xiàn)Java版秒殺的示例代碼
- Springboot+redis+Vue實(shí)現(xiàn)秒殺的項(xiàng)目實(shí)踐
- SpringBoot+RabbitMQ+Redis實(shí)現(xiàn)商品秒殺的示例代碼
- 基于Redis結(jié)合SpringBoot的秒殺案例詳解
- SpringBoot之使用Redis實(shí)現(xiàn)分布式鎖(秒殺系統(tǒng))
- SpringBoot使用Redisson實(shí)現(xiàn)分布式鎖(秒殺系統(tǒng))
- springboot集成redis實(shí)現(xiàn)簡(jiǎn)單秒殺系統(tǒng)
- 基于SpringBoot+Redis+Lua 實(shí)現(xiàn)高并發(fā)秒殺系統(tǒng)
相關(guān)文章
springmvc實(shí)現(xiàn)json交互-requestBody和responseBody
本文主要介紹了springmvc實(shí)現(xiàn)json交互-requestBody和responseBody的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-03-03
Java concurrency之AtomicLongFieldUpdater原子類_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
AtomicLongFieldUpdater可以對(duì)指定"類的 'volatile long'類型的成員"進(jìn)行原子更新。它是基于反射原理實(shí)現(xiàn)的。下面通過本文給大家分享Java concurrency之AtomicLongFieldUpdater原子類的相關(guān)知識(shí),感興趣的朋友一起看看吧2017-06-06
Java常用類庫Apache Commons工具類說明及使用實(shí)例詳解
這篇文章主要介紹了Java常用類庫Apache Commons工具類說明及使用實(shí)例詳解,需要的朋友可以參考下2020-02-02
Eclipse連接Mysql數(shù)據(jù)庫操作總結(jié)
這篇文章主要介紹了Eclipse連接Mysql數(shù)據(jù)庫操作總結(jié)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08
spring boot自定義配置時(shí)在yml文件輸入有提示問題及解決方案
自定義一個(gè)配置類,然后在yml文件具體配置值時(shí),一般不會(huì)有提示,今天小編給大家分享spring boot自定義配置時(shí)在yml文件輸入有提示問題,感興趣的朋友一起看看吧2023-10-10
SpringBoot動(dòng)態(tài)更新yml文件
在系統(tǒng)運(yùn)行過程中,可能由于一些配置項(xiàng)的簡(jiǎn)單變動(dòng)需要重新打包啟停項(xiàng)目,這對(duì)于在運(yùn)行中的項(xiàng)目會(huì)造成數(shù)據(jù)丟失,客戶操作無響應(yīng)等情況發(fā)生,針對(duì)這類情況對(duì)開發(fā)框架進(jìn)行升級(jí)提供yml文件實(shí)時(shí)修改更新功能,這篇文章主要介紹了SpringBoot動(dòng)態(tài)更新yml文件2023-01-01
SpringBoot和VUE源碼直接整合打包成jar的踩坑記錄
這篇文章主要介紹了SpringBoot和VUE源碼直接整合打包成jar的踩坑記錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03

