「有問必答」秒殺系統 Go並行程式設計實踐!

2023-11-28 18:00:43

有問必答

摘要

本文將介紹如何使用Go語言的並行原語來構建一個簡單的高並行秒殺系統。

我們將使用Go語言的原生庫和一些常見的技術手段,包括互斥鎖、通道、計數器等,來解決並行存取和資料一致性的問題。

本文只是一個簡單的範例,重點是Go語言並行原語在業務場景中的應用。

在實際應用中,還需要考慮資料庫事務、分散式鎖、限流等問題。我之前也寫過一篇文章,附在了文末。

1. 引言

秒殺系統是一種高並行場景下的特殊應用,需要處理大量的並行請求和保證資料的一致性。本文將介紹如何使用Go語言的並行原語來構建一個高並行的秒殺系統,以滿足使用者的需求並保證系統的穩定性。

2. 架構設計

我們的秒殺系統將採用經典的使用者端-伺服器架構。使用者端傳送秒殺請求,伺服器處理請求並更新庫存。為了保證系統的高並行效能,我們將使用以下技術和原語:

  • 互斥鎖(sync.Mutex):用於保護共用資源的並行存取。
  • 通道(channel):用於協程間的通訊。
  • 計數器(sync.WaitGroup):用於等待所有請求完成。

3. 實現步驟

下面是我們實現秒殺系統的關鍵步驟:

3.1 初始化庫存

在系統啟動時,我們需要初始化商品的庫存。

var stock = 100 // 商品庫存
var mu sync.Mutex

3.2 處理秒殺請求

當用戶端傳送秒殺請求時,伺服器需要處理請求並更新庫存。

func handleRequest(user int) {
    defer wg.Done()
    if tryAcquireLock() {
        if stock > 0 {
            // 執行秒殺邏輯
            stock--
            fmt.Printf("使用者%d秒殺成功,剩餘庫存:%d\n", user, stock)
        } else {
            fmt.Printf("使用者%d秒殺失敗,庫存不足\n", user)
        }
        releaseLock()
    } else {
        fmt.Printf("使用者%d未獲取到鎖,秒殺失敗\n", user)
    }
}

3.3 並行控制和等待

為了控制並行請求的數量,我們使用計數器和通道來限制並行度。

var wg sync.WaitGroup

func main() {
    for i := 1; i <= 1000; i++ {
        wg.Add(1)
        go handleRequest(i)
    }
    wg.Wait()
}

3.4 互斥鎖和並行安全

為了保證並行存取的安全性,我們使用互斥鎖來保護共用資源的存取。

注意:TryLock()是go1.18才引入的

func tryAcquireLock() bool {
    return mu.TryLock()
}

func releaseLock() {
    mu.Unlock()
}

4. 完整程式碼

package main

import (
 "fmt"
 "sync"
)

//後面開啟了1000個goroutine,所以這裡channel的緩衝區設定成了1000
var ch = make(chan bool, 1000)

type Product struct {
 sync.Mutex
 stock int64 // 商品庫存
}

func main() {
 p := Product{stock: 1000}
 for i := 1; i <= 1000; i++ {
  go p.handleRequest(i)
 }
 <-ch
}

func (p *Product) handleRequest(user int) {
 if p.tryAcquireLock() {
  if p.stock > 0 {
   // 執行秒殺邏輯
   p.stock--
   fmt.Printf("使用者%d秒殺成功,剩餘庫存:%d\n", user, p.stock)
  } else {
   fmt.Printf("使用者%d秒殺失敗,庫存不足\n", user)
  }
  //這裡是不可以使用defer的,因為可能會加鎖失敗,unlock一個不存在的鎖
  p.releaseLock()
 } else {
  fmt.Printf("使用者%d未獲取到鎖,秒殺失敗\n", user)
 }
}

func (p *Product) tryAcquireLock() bool {
//p.TryLock() 方法用於嘗試獲取鎖,如果成功獲取到鎖,則相當於執行了 Lock() 操作,即加鎖成功。 
 return p.TryLock()
}

func (p *Product) releaseLock() {
 p.Unlock()
 ch <- true
}

解析程式碼

var ch = make(chan bool, 1000):後面開啟了1000個goroutine,所以這裡channel的緩衝區設定成了1000

p.releaseLock():這裡是不可以使用defer的,因為可能會加鎖失敗,unlock一個不存在的鎖

p.TryLock():方法用於嘗試獲取鎖,如果成功獲取到鎖,則相當於執行了 Lock() 操作,即加鎖成功。

5. 執行結果

6. 總結

通過使用Go語言的並行原語,我們成功地構建了一個高並行的秒殺系統。

使用互斥鎖和計數器等原語,我們實現了並行控制、資料一致性和並行安全。這些原語幫助我們解決了高並行場景下的並行存取問題,並保證了系統的穩定性和效能。

本文只是一個簡單的範例,實際的秒殺系統可能涉及更多的業務邏輯和並行控制。

在實際應用中,還需要考慮資料庫事務、分散式鎖、限流等問題。因此,建議根據實際需求和場景進行更詳細的設計和實現。

我之前也有寫萬字長文總結過,感興趣的朋友歡迎檢視:萬字詳解:秒殺系統設計

一起學習

歡迎大家關注我的賬號,你的支援,是我更文的最大動力!

也歡迎關注我的公眾號: 程式設計師升職加薪之旅,領取更多Go學習和麵試資料。

微訊號:wangzhongyang1993