讀寫鎖分離設(shè)計(jì)模式:提升商城系統(tǒng)庫存管理性能的利器

2024-12-30 18:33 更新

大家好,我是 V 哥。商城系統(tǒng)中,用戶在瀏覽商品詳情頁時(shí)可以查看庫存數(shù)量,這是讀操作,頻率較高。當(dāng)用戶下單成功時(shí),系統(tǒng)會(huì)更新庫存數(shù)量,這是寫操作,但相對(duì)較少。這是一個(gè)再常見不過的應(yīng)用場(chǎng)景了,在這種場(chǎng)景下,讀寫鎖分離設(shè)計(jì)模式就是最好的武器。

讀寫鎖分離設(shè)計(jì)模式是一種多線程設(shè)計(jì)模式,適合在有讀多寫少的場(chǎng)景中使用。它通過讀寫操作的分離,提升了系統(tǒng)的并發(fā)性和性能。在這個(gè)模式中,讀操作是共享的,可以同時(shí)被多個(gè)線程執(zhí)行,而寫操作需要獨(dú)占鎖,避免并發(fā)寫入帶來的數(shù)據(jù)不一致問題。

讀寫鎖分離設(shè)計(jì)模式的原理

咱們?cè)賮戆炎x寫鎖分離的原理先明確一下:

  1. 讀寫鎖(ReadWriteLock)
    • 讀寫鎖提供了兩種鎖:讀鎖和寫鎖。
    • 讀鎖是共享的,可以允許多個(gè)線程同時(shí)持有,多個(gè)線程可以并發(fā)讀取數(shù)據(jù)。
    • 寫鎖是獨(dú)占的,只有一個(gè)線程能獲取寫鎖。寫操作會(huì)阻塞所有的讀寫操作,確保數(shù)據(jù)一致性。

  1. 適用場(chǎng)景
    • 讀操作遠(yuǎn)多于寫操作,數(shù)據(jù)寫入不頻繁的場(chǎng)景,如緩存、配置讀取、數(shù)據(jù)分析等。
    • 通過讀寫分離,可以避免讀操作阻塞,從而提高系統(tǒng)的吞吐量和并發(fā)性。

特別的愛給特別的你,滿足才是硬道理

咱們就拿在電商平臺(tái)的商品庫存管理系統(tǒng)來說,庫存數(shù)據(jù)需要滿足如下業(yè)務(wù)需求:

  1. 高并發(fā)訪問:商城有大量用戶會(huì)同時(shí)訪問商品詳情頁,查詢庫存數(shù)量。訪問這些商品庫存的用戶數(shù)是巨大的,因此讀取庫存的操作需要具備高并發(fā)能力,保證每個(gè)用戶能夠快速查詢到最新的庫存信息。

  1. 實(shí)時(shí)更新庫存:當(dāng)用戶成功下單,庫存需要相應(yīng)減少。此外,可能會(huì)有后臺(tái)系統(tǒng)進(jìn)行庫存更新操作,比如在補(bǔ)貨時(shí)增加庫存。因此,寫操作雖然較少,但必須做到線程安全,確保更新數(shù)據(jù)的準(zhǔn)確性,避免因并發(fā)寫入導(dǎo)致庫存錯(cuò)誤。

  1. 數(shù)據(jù)一致性要求:為了確保庫存數(shù)據(jù)的一致性,寫操作必須是獨(dú)占的,也就是說,只有在沒有任何讀或?qū)懖僮鲿r(shí),系統(tǒng)才能進(jìn)行寫操作,從而避免多個(gè)線程同時(shí)寫入導(dǎo)致的庫存數(shù)據(jù)錯(cuò)誤。同時(shí),也要保證在進(jìn)行寫操作時(shí),讀線程不能獲取到庫存的中間狀態(tài),確保用戶獲取的是準(zhǔn)確的庫存信息。

  1. 讀多寫少:在實(shí)際業(yè)務(wù)中,庫存查詢的頻率遠(yuǎn)高于更新庫存的頻率,絕大部分用戶操作僅涉及查詢商品是否有庫存,而少部分用戶操作涉及到更新庫存,比如完成下單、取消訂單、或者后臺(tái)管理員進(jìn)行庫存調(diào)整。

  1. 性能要求:庫存查詢的響應(yīng)速度直接影響到用戶體驗(yàn),特別是在大型促銷活動(dòng)中,大量用戶同時(shí)訪問某些熱門商品的庫存數(shù)據(jù),因此必須保證高并發(fā)讀取的性能。而寫操作因?yàn)檩^少,不會(huì)頻繁發(fā)生,能容忍一定的等待時(shí)間。

具體操作場(chǎng)景

回到功能業(yè)務(wù),通常要實(shí)現(xiàn)的具體功能場(chǎng)景是這樣的:

  • 庫存查詢:用戶在瀏覽商品詳情頁時(shí),都會(huì)查看商品庫存。一個(gè)頁面可能會(huì)展示多個(gè)商品的庫存,因此多個(gè)用戶并發(fā)查詢不同商品的庫存時(shí),系統(tǒng)需要支持多個(gè)讀線程同時(shí)讀取數(shù)據(jù),而不會(huì)互相干擾。

  • 庫存更新:當(dāng)用戶下單購買商品時(shí),系統(tǒng)需要減少相應(yīng)的庫存數(shù)量。這個(gè)寫操作必須是獨(dú)占的,以避免并發(fā)寫入導(dǎo)致庫存數(shù)量的不一致。更新操作還會(huì)在訂單取消、訂單失效等情況下發(fā)生。此外,后臺(tái)的庫存補(bǔ)貨也是一種寫操作,更新后的庫存應(yīng)能被用戶實(shí)時(shí)查詢到。

  • 促銷場(chǎng)景:在大促活動(dòng)中(如雙11、黑五促銷),某些商品會(huì)有大量用戶訪問庫存。如果不進(jìn)行讀寫鎖分離,頻繁的寫鎖會(huì)阻塞所有的讀線程,導(dǎo)致用戶體驗(yàn)下降。因此,系統(tǒng)應(yīng)支持多個(gè)用戶同時(shí)進(jìn)行讀取操作,同時(shí)保障寫入的獨(dú)占性,以平衡高并發(fā)和數(shù)據(jù)一致性的需求。

實(shí)施目標(biāo)

有了這樣的場(chǎng)景,采用讀寫鎖分離設(shè)計(jì)模式來優(yōu)化庫存管理,可以達(dá)到以下目標(biāo):

  1. 提升讀操作的并發(fā)性:允許多個(gè)用戶并發(fā)查詢庫存,保證在讀多寫少的場(chǎng)景下庫存查詢的高效性。
  2. 保證寫操作的獨(dú)占性:避免并發(fā)寫入的沖突,確保庫存數(shù)據(jù)的一致性,滿足商品庫存管理系統(tǒng)的數(shù)據(jù)準(zhǔn)確性需求。
  3. 降低讀寫沖突的等待時(shí)間:在寫操作較少的情況下,通過讀寫鎖分離有效降低讀操作的等待時(shí)間,提升系統(tǒng)整體性能。

案例:商品庫存管理

下面咱們來具體看一下案例實(shí)現(xiàn),我們要實(shí)現(xiàn)一個(gè)商品庫存管理,需求是這樣滴:

  • 多個(gè)線程可以同時(shí)讀取某商品的庫存數(shù)量。
  • 只有一個(gè)線程可以更新庫存,避免多個(gè)寫操作造成數(shù)據(jù)不一致。

一、實(shí)現(xiàn)步驟

  1. 定義商品庫存管理類 InventoryManager,使用 ReentrantReadWriteLock 進(jìn)行讀寫鎖分離。
  2. 創(chuàng)建 checkStock 方法進(jìn)行庫存讀取操作,獲取讀鎖。
  3. 創(chuàng)建 updateStock 方法進(jìn)行庫存更新操作,獲取寫鎖。

以下是實(shí)現(xiàn)代碼:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;


public class InventoryManager {
    // 商品庫存存儲(chǔ)
    private final Map<String, Integer> inventory = new HashMap<>();
    // 讀寫鎖
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();


    // 獲取庫存數(shù)量(讀操作)
    public int checkStock(String productId) {
        readLock.lock();
        try {
            return inventory.getOrDefault(productId, 0);
        } finally {
            readLock.unlock();
        }
    }


    // 更新庫存數(shù)量(寫操作)
    public void updateStock(String productId, int quantity) {
        writeLock.lock();
        try {
            int currentStock = inventory.getOrDefault(productId, 0);
            inventory.put(productId, currentStock + quantity);
            System.out.println("Updated stock for product " + productId + ": " + (currentStock + quantity));
        } finally {
            writeLock.unlock();
        }
    }
}

二、測(cè)試案例

在測(cè)試案例中,模擬多個(gè)用戶并發(fā)訪問庫存,讀取庫存的線程可以并發(fā)執(zhí)行,而更新庫存的線程會(huì)獨(dú)占鎖。

public class InventoryManagerTest {
    public static void main(String[] args) {
        InventoryManager inventoryManager = new InventoryManager();

        
        // 初始化庫存
        inventoryManager.updateStock("product_1", 100);

        
        // 模擬多個(gè)線程同時(shí)讀取庫存
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                System.out.println("Stock for product_1: " + inventoryManager.checkStock("product_1"));
            }).start();
        }


        // 模擬一個(gè)線程更新庫存
        new Thread(() -> {
            inventoryManager.updateStock("product_1", -10);
            System.out.println("Stock after selling 10 units for product_1: " + inventoryManager.checkStock("product_1"));
        }).start();
    }
}

三、代碼分析

  • 讀操作 (checkStock):
    • 使用 readLock 加鎖,只需獲取讀鎖,不會(huì)影響其他讀取線程。
    • 多個(gè)讀取線程可以同時(shí)進(jìn)入 checkStock 方法,提升讀取并發(fā)性。

  • 寫操作 (updateStock):
    • 使用 writeLock 加鎖,寫操作會(huì)阻塞其他讀寫操作。
    • 確保寫入操作是獨(dú)占的,防止并發(fā)寫操作導(dǎo)致的數(shù)據(jù)不一致問題。

四、運(yùn)行結(jié)果示例

輸出可能如下(順序可能有所不同):

Stock for product_1: 100
Stock for product_1: 100
Stock for product_1: 100
Stock for product_1: 100
Stock for product_1: 100
Updated stock for product product_1: 90
Stock after selling 10 units for product_1: 90

五、優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn):讀寫鎖分離允許多個(gè)讀線程并發(fā)執(zhí)行,大大提高了讀操作的效率,適合讀多寫少的場(chǎng)景。
  • 缺點(diǎn):如果寫操作頻繁,寫鎖會(huì)阻塞讀操作,可能會(huì)降低系統(tǒng)性能,但在寫安全重要程度來看,犧牲點(diǎn)性能是完全可以忍受的。

到這里,你是不是可以感受到讀寫鎖分離設(shè)計(jì)模式解決了大問題了呢,并發(fā)場(chǎng)景下我們必須要考慮這個(gè)問題。你學(xué)沸了嗎,關(guān)注威哥愛編程,一起搞不寂寞。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)