Redis搶單預(yù)熱的實(shí)現(xiàn)示例
前言
在當(dāng)今的互聯(lián)網(wǎng)時(shí)代,搶單活動(dòng)已經(jīng)成為了電商平臺(tái)、外賣平臺(tái)等各種電子商務(wù)平臺(tái)中常見的營銷手段。通過搶單活動(dòng),商家可以吸引大量用戶參與,從而提高銷量和知名度。然而,搶單活動(dòng)所帶來的高并發(fā)請(qǐng)求往往會(huì)給系統(tǒng)帶來巨大的壓力,如何在搶單活動(dòng)開始前進(jìn)行預(yù)熱,以確保系統(tǒng)能夠穩(wěn)定運(yùn)行,成為了技術(shù)人員需要解決的重要問題。
在這篇博客中,我們將深入探討如何利用Redis技術(shù)來進(jìn)行搶單預(yù)熱,以應(yīng)對(duì)搶單活動(dòng)帶來的高并發(fā)訪問壓力。我們將介紹Redis的基本概念和特點(diǎn),以及如何利用Redis來進(jìn)行緩存預(yù)熱、數(shù)據(jù)預(yù)加載等操作,從而提高系統(tǒng)的并發(fā)處理能力和穩(wěn)定性。同時(shí),我們也將分享一些實(shí)際案例和經(jīng)驗(yàn),幫助讀者更好地理解和應(yīng)用Redis技術(shù)解決搶單預(yù)熱的挑戰(zhàn)。
通過本文的學(xué)習(xí),讀者將能夠深入了解搶單預(yù)熱的必要性和原理,掌握利用Redis進(jìn)行搶單預(yù)熱的具體方法和技巧,從而為自己的系統(tǒng)應(yīng)對(duì)搶單活動(dòng)帶來的高并發(fā)訪問壓力提供有效的解決方案。讓我們一起深入探討Redis在搶單預(yù)熱中的應(yīng)用吧!
一、前期準(zhǔn)備
1、新建項(xiàng)目,結(jié)構(gòu)如下
2、添加依賴
<dependencies> <!-- 放在最前面依賴,依據(jù)依賴的最短路徑原則, 將不在使用spring-data中的slf4j,否則 會(huì)引發(fā)沖突--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.3.8</version> </dependency> <!-- spring data框架,提供了對(duì)redis的整合支持, 內(nèi)部支持lettuce以及Jedis客戶端--> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.5.6</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.29</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.29</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.29</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.32</version> </dependency> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>6.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.11.2</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> </dependencies>
這個(gè)Maven項(xiàng)目中包含了多個(gè)依賴,以下是每個(gè)依賴的作用:
logback-classic: 這是logback日志框架的經(jīng)典模塊,用于在應(yīng)用程序中進(jìn)行日志記錄和管理。
spring-data-redis: 提供了Spring Data框架對(duì)Redis的整合支持,包括對(duì)lettuce和Jedis客戶端的支持,可以方便地使用Redis進(jìn)行數(shù)據(jù)操作。
javax.servlet-api: 這是Java Servlet API的依賴,提供了對(duì)Servlet的支持,通常在Java Web應(yīng)用中使用。
spring-webmvc: Spring框架的Web MVC模塊,提供了基于MVC架構(gòu)的Web應(yīng)用程序開發(fā)支持。
spring-jdbc: Spring框架的JDBC模塊,提供了對(duì)JDBC的封裝和支持,用于在Spring應(yīng)用中進(jìn)行數(shù)據(jù)庫操作。
spring-tx: Spring框架的事務(wù)管理模塊,提供了聲明式事務(wù)管理的支持。
druid: 阿里巴巴開源的數(shù)據(jù)庫連接池,在應(yīng)用中用于管理數(shù)據(jù)庫連接。
mysql-connector-java: MySQL數(shù)據(jù)庫的JDBC驅(qū)動(dòng),用于連接MySQL數(shù)據(jù)庫。
lettuce-core: Lettuce是一個(gè)高性能的開源Java Redis客戶端,用于與Redis進(jìn)行交互。
lombok: Lombok是一個(gè)Java庫,可以通過注解的方式來簡化Java代碼的編寫,提高開發(fā)效率。
junit: JUnit是一個(gè)Java單元測試框架,用于編寫和運(yùn)行自動(dòng)化的單元測試。
jackson-databind: Jackson是一個(gè)流行的Java JSON處理庫,jackson-databind模塊提供了數(shù)據(jù)綁定功能,用于將Java對(duì)象和JSON數(shù)據(jù)進(jìn)行相互轉(zhuǎn)換。
mybatis: MyBatis是一個(gè)持久層框架,用于在Java應(yīng)用中進(jìn)行數(shù)據(jù)庫操作。
mybatis-spring: MyBatis與Spring框架的整合模塊,提供了MyBatis和Spring框架的無縫集成支持。
這些依賴項(xiàng)涵蓋了日志記錄、Web開發(fā)、數(shù)據(jù)庫操作、緩存操作、測試等多個(gè)方面,可以滿足一個(gè)典型的Java應(yīng)用程序的開發(fā)需求。
二、編寫 dao
由于代碼量太多了,就不一一講解了,本次案例只是講重要的怎么預(yù)熱和減庫存。
1、GoodsDao
public interface GoodsDao { /** * 查詢參與活動(dòng)的商品 * @return */ List<Goods> listProduct(); /** * 減庫存 * @param id * @return */ void decrStock(int id); }
首先要有一個(gè)查詢庫存的方法和一個(gè)刪減庫存的方法。
三、編寫 service
1、OrderService
public interface OrderService { /** * 下單 * @param id */ void placeOrder(int id); }
當(dāng)庫存放生改變時(shí),我們需要為這寫下單的用戶添加訂單記錄。
2、GoodsService
public interface GoodsService { /** * 扣減庫存 * @param id */ void decrStock(int id); }
當(dāng)下單成功后,需要扣減數(shù)據(jù)庫的庫存數(shù)量。
3、GoodsServiceImpl
@Service @Slf4j @RequiredArgsConstructor @Transactional(rollbackFor = RuntimeException.class) public class GoodsServiceImpl implements GoodsService { private final GoodsDao goodsDao; private final RedisTemplate<String, Object> redisTemplate; /** * 緩存預(yù)熱 */ @PostConstruct public void initProductCache() { goodsDao.listProduct().forEach(goods -> { //將商品數(shù)量加入redis緩存 String key = GoodsEnum.PREFIX.value() + goods.getId(); redisTemplate.opsForValue().set(key, goods.getStock(), Duration.ofMinutes(60)); }); } @Override public void decrStock(int id) { goodsDao.decrStock(id); } }
這段代碼是一個(gè)名為GoodsServiceImpl的服務(wù)類,使用了Lombok的@RequiredArgsConstructor注解來自動(dòng)生成構(gòu)造函數(shù),并且使用了Slf4j來實(shí)現(xiàn)日志記錄。同時(shí),@Service注解表明這是一個(gè)Spring的服務(wù)類,@Transactional注解表明這個(gè)類中的方法將進(jìn)行事務(wù)管理,并且在遇到RuntimeException時(shí)進(jìn)行回滾。
這個(gè)類中有兩個(gè)成員變量:GoodsDao和RedisTemplate。GoodsDao是一個(gè)數(shù)據(jù)訪問對(duì)象,用于對(duì)商品數(shù)據(jù)進(jìn)行持久化操作;而RedisTemplate是Spring提供的用于操作Redis的模板類。
在這個(gè)類中,有一個(gè)@PostConstruct注解的方法initProductCache(),它在類實(shí)例化后會(huì)被自動(dòng)調(diào)用。這個(gè)方法通過goodsDao.listProduct()獲取所有商品,并將它們的庫存數(shù)量加入到Redis緩存中,以實(shí)現(xiàn)商品的緩存預(yù)熱。對(duì)于每個(gè)商品,它會(huì)將商品的id作為key,庫存數(shù)量作為value存入Redis,并設(shè)置了緩存的有效期為60分鐘。
另外,這個(gè)類還實(shí)現(xiàn)了GoodsService接口,其中包含了decrStock(int id)方法,用于減少商品庫存。在這個(gè)方法中,它調(diào)用了goodsDao.decrStock(id)來實(shí)現(xiàn)對(duì)商品庫存的減少操作。
總的來說,這個(gè)類主要負(fù)責(zé)商品庫存的管理,通過緩存預(yù)熱來提高系統(tǒng)性能,并且在減少商品庫存時(shí)進(jìn)行事務(wù)管理。
4、OrderServiceImpl
@Service @Slf4j @RequiredArgsConstructor @Transactional public class OrderServiceImpl implements OrderService { private final RedisTemplate<String, Object> redisTemplate; private final OrderDao orderDao; private final GoodsDao goodsDao; /** * 下單 * @param id */ @Override public void placeOrder(int id) { //扣減庫存 decrCacheStock(id); //生成訂單 createOrder(id); //同步數(shù)據(jù)庫的庫存 goodsDao.decrStock(id); } /** * 在緩存中扣減庫存 * @param id */ private void decrCacheStock(int id) { //扣減庫存(原子減),并返回剩余庫存量 long stock = redisTemplate.opsForValue().decrement(GoodsEnum.PREFIX.value() + id); //如果redis中庫存為0,則拋出異常告訴用戶已經(jīng)售罄 if(stock < 0) { //在并發(fā)時(shí)redis扣減后的庫存為負(fù)數(shù),因此要將redis自增回來 redisTemplate.opsForValue().increment(GoodsEnum.PREFIX.value() + id); throw new OrderException(ErrorMessageEnum.SELL_OUT); } } /** * 生成訂單 * @param gid */ private Order createOrder(int gid) { try { Order order = new Order(); //用戶ID order.setUserId(1); //商品ID order.setGoodsId(gid); //0表示未支付 order.setStatus(0); orderDao.save(order); return order; } catch (Exception e) { log.error(e.getMessage(), e); throw new RuntimeException(e); } } }
這段代碼是一個(gè)名為OrderServiceImpl的服務(wù)類,同樣使用了Lombok的@RequiredArgsConstructor注解來自動(dòng)生成構(gòu)造函數(shù),并且使用了@Slf4j來實(shí)現(xiàn)日志記錄。同時(shí),@Service注解表明這是一個(gè)Spring的服務(wù)類,@Transactional注解表明這個(gè)類中的方法將進(jìn)行事務(wù)管理。
這個(gè)類中有三個(gè)成員變量:RedisTemplate用于操作Redis緩存,OrderDao用于對(duì)訂單數(shù)據(jù)進(jìn)行持久化操作,GoodsDao用于對(duì)商品數(shù)據(jù)進(jìn)行持久化操作。
在這個(gè)類中,有一個(gè)placeOrder(int id)方法,用于處理下單操作。在這個(gè)方法中,首先調(diào)用了decrCacheStock(int id)方法來扣減商品的庫存,然后調(diào)用了createOrder(int id)方法來生成訂單,最后調(diào)用了goodsDao.decrStock(id)方法來同步數(shù)據(jù)庫中的庫存信息。
在decrCacheStock(int id)方法中,它使用了RedisTemplate來實(shí)現(xiàn)對(duì)Redis緩存中商品庫存的扣減操作,并且通過判斷庫存是否小于0來判斷商品是否售罄,如果售罄則拋出OrderException異常。
在createOrder(int gid)方法中,它創(chuàng)建了一個(gè)訂單對(duì)象,并將訂單信息存入數(shù)據(jù)庫中。如果在存入數(shù)據(jù)庫時(shí)出現(xiàn)異常,它會(huì)記錄錯(cuò)誤日志并拋出RuntimeException異常。
總的來說,這個(gè)類主要負(fù)責(zé)處理訂單的生成和庫存的扣減操作,通過調(diào)用RedisTemplate來實(shí)現(xiàn)對(duì)Redis緩存的操作,并且在數(shù)據(jù)庫操作時(shí)進(jìn)行事務(wù)管理。
四、編寫controller
@RestController @RequiredArgsConstructor public class OrderController extends BaseController{ private final OrderService orderService; @PostMapping("/seckill") public ResultVO placeOrder(Integer gid) { orderService.placeOrder(2); return success(); } }
這段代碼是一個(gè)名為OrderController的控制器類,使用了Lombok的@RequiredArgsConstructor注解來自動(dòng)生成構(gòu)造函數(shù),并且繼承了BaseController。同時(shí),@RestController注解表明這是一個(gè)Spring的RESTful控制器類。
在這個(gè)類中,有一個(gè)成員變量OrderService,用于處理訂單相關(guān)的業(yè)務(wù)邏輯。在控制器中,有一個(gè)@PostMapping注解的方法placeOrder(Integer gid),用于處理秒殺下單的請(qǐng)求。在這個(gè)方法中,它調(diào)用了orderService.placeOrder(2)來處理下單操作,并且返回了一個(gè)ResultVO對(duì)象,通過success()方法來表示操作成功。
總的來說,這個(gè)控制器類主要用于處理秒殺下單的請(qǐng)求,通過調(diào)用OrderService來實(shí)現(xiàn)下單操作,并返回相應(yīng)的結(jié)果。
五、使用jmeter測試
官網(wǎng)網(wǎng)址:Apache JMeter - Apache JMeter™
去官網(wǎng)下載下來,我們用 jmeter 來測試我們的controller。
1、jmeter有什么用
JMeter是一個(gè)用于進(jìn)行性能測試的開源工具,它最初是為測試Web應(yīng)用程序而設(shè)計(jì)的,但后來擴(kuò)展到其他測試領(lǐng)域。JMeter的主要用途包括:
性能測試:JMeter可以模擬多個(gè)并發(fā)用戶對(duì)目標(biāo)系統(tǒng)(如Web服務(wù)器、數(shù)據(jù)庫、FTP服務(wù)器等)發(fā)起請(qǐng)求,以評(píng)估系統(tǒng)的性能和穩(wěn)定性。它可以測量系統(tǒng)在不同負(fù)載下的響應(yīng)時(shí)間、吞吐量和并發(fā)用戶數(shù)等指標(biāo),幫助開發(fā)人員和測試人員發(fā)現(xiàn)系統(tǒng)性能方面的問題。
負(fù)載測試:通過模擬大量用戶請(qǐng)求,JMeter可以測試系統(tǒng)在高負(fù)載情況下的表現(xiàn),評(píng)估系統(tǒng)的承載能力和性能瓶頸,以便確定系統(tǒng)是否能夠滿足預(yù)期的用戶需求。負(fù)載測試也可以用于驗(yàn)證系統(tǒng)的可伸縮性和穩(wěn)定性。
壓力測試:JMeter可以模擬系統(tǒng)在正?;虍惓X?fù)載下的表現(xiàn),以便評(píng)估系統(tǒng)在不同壓力下的穩(wěn)定性和可靠性。通過壓力測試,可以發(fā)現(xiàn)系統(tǒng)在極端情況下可能出現(xiàn)的問題,如內(nèi)存泄漏、資源競爭等。
功能測試:除了性能測試,JMeter也可以用于進(jìn)行功能測試,例如測試網(wǎng)站的登錄、注冊、搜索等功能,以及測試API的響應(yīng)等。
總的來說,JMeter是一個(gè)功能強(qiáng)大的測試工具,可以幫助開發(fā)人員和測試人員進(jìn)行性能、負(fù)載、壓力和功能測試,以確保系統(tǒng)能夠穩(wěn)定、高效地運(yùn)行。
2、測試
1)打開 jmeter ,bin目錄下雙擊ApacheJMeter.jar 運(yùn)行
運(yùn)行:
2)添加線程組
3)添加HTTP請(qǐng)求
4)添加匯總報(bào)告
5)填寫信息
添加循環(huán)數(shù)量,我們的庫存中有100個(gè)庫存,我們執(zhí)行150次,看會(huì)不會(huì)出現(xiàn)超賣的情況。還是售完了直接就拋異常。
填寫 HTTP 協(xié)議
請(qǐng)求路徑不要寫錯(cuò)了,還有就是請(qǐng)求的方式是什么就選擇什么。
6)測試結(jié)果
當(dāng)運(yùn)行測試后,售完100個(gè)數(shù)量之后并沒有出現(xiàn)超賣的現(xiàn)象,證明我們的代碼就沒有寫錯(cuò),并且在售完之后直接提示用戶商品已售完。
六、gitee 案例
地址:ch02 · qiuqiu/Redis-study - 碼云 - 開源中國 (gitee.com)
到此這篇關(guān)于Redis搶單預(yù)熱的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Redis搶單預(yù)熱內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis 限制內(nèi)存使用大小的實(shí)現(xiàn)
這篇文章主要介紹了redis 限制內(nèi)存使用大小的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05Redis分布式鎖如何自動(dòng)續(xù)期的實(shí)現(xiàn)
本文主要介紹了Redis分布式鎖如何自動(dòng)續(xù)期的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12redis快速部署為docker容器的方法實(shí)現(xiàn)
部署 Redis 作為 Docker 容器是一種快速、靈活且可重復(fù)使用的方式,特別適合開發(fā)、測試和部署環(huán)境,本文主要介紹了redis快速部署為docker容器的方法實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-05-05Redis migrate數(shù)據(jù)遷移工具的使用教程
這篇文章主要給大家介紹了關(guān)于Redis migrate數(shù)據(jù)遷移工具的使用教程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08