Redis搶單預(yù)熱的實(shí)現(xiàn)示例
前言
在當(dāng)今的互聯(lián)網(wǎng)時(shí)代,搶單活動(dòng)已經(jīng)成為了電商平臺(tái)、外賣平臺(tái)等各種電子商務(wù)平臺(tái)中常見(jiàn)的營(yíng)銷手段。通過(guò)搶單活動(dòng),商家可以吸引大量用戶參與,從而提高銷量和知名度。然而,搶單活動(dòng)所帶來(lái)的高并發(fā)請(qǐng)求往往會(huì)給系統(tǒng)帶來(lái)巨大的壓力,如何在搶單活動(dòng)開(kāi)始前進(jìn)行預(yù)熱,以確保系統(tǒng)能夠穩(wěn)定運(yùn)行,成為了技術(shù)人員需要解決的重要問(wèn)題。
在這篇博客中,我們將深入探討如何利用Redis技術(shù)來(lái)進(jìn)行搶單預(yù)熱,以應(yīng)對(duì)搶單活動(dòng)帶來(lái)的高并發(fā)訪問(wèn)壓力。我們將介紹Redis的基本概念和特點(diǎn),以及如何利用Redis來(lái)進(jìn)行緩存預(yù)熱、數(shù)據(jù)預(yù)加載等操作,從而提高系統(tǒng)的并發(fā)處理能力和穩(wěn)定性。同時(shí),我們也將分享一些實(shí)際案例和經(jīng)驗(yàn),幫助讀者更好地理解和應(yīng)用Redis技術(shù)解決搶單預(yù)熱的挑戰(zhàn)。
通過(guò)本文的學(xué)習(xí),讀者將能夠深入了解搶單預(yù)熱的必要性和原理,掌握利用Redis進(jìn)行搶單預(yù)熱的具體方法和技巧,從而為自己的系統(tǒng)應(yīng)對(duì)搶單活動(dòng)帶來(lái)的高并發(fā)訪問(wèn)壓力提供有效的解決方案。讓我們一起深入探討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)用程序開(kāi)發(fā)支持。
spring-jdbc: Spring框架的JDBC模塊,提供了對(duì)JDBC的封裝和支持,用于在Spring應(yīng)用中進(jìn)行數(shù)據(jù)庫(kù)操作。
spring-tx: Spring框架的事務(wù)管理模塊,提供了聲明式事務(wù)管理的支持。
druid: 阿里巴巴開(kāi)源的數(shù)據(jù)庫(kù)連接池,在應(yīng)用中用于管理數(shù)據(jù)庫(kù)連接。
mysql-connector-java: MySQL數(shù)據(jù)庫(kù)的JDBC驅(qū)動(dòng),用于連接MySQL數(shù)據(jù)庫(kù)。
lettuce-core: Lettuce是一個(gè)高性能的開(kāi)源Java Redis客戶端,用于與Redis進(jìn)行交互。
lombok: Lombok是一個(gè)Java庫(kù),可以通過(guò)注解的方式來(lái)簡(jiǎn)化Java代碼的編寫(xiě),提高開(kāi)發(fā)效率。
junit: JUnit是一個(gè)Java單元測(cè)試框架,用于編寫(xiě)和運(yùn)行自動(dòng)化的單元測(cè)試。
jackson-databind: Jackson是一個(gè)流行的Java JSON處理庫(kù),jackson-databind模塊提供了數(shù)據(jù)綁定功能,用于將Java對(duì)象和JSON數(shù)據(jù)進(jìn)行相互轉(zhuǎn)換。
mybatis: MyBatis是一個(gè)持久層框架,用于在Java應(yīng)用中進(jìn)行數(shù)據(jù)庫(kù)操作。
mybatis-spring: MyBatis與Spring框架的整合模塊,提供了MyBatis和Spring框架的無(wú)縫集成支持。
這些依賴項(xiàng)涵蓋了日志記錄、Web開(kāi)發(fā)、數(shù)據(jù)庫(kù)操作、緩存操作、測(cè)試等多個(gè)方面,可以滿足一個(gè)典型的Java應(yīng)用程序的開(kāi)發(fā)需求。
二、編寫(xiě) dao
由于代碼量太多了,就不一一講解了,本次案例只是講重要的怎么預(yù)熱和減庫(kù)存。
1、GoodsDao
public interface GoodsDao {
/**
* 查詢參與活動(dòng)的商品
* @return
*/
List<Goods> listProduct();
/**
* 減庫(kù)存
* @param id
* @return
*/
void decrStock(int id);
}首先要有一個(gè)查詢庫(kù)存的方法和一個(gè)刪減庫(kù)存的方法。
三、編寫(xiě) service
1、OrderService
public interface OrderService {
/**
* 下單
* @param id
*/
void placeOrder(int id);
}
當(dāng)庫(kù)存放生改變時(shí),我們需要為這寫(xiě)下單的用戶添加訂單記錄。
2、GoodsService
public interface GoodsService {
/**
* 扣減庫(kù)存
* @param id
*/
void decrStock(int id);
}
當(dāng)下單成功后,需要扣減數(shù)據(jù)庫(kù)的庫(kù)存數(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注解來(lái)自動(dòng)生成構(gòu)造函數(shù),并且使用了Slf4j來(lái)實(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ù)訪問(wèn)對(duì)象,用于對(duì)商品數(shù)據(jù)進(jìn)行持久化操作;而RedisTemplate是Spring提供的用于操作Redis的模板類。
在這個(gè)類中,有一個(gè)@PostConstruct注解的方法initProductCache(),它在類實(shí)例化后會(huì)被自動(dòng)調(diào)用。這個(gè)方法通過(guò)goodsDao.listProduct()獲取所有商品,并將它們的庫(kù)存數(shù)量加入到Redis緩存中,以實(shí)現(xiàn)商品的緩存預(yù)熱。對(duì)于每個(gè)商品,它會(huì)將商品的id作為key,庫(kù)存數(shù)量作為value存入Redis,并設(shè)置了緩存的有效期為60分鐘。
另外,這個(gè)類還實(shí)現(xiàn)了GoodsService接口,其中包含了decrStock(int id)方法,用于減少商品庫(kù)存。在這個(gè)方法中,它調(diào)用了goodsDao.decrStock(id)來(lái)實(shí)現(xiàn)對(duì)商品庫(kù)存的減少操作。
總的來(lái)說(shuō),這個(gè)類主要負(fù)責(zé)商品庫(kù)存的管理,通過(guò)緩存預(yù)熱來(lái)提高系統(tǒng)性能,并且在減少商品庫(kù)存時(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) {
//扣減庫(kù)存
decrCacheStock(id);
//生成訂單
createOrder(id);
//同步數(shù)據(jù)庫(kù)的庫(kù)存
goodsDao.decrStock(id);
}
/**
* 在緩存中扣減庫(kù)存
* @param id
*/
private void decrCacheStock(int id) {
//扣減庫(kù)存(原子減),并返回剩余庫(kù)存量
long stock = redisTemplate.opsForValue().decrement(GoodsEnum.PREFIX.value() + id);
//如果redis中庫(kù)存為0,則拋出異常告訴用戶已經(jīng)售罄
if(stock < 0) {
//在并發(fā)時(shí)redis扣減后的庫(kù)存為負(fù)數(shù),因此要將redis自增回來(lái)
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注解來(lái)自動(dòng)生成構(gòu)造函數(shù),并且使用了@Slf4j來(lái)實(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)方法來(lái)扣減商品的庫(kù)存,然后調(diào)用了createOrder(int id)方法來(lái)生成訂單,最后調(diào)用了goodsDao.decrStock(id)方法來(lái)同步數(shù)據(jù)庫(kù)中的庫(kù)存信息。
在decrCacheStock(int id)方法中,它使用了RedisTemplate來(lái)實(shí)現(xiàn)對(duì)Redis緩存中商品庫(kù)存的扣減操作,并且通過(guò)判斷庫(kù)存是否小于0來(lái)判斷商品是否售罄,如果售罄則拋出OrderException異常。
在createOrder(int gid)方法中,它創(chuàng)建了一個(gè)訂單對(duì)象,并將訂單信息存入數(shù)據(jù)庫(kù)中。如果在存入數(shù)據(jù)庫(kù)時(shí)出現(xiàn)異常,它會(huì)記錄錯(cuò)誤日志并拋出RuntimeException異常。
總的來(lái)說(shuō),這個(gè)類主要負(fù)責(zé)處理訂單的生成和庫(kù)存的扣減操作,通過(guò)調(diào)用RedisTemplate來(lái)實(shí)現(xiàn)對(duì)Redis緩存的操作,并且在數(shù)據(jù)庫(kù)操作時(shí)進(jìn)行事務(wù)管理。
四、編寫(xiě)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注解來(lái)自動(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)來(lái)處理下單操作,并且返回了一個(gè)ResultVO對(duì)象,通過(guò)success()方法來(lái)表示操作成功。
總的來(lái)說(shuō),這個(gè)控制器類主要用于處理秒殺下單的請(qǐng)求,通過(guò)調(diào)用OrderService來(lái)實(shí)現(xiàn)下單操作,并返回相應(yīng)的結(jié)果。
五、使用jmeter測(cè)試
官網(wǎng)網(wǎng)址:Apache JMeter - Apache JMeter™
去官網(wǎng)下載下來(lái),我們用 jmeter 來(lái)測(cè)試我們的controller。
1、jmeter有什么用
JMeter是一個(gè)用于進(jìn)行性能測(cè)試的開(kāi)源工具,它最初是為測(cè)試Web應(yīng)用程序而設(shè)計(jì)的,但后來(lái)擴(kuò)展到其他測(cè)試領(lǐng)域。JMeter的主要用途包括:
性能測(cè)試:JMeter可以模擬多個(gè)并發(fā)用戶對(duì)目標(biāo)系統(tǒng)(如Web服務(wù)器、數(shù)據(jù)庫(kù)、FTP服務(wù)器等)發(fā)起請(qǐng)求,以評(píng)估系統(tǒng)的性能和穩(wěn)定性。它可以測(cè)量系統(tǒng)在不同負(fù)載下的響應(yīng)時(shí)間、吞吐量和并發(fā)用戶數(shù)等指標(biāo),幫助開(kāi)發(fā)人員和測(cè)試人員發(fā)現(xiàn)系統(tǒng)性能方面的問(wèn)題。
負(fù)載測(cè)試:通過(guò)模擬大量用戶請(qǐng)求,JMeter可以測(cè)試系統(tǒng)在高負(fù)載情況下的表現(xiàn),評(píng)估系統(tǒng)的承載能力和性能瓶頸,以便確定系統(tǒng)是否能夠滿足預(yù)期的用戶需求。負(fù)載測(cè)試也可以用于驗(yàn)證系統(tǒng)的可伸縮性和穩(wěn)定性。
壓力測(cè)試:JMeter可以模擬系統(tǒng)在正?;虍惓X?fù)載下的表現(xiàn),以便評(píng)估系統(tǒng)在不同壓力下的穩(wěn)定性和可靠性。通過(guò)壓力測(cè)試,可以發(fā)現(xiàn)系統(tǒng)在極端情況下可能出現(xiàn)的問(wèn)題,如內(nèi)存泄漏、資源競(jìng)爭(zhēng)等。
功能測(cè)試:除了性能測(cè)試,JMeter也可以用于進(jìn)行功能測(cè)試,例如測(cè)試網(wǎng)站的登錄、注冊(cè)、搜索等功能,以及測(cè)試API的響應(yīng)等。
總的來(lái)說(shuō),JMeter是一個(gè)功能強(qiáng)大的測(cè)試工具,可以幫助開(kāi)發(fā)人員和測(cè)試人員進(jìn)行性能、負(fù)載、壓力和功能測(cè)試,以確保系統(tǒng)能夠穩(wěn)定、高效地運(yùn)行。
2、測(cè)試
1)打開(kāi) jmeter ,bin目錄下雙擊ApacheJMeter.jar 運(yùn)行

運(yùn)行:

2)添加線程組

3)添加HTTP請(qǐng)求

4)添加匯總報(bào)告

5)填寫(xiě)信息
添加循環(huán)數(shù)量,我們的庫(kù)存中有100個(gè)庫(kù)存,我們執(zhí)行150次,看會(huì)不會(huì)出現(xiàn)超賣的情況。還是售完了直接就拋異常。

填寫(xiě) HTTP 協(xié)議

請(qǐng)求路徑不要寫(xiě)錯(cuò)了,還有就是請(qǐng)求的方式是什么就選擇什么。
6)測(cè)試結(jié)果

當(dāng)運(yùn)行測(cè)試后,售完100個(gè)數(shù)量之后并沒(méi)有出現(xiàn)超賣的現(xiàn)象,證明我們的代碼就沒(méi)有寫(xiě)錯(cuò),并且在售完之后直接提示用戶商品已售完。
六、gitee 案例
地址:ch02 · qiuqiu/Redis-study - 碼云 - 開(kāi)源中國(guó) (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),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05
Redis 設(shè)置密碼無(wú)效問(wèn)題解決
本文主要介紹了Redis 設(shè)置密碼無(wú)效問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
Redis分布式鎖如何自動(dòng)續(xù)期的實(shí)現(xiàn)
本文主要介紹了Redis分布式鎖如何自動(dòng)續(xù)期的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
redis快速部署為docker容器的方法實(shí)現(xiàn)
部署 Redis 作為 Docker 容器是一種快速、靈活且可重復(fù)使用的方式,特別適合開(kāi)發(fā)、測(cè)試和部署環(huán)境,本文主要介紹了redis快速部署為docker容器的方法實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-05-05
Redis migrate數(shù)據(jù)遷移工具的使用教程
這篇文章主要給大家介紹了關(guān)于Redis migrate數(shù)據(jù)遷移工具的使用教程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08

