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