springboot?+rabbitmq+redis實(shí)現(xiàn)秒殺示例
實(shí)現(xiàn)說(shuō)明
這里的核心在于如何在大并發(fā)的情況下保證數(shù)據(jù)庫(kù)能扛得住壓力,因?yàn)榇蟛l(fā)的瓶頸在于數(shù)據(jù)庫(kù)。如果用戶的請(qǐng)求直接從前端傳到數(shù)據(jù)庫(kù),顯然,數(shù)據(jù)庫(kù)是無(wú)法承受幾十萬(wàn)上百萬(wàn)甚至上千萬(wàn)的并發(fā)量的。因此,我們能做的只能是減少對(duì)數(shù)據(jù)庫(kù)的訪問(wèn)。例如,前端發(fā)出了100萬(wàn)個(gè)請(qǐng)求,通過(guò)我們的處理,最終只有10個(gè)會(huì)訪問(wèn)數(shù)據(jù)庫(kù),這樣就會(huì)大大提升系統(tǒng)性能。再針對(duì)秒殺這種場(chǎng)景,因?yàn)槊霘⑸唐返臄?shù)量是有限的,因此采用上述實(shí)現(xiàn)方案。
假如,某個(gè)商品可秒殺的數(shù)量是10,那么在秒殺活動(dòng)開(kāi)始之前,把商品的ID和數(shù)量加載到Redis緩存。當(dāng)服務(wù)端收到請(qǐng)求時(shí),首先預(yù)減Redis中的數(shù)量,如果數(shù)量減到小于0時(shí),那么隨后的訪問(wèn)直接返回秒殺失敗的信息。也就是說(shuō),最終只有10個(gè)請(qǐng)求會(huì)去訪問(wèn)數(shù)據(jù)庫(kù)。
如果商品數(shù)量比較多,比如1萬(wàn)件商品參與秒殺,那么就有1萬(wàn)*10=10萬(wàn)個(gè)請(qǐng)求并發(fā)去訪問(wèn)數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)的壓力還是會(huì)很大。這里就用到了另外一個(gè)非常重要的組件:消息隊(duì)列。我們不是把請(qǐng)求直接去訪問(wèn)數(shù)據(jù)庫(kù),而是先把請(qǐng)求寫到消息隊(duì)列中,做一個(gè)緩存,然后再去慢慢的更新數(shù)據(jù)庫(kù)。這樣做之后,前端用戶的請(qǐng)求可能不會(huì)立即得到響應(yīng)是成功還是失敗,很可能得到的是一個(gè)排隊(duì)中的返回值,這個(gè)時(shí)候,需要客戶端去服務(wù)端輪詢,因?yàn)槲覀儾荒鼙WC一定就秒殺成功了。當(dāng)服務(wù)端出隊(duì),生成訂單以后,把用戶ID和商品ID寫到緩存中,來(lái)應(yīng)對(duì)客戶端的輪詢就可以了。這樣處理以后,我們的應(yīng)用是可以很簡(jiǎn)單的進(jìn)行分布式橫向擴(kuò)展的,以應(yīng)對(duì)更大的并發(fā)。當(dāng)然,秒殺系統(tǒng)還有很多要處理的事情,比如限流防刷、分布式Session等等。
1、工具準(zhǔn)備
rabbitmq安裝:http://chabaoo.cn/article/253706.htm
界面地址:http://localhost:15672/#/
用戶名 guest
密碼 guest
redis安裝:http://chabaoo.cn/article/145704.htm
jmeter安裝:http://chabaoo.cn/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 '庫(kù)存', `sale` int(11) NOT NULL COMMENT '已售', `version` int(11) NOT NULL COMMENT '樂(lè)觀鎖,版本號(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 '庫(kù)存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 { //庫(kù)存交換機(jī) public static final String STORY_EXCHANGE = "STORY_EXCHANGE"; //訂單交換機(jī) public static final String ORDER_EXCHANGE = "ORDER_EXCHANGE"; //庫(kù)存隊(duì)列 public static final String STORY_QUEUE = "STORY_QUEUE"; //訂單隊(duì)列 public static final String ORDER_QUEUE = "ORDER_QUEUE"; //庫(kù)存路由鍵 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)建庫(kù)存交換機(jī) @Bean public Exchange getStoryExchange() { return ExchangeBuilder.directExchange(STORY_EXCHANGE).durable(true).build(); } //創(chuàng)建庫(kù)存隊(duì)列 @Bean public Queue getStoryQueue() { return new Queue(STORY_QUEUE); } //庫(kù)存交換機(jī)和庫(kù)存隊(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)庫(kù)存 Stock stock = checkStock(id); // if(stock.getCount()>0){ // System.out.println("當(dāng)前庫(kù)存:" + stock.getCount()); // //扣庫(kù)存 // if(updateSale(stock) == 1){ // // }else { // return 0; // } // } // return 0; return createOrder(stock); } @Override public void decrByStock(Integer id){ //校驗(yàn)庫(kù)存 Stock stock = checkStock(id); if(stock.getCount()>0){ System.out.println("當(dāng)前庫(kù)存:" + stock.getCount()); //扣庫(kù)存 updateSale(stock); } } //校驗(yàn)庫(kù)存 private Stock checkStock(Integer id) { return stockMapper.checkStock(id); } //扣庫(kù)存 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)層
減庫(kù)存
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)聽(tīng)?zhēng)齑嫦㈥?duì)列,并消費(fèi) * @param id */ @RabbitListener(queues = MyRabbitMQConfig.STORY_QUEUE) public void decrByStock(Integer id) { /** * 調(diào)用數(shù)據(jù)庫(kù)service給數(shù)據(jù)庫(kù)對(duì)應(yīng)商品庫(kù)存減一 */ log.info("減庫(kù)存"); 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)聽(tīng)訂單消息隊(duì)列,并消費(fèi) * * @param id */ @RabbitListener(queues = MyRabbitMQConfig.ORDER_QUEUE) public void createOrder(Integer id) { log.info("收到訂單消息"); orderService.createOrder(id); } }
9、redis模擬初始化庫(kù)存量
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初始化商品的庫(kù)存量和信息 * @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)商品庫(kù)存量減一 Long decrByResult = redisService.decrBy(id+""); if (decrByResult >= 0) { /** * 說(shuō)明該商品的庫(kù)存量有剩余,可以進(jìn)行下訂單操作 */ //發(fā)消息給庫(kù)存消息隊(duì)列,將庫(kù)存數(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 { /** * 說(shuō)明該商品的庫(kù)存量沒(méi)有剩余,直接返回秒殺失敗的消息給用戶 */ message ="商品" + id + "秒殺商品的庫(kù)存量沒(méi)有剩余,秒殺結(jié)束"; } return message; }
11、測(cè)試
新建線程組——新建取樣器(http請(qǐng)求)——新建查看結(jié)果樹(shù)
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+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隊(duì)列實(shí)現(xiàn)Java版秒殺的示例代碼
相關(guān)文章
Java業(yè)務(wù)校驗(yàn)工具實(shí)現(xiàn)方法
這篇文章主要介紹了Java業(yè)務(wù)校驗(yàn)工具實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06支付寶APP支付(IOS手機(jī)端+java后臺(tái))版
這篇文章主要為大家詳細(xì)介紹了支付寶APP支付(IOS手機(jī)端+java后臺(tái))版,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05jbuilder2006連接sqlserver2000的方法
xp jbuiler2006 連接SQL SERVER2000的問(wèn)題2008-10-10淺談Ribbon、Feign和OpenFeign的區(qū)別
這篇文章主要介紹了淺談Ribbon、Feign和OpenFeign的區(qū)別。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06利用Spring Cloud Config結(jié)合Bus實(shí)現(xiàn)分布式配置中心的步驟
這篇文章主要介紹了利用Spring Cloud Config結(jié)合Bus實(shí)現(xiàn)分布式配置中心的相關(guān)資料,文中通過(guò)示例代碼將實(shí)現(xiàn)的步驟一步步介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友下面來(lái)一起看看吧2018-05-05Java中shiro框架和security框架的區(qū)別
這篇文章主要介紹了Java中shiro框架和security框架的區(qū)別,shiro和security作為兩款流行的功能強(qiáng)大的且易于使用的java安全認(rèn)證框架,在近些年中的項(xiàng)目開(kāi)發(fā)過(guò)程中使用廣泛,今天我們就來(lái)一起了解一下兩者的區(qū)別2023-08-08