Java實(shí)現(xiàn)高并發(fā)秒殺的七種方式
本文將詳細(xì)介紹如何實(shí)現(xiàn)高并發(fā)秒殺功能,我們將深入探討七種常見(jiàn)的秒殺系統(tǒng)實(shí)現(xiàn)方式,包括使用緩存、數(shù)據(jù)庫(kù)樂(lè)觀鎖、數(shù)據(jù)庫(kù)悲觀鎖、分布式鎖、隊(duì)列限流、令牌桶算法和限流器。
1. 引言
在現(xiàn)代的互聯(lián)網(wǎng)應(yīng)用中,秒殺活動(dòng)是一種常見(jiàn)的需求,允許用戶在短時(shí)間內(nèi)搶購(gòu)有限的商品。然而,這種類型的活動(dòng)可能會(huì)導(dǎo)致高并發(fā)的請(qǐng)求,對(duì)系統(tǒng)造成巨大的壓力。為了應(yīng)對(duì)這種高并發(fā)的場(chǎng)景,我們需要實(shí)現(xiàn)一種能夠處理大量并發(fā)請(qǐng)求的秒殺系統(tǒng)。
秒殺系統(tǒng)通常需要處理以下挑戰(zhàn):
- 高并發(fā):在短時(shí)間內(nèi),大量的用戶可能會(huì)同時(shí)訪問(wèn)秒殺活動(dòng)頁(yè)面,導(dǎo)致系統(tǒng)壓力巨大。
- 數(shù)據(jù)一致性:在秒殺過(guò)程中,需要保證數(shù)據(jù)的準(zhǔn)確性和一致性。
- 系統(tǒng)穩(wěn)定性:在秒殺過(guò)程中,系統(tǒng)需要能夠承受高并發(fā)的請(qǐng)求,保證系統(tǒng)的穩(wěn)定運(yùn)行。
為了應(yīng)對(duì)這些挑戰(zhàn),我們可以使用多種技術(shù)來(lái)實(shí)現(xiàn)高并發(fā)的秒殺系統(tǒng)。以下是一些常見(jiàn)的實(shí)現(xiàn)方式:
2. 使用緩存
使用緩存是一種常見(jiàn)的秒殺系統(tǒng)實(shí)現(xiàn)方式。通過(guò)使用緩存,我們可以減少數(shù)據(jù)庫(kù)的訪問(wèn)次數(shù),提高系統(tǒng)的響應(yīng)速度。在秒殺活動(dòng)中,我們可以將商品的數(shù)量緩存到 Redis 中,并在用戶請(qǐng)求時(shí)直接從緩存中獲取商品數(shù)量。
import redis.clients.jedis.Jedis; public class CacheBasedSecKill { private Jedis jedis; public CacheBasedSecKill(Jedis jedis) { this.jedis = jedis; } public boolean isProductAvailable(String productId) { String productKey = "product:" + productId; return jedis.exists(productKey) && jedis.decr(productKey) > 0; } }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)名為 CacheBasedSecKill
的類,它接受一個(gè) Jedis
實(shí)例作為參數(shù)。我們定義了一個(gè)名為 isProductAvailable
的方法,它接受一個(gè)名為 productId
的字符串參數(shù)。我們使用 Redis 的 exists
命令檢查緩存中是否存在該商品的鍵,并使用 decr
命令減少商品的數(shù)量。如果商品的數(shù)量大于 0,我們返回 true
,表示商品可用;否則返回 false
。
3. 使用數(shù)據(jù)庫(kù)樂(lè)觀鎖
數(shù)據(jù)庫(kù)樂(lè)觀鎖是一種基于數(shù)據(jù)版本號(hào)或時(shí)間戳的鎖機(jī)制,用于處理并發(fā)更新操作。在秒殺活動(dòng)中,我們可以使用樂(lè)觀鎖來(lái)處理商品數(shù)量的更新。
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class OptimisticLockBasedSecKill { private Connection connection; public OptimisticLockBasedSecKill(Connection connection) { this.connection = connection; } public boolean isProductAvailable(String productId) throws SQLException { String sql = "SELECT product_id, quantity, version FROM product WHERE product_id = ? FOR UPDATE"; try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) { preparedStatement.setString(1, productId); ResultSet resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { int quantity = resultSet.getInt("quantity"); int version = resultSet.getInt("version"); if (quantity > 0) { sql = "UPDATE product SET quantity = quantity - 1, version = version + 1 WHERE product_id = ? AND version = ?"; try (PreparedStatement updateStatement = connection.prepareStatement(sql)) { updateStatement.setString(1, productId); updateStatement.setInt(2, version); int affectedRows = updateStatement.executeUpdate(); return affectedRows > 0; } } } } return false; } }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)名為 OptimisticLockBasedSecKill
的類,它接受一個(gè) Connection
實(shí)例作為參數(shù)。我們定義了一個(gè)名為 isProductAvailable
的方法,它接受一個(gè)名為 productId
的字符串參數(shù)。我們首先執(zhí)行一個(gè) SELECT
查詢,獲取商品的 ID、數(shù)量和版本號(hào)。然后,我們檢查商品的數(shù)量是否大于 0。如果商品的數(shù)量大于 0,我們執(zhí)行一個(gè) UPDATE
查詢,減少商品的數(shù)量并增加版本號(hào)。如果 UPDATE
查詢影響的行數(shù)大于 0,我們返回 true
,表示商品可用;否則返回 false
。
4. 使用數(shù)據(jù)庫(kù)悲觀鎖
數(shù)據(jù)庫(kù)悲觀鎖是一種基于排他鎖的機(jī)制,用于處理并發(fā)更新操作。在秒殺活動(dòng)中,我們可以使用悲觀鎖來(lái)處理商品數(shù)量的更新。
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class PessimisticLockBasedSecKill { private Connection connection; public PessimisticLockBasedSecKill(Connection connection) { this.connection = connection; } public boolean isProductAvailable(String productId) throws SQLException { String sql = "SELECT quantity FROM product WHERE product_id = ? FOR UPDATE"; try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) { preparedStatement.setString(1, productId); ResultSet resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { int quantity = resultSet.getInt("quantity"); if (quantity > 0) { sql = "UPDATE product SET quantity = quantity - 1 WHERE product_id = ?"; try (PreparedStatement updateStatement = connection.prepareStatement(sql)) { updateStatement.setString(1, productId); int affectedRows = updateStatement.executeUpdate(); return affectedRows > 0; } } } } return false; } }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)名為 PessimisticLockBasedSecKill
的類,它接受一個(gè) Connection
實(shí)例作為參數(shù)。我們定義了一個(gè)名為 isProductAvailable
的方法,它接受一個(gè)名為 productId
的字符串參數(shù)。我們首先執(zhí)行一個(gè) SELECT
查詢,并使用 FOR UPDATE
子句獲取商品的排他鎖。然后,我們檢查商品的數(shù)量是否大于 0。如果商品的數(shù)量大于 0,我們執(zhí)行一個(gè) UPDATE
查詢,減少商品的數(shù)量。如果 UPDATE
查詢影響的行數(shù)大于 0,我們返回 true
,表示商品可用;否則返回 false
。
5. 使用分布式鎖
分布式鎖是一種用于在分布式系統(tǒng)中控制對(duì)共享資源訪問(wèn)的鎖機(jī)制。在秒殺活動(dòng)中,我們可以使用分布式鎖來(lái)保證對(duì)商品數(shù)量的一致性訪問(wèn)。
import redis.clients.jedis.Jedis; public class DistributedLockBasedSecKill { private Jedis jedis; public DistributedLockBasedSecKill(Jedis jedis) { this.jedis = jedis; } public boolean isProductAvailable(String productId) { String lockKey = "lock:" + productId; String requestId = "request:" + Thread.currentThread().getId(); while (true) { if (jedis.setnx(lockKey, requestId) == 1) { String quantity = jedis.get("product:" + productId); if (quantity != null && Integer.parseInt(quantity) > 0) { jedis.decr("product:" + productId); jedis.del(lockKey); return true; } jedis.del(lockKey); return false; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)名為 DistributedLockBasedSecKill
的類,它接受一個(gè) Jedis
實(shí)例作為參數(shù)。我們定義了一個(gè)名為 isProductAvailable
的方法,它接受一個(gè)名為 productId
的字符串參數(shù)。我們首先嘗試使用 SETNX
命令獲取分布式鎖,如果成功獲取鎖,我們檢查商品的數(shù)量是否大于 0。如果商品的數(shù)量大于 0,我們減少商品的數(shù)量,并刪除鎖。如果獲取鎖失敗,我們等待一段時(shí)間后重試。
6. 使用隊(duì)列限流
隊(duì)列限流是一種基于隊(duì)列的限流機(jī)制,用于控制并發(fā)請(qǐng)求的數(shù)量。在秒殺活動(dòng)中,我們可以使用隊(duì)列限流來(lái)限制同時(shí)處理的請(qǐng)求數(shù)量。
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class QueueBasedRateLimiter { private ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>(); private int maxQueueSize; private ExecutorService executorService; public QueueBasedRateLimiter(int maxQueueSize) { this.maxQueueSize = maxQueueSize; this.executorService = Executors.newFixedThreadPool(maxQueueSize); } public boolean isProductAvailable(String productId) { if (queue.size() < maxQueueSize) { queue.add(productId); executorService.execute(() -> { try { // 執(zhí)行秒殺邏輯 } finally { queue.poll(); } }); return true; } return false; } }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)名為 QueueBasedRateLimiter
的類,它接受一個(gè)名為 maxQueueSize
的整數(shù)參數(shù)。我們定義了一個(gè)名為 isProductAvailable
的方法,它接受一個(gè)名為 productId
的字符串參數(shù)。我們首先檢查隊(duì)列的大小是否小于最大隊(duì)列大小。如果小于,我們將商品 ID 添加到隊(duì)列中,并使用線程池執(zhí)行秒殺邏輯。如果等于或大于,我們返回 false
。
7. 使用令牌桶算法和限流器
令牌桶算法是一種常見(jiàn)的限流算法,用于控制數(shù)據(jù)包的發(fā)送速率。在秒殺活動(dòng)中,我們可以使用令牌桶算法來(lái)限制并發(fā)請(qǐng)求的數(shù)量。
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TokenBucketRateLimiter { private ConcurrentLinkedQueue<String> bucket = new ConcurrentLinkedQueue<>(); private int maxTokens; private int refillRate; private ExecutorService executorService; public TokenBucketRateLimiter(int maxTokens, int refillRate) { this.maxTokens = maxTokens; this.refillRate = refillRate; this.executorService = Executors.newFixedThreadPool(maxTokens); } public boolean isProductAvailable(String productId) { synchronized (bucket) { while (bucket.size() >= maxTokens) { try { bucket.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } bucket.add(productId); executorService.execute(() -> { try { // 執(zhí)行秒殺邏輯 } finally { synchronized (bucket) { bucket.remove(productId); bucket.notifyAll(); } } }); return true; } } }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)名為 TokenBucketRateLimiter
的類,它接受兩個(gè)名為 maxTokens
和 refillRate
的整數(shù)參數(shù)。我們定義了一個(gè)名為 isProductAvailable
的方法,它接受一個(gè)名為 productId
的字符串參數(shù)。
我們首先檢查令牌桶的大小是否達(dá)到最大令牌數(shù)。如果達(dá)到,我們等待直到有新的令牌被添加。一旦有新的令牌被添加,我們將商品 ID 添加到令牌桶中,并使用線程池執(zhí)行秒殺邏輯。如果令牌桶未滿,我們直接將商品 ID 添加到令牌桶中,并執(zhí)行秒殺邏輯。
8. 總結(jié)
本文詳細(xì)介紹了如何實(shí)現(xiàn)高并發(fā)秒殺功能,我們深入探討了七種常見(jiàn)的秒殺系統(tǒng)實(shí)現(xiàn)方式,包括使用緩存、數(shù)據(jù)庫(kù)樂(lè)觀鎖、數(shù)據(jù)庫(kù)悲觀鎖、分布式鎖、隊(duì)列限流、令牌桶算法和限流器。每種方式都有其優(yōu)缺點(diǎn),適用于不同的場(chǎng)景。通過(guò)使用這些技術(shù),我們可以有效地處理高并發(fā)的秒殺活動(dòng),保證系統(tǒng)的穩(wěn)定性和性能。
到此這篇關(guān)于Java實(shí)現(xiàn)高并發(fā)秒殺的七種方式的文章就介紹到這了,更多相關(guān)Java 高并發(fā)秒殺內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
jvm之java類加載機(jī)制和類加載器(ClassLoader)的用法
這篇文章主要介紹了jvm之java類加載機(jī)制和類加載器(ClassLoader)的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09SpringBoot通過(guò)RedisTemplate執(zhí)行Lua腳本的方法步驟
這篇文章主要介紹了SpringBoot通過(guò)RedisTemplate執(zhí)行Lua腳本的方法步驟,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02Java中&&與?表達(dá)式結(jié)合時(shí)出現(xiàn)的坑
這篇文章主要給大家介紹了關(guān)于Java中&&與?表達(dá)式結(jié)合時(shí)出現(xiàn)的坑的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02Java集合基礎(chǔ)知識(shí) List/Set/Map詳解
這篇文章主要介紹了Java集合基礎(chǔ)知識(shí) List/Set/Map,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03SpringBoot Security前后端分離登錄驗(yàn)證的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot Security前后端分離登錄驗(yàn)證的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09一步步教你如何使用Java實(shí)現(xiàn)WebSocket
websocket協(xié)議是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議,它實(shí)現(xiàn)了瀏覽器與服務(wù)器的全雙工通訊-允許服務(wù)器主動(dòng)發(fā)起信息個(gè)客戶端,websocket是一種持久協(xié)議,http是非持久協(xié)議,下面這篇文章主要給大家介紹了關(guān)于如何使用Java實(shí)現(xiàn)WebSocket的相關(guān)資料,需要的朋友可以參考下2023-05-05JAVA實(shí)現(xiàn)Excel和PDF上下標(biāo)的操作代碼
這篇文章主要介紹了JAVA實(shí)現(xiàn)Excel和PDF上下標(biāo),本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09java?Object的hashCode方法的計(jì)算邏輯分析
這篇文章主要介紹了java?Object的hashCode方法的計(jì)算邏輯分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12