成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

聽說你會架構設計?來,弄一個紅包系統

開發 架構
和大多數秒殺系統設計相似,由于搶紅包時并發很高,如果直接操作 DB 里的數據表,可能觸發 DB 鎖的邏輯,導致響應不及時。

大家好,我是小?,一個漂泊江湖多年的 985 非科班程序員,曾混跡于國企、互聯網大廠和創業公司的后臺開發攻城獅。

1. 引言

當我那天拿著手機,正在和朋友們的微信群里暢聊著八卦新聞和即將到來的周末計劃時,忽然一條帶著喜意的消息撲面而來,消息正中間赫然寫著八個大字:恭喜發財,大吉大利。

圖片圖片

搶紅包!!相信大部分人對此都不陌生,自 2015 年春節以來,微信就新增了各類型搶紅包功能,吸引了數以億萬級的用戶參與體驗,今天,我們就來聊一聊這個奇妙有趣的紅包系統。

2. 概要設計

2.1 系統特點

圖片圖片

搶紅包系統從功能拆分,可以分為包紅包、發紅包、搶紅包和拆紅包 4 個功能。

對于系統特性來說,搶紅包系統和秒殺系統類似。

圖片圖片

每次發紅包都是一次商品秒殺流程,包括商品準備,商品上架,查庫存、減庫存,以及秒殺開始,最終的用戶轉賬就是紅包到賬的過程。

2.2 難點

相比秒殺活動,微信發紅包系統的用戶量更大,設計更加復雜,需要重視的點更多,主要包括以下幾點。

1、高并發

海量并發請求,秒殺只有一次活動,但紅包可能同一時刻有幾十萬個秒殺活動。

比如 2017 雞年除夕,微信紅包搶紅包用戶數高達 3.42 億,收發峰值 76 萬/秒,發紅包 37.77 億 個。

2、安全性要求

紅包業務涉及資金交易,所以一定不能出現超賣、少賣的情況。

  • 超賣:發了 10 塊錢,結果搶到了 11 塊錢,多的錢只能系統補上,如此為愛發電應用估計早就下架了;
  • 少賣:發了 10 塊錢,只搶了 9 塊,多的錢得原封不動地退還用戶,否則第二天就接到法院傳單了。

3、嚴格事務

參與用戶越多,并發 DB 請求越大,數據越容易出現事務問題,所以系統得做好事務一致性。

這也是一般秒殺活動的難點所在,而且搶紅包系統涉及金錢交易,所以事務級別要求更高,不能出現臟數據。

3. 概要設計

3.1 功能說明

搶紅包功能允許用戶在群聊中發送任意個數和金額的紅包,群成員可以搶到隨機金額的紅包,但要保證每個用戶的紅包金額不小于 0.01 元。

圖片圖片

搶紅包的詳細交互流程如下:

  1. 用戶接收到搶紅包通知,點擊通知打開群聊頁面;
  2. 用戶點擊搶紅包,后臺服務驗證用戶資格,確保用戶尚未領取過此紅包;
  3. 若用戶資格驗證通過,后臺服務分配紅包金額并存儲領取記錄;
  4. 用戶在微信群中看到領取金額,紅包狀態更新為“已領取”;
  5. 異步調用支付接口,將紅包金額更新到錢包里。

3.2 數據庫設計

紅包表 redpack 的字段如下:

  • id: 主鍵,紅包ID
  • userId: 發紅包的用戶ID
  • totalAmount: 總金額
  • surplusAmount: 剩余金額
  • total: 紅包總數
  • surplusTotal: 剩余紅包總數

該表用來記錄用戶發了多少紅包,以及需要維護的剩余金額。

紅包記錄表 redpack_record 如下:

  • id: 主鍵,記錄ID
  • redpackId: 紅包ID,外鍵
  • userId: 用戶ID
  • amount: 搶到的金額

記錄表用來存放用戶具體搶到的紅包信息,也是紅包表的副表。

3.3 發紅包

  1. 用戶設置紅包的總金額和個數后,在紅包表中增加一條數據,開始發紅包;
  2. 為了保證實時性和搶紅包的效率,在 Redis 中增加一條記錄,存儲紅包 ID 和總人數 n;
  3. 搶紅包消息推送給所有群成員。

3.4 搶紅包

從 2015 年起,微信紅包的搶紅包和拆紅包就分離了,用戶點擊搶紅包后需要進行兩次操作。

這也是為什么明明有時候搶到了紅包,點開后卻發現該紅包已經被領取完了。

圖片圖片

搶紅包的交互步驟如下:

  1. 搶紅包:搶操作在 Redis 緩存層完成,通過原子遞減的操作來更新紅包個數,個數遞減為 0 后就說明搶光了。
  2. 拆紅包:拆紅包時,首先會實時計算金額,一般是通過二倍均值法實現(即 0.01 到剩余平均值的 2 倍之間)。
  3. 紅包記錄:用戶獲取紅包金額后,通過數據庫的事務操作累加已經領取的個數和金額,并更新紅包表和記錄表。
  4. 轉賬:為了提升效率,最終的轉賬為異步操作,這也是為什么在春節期間,紅包領取后不能立即在余額中看到的原因。

上述流程,在一般的秒殺活動中隨處可見,但是,紅包系統真的有這么簡單嗎?

當用戶量過大時,高并發下的事務一致性怎么保證,數據分流如何處理,紅包的數額分配又是怎么做的,接下來我們一一探討。

4. 詳細設計

由于是秒殺類設計,以及 money 分發,所以我們重點關注搶紅包時的高并發解決方案和紅包分配算法。

4.1 高并發解決方案

首先,搶紅包系統的用戶量很大,如果幾千萬甚至億萬用戶同時在線發搶紅包,請求直接打到數據庫,必然會導致后端服務過載甚至崩潰。

而在這種業務量下,簡單地對數據庫進行擴容不僅會讓成本消耗劇增,另一方面由于存在磁盤的性能瓶頸,所以大概率解決不了問題。

所以,我們將解決方案集中在 減輕系統壓力、提升響應速度 上,接下來會從緩存、加鎖、異步分治等方案來探討可行性。

1、緩存

和大多數秒殺系統設計相似,由于搶紅包時并發很高,如果直接操作 DB 里的數據表,可能觸發 DB 鎖的邏輯,導致響應不及時。

圖片圖片

所以,我們可以在 DB 落盤之前加一層緩存,先限制住流量,再處理紅包訂單的數據更新。

這樣做的優點是用緩存操作替代了磁盤操作,提升了并發性能,這在一般的小型秒殺活動中非常有效!

但是,隨著微信使用發&搶紅包的用戶量增多,系統壓力增大,各種連鎖反應產生后,數據一致性的問題逐漸暴露出來:

  • 假設庫存減少的內存操作成功,但是 DB 持久化失敗了,會出現紅包少發的問題;
  • 如果庫存操作失敗,DB 持久化成功,又可能會出現紅包超發的問題。

而且在幾十萬的并發下,直接對業務加鎖也是不現實的,即便是樂觀鎖。

2、加鎖

在關系型 DB 里,有兩種并發控制方法:分為樂觀鎖(又叫樂觀并發控制,Optimistic Concurrency Control,縮寫 “OCC”)和悲觀鎖(又叫悲觀并發,Pessimistic Concurrency Control,縮寫“PCC”)。

圖片圖片

悲觀鎖在操作數據時比較悲觀,認為別的事務可能會同時修改數據,所以每次操作數據時會先把數據鎖住,直到操作完成。

樂觀鎖正好相反,這種策略主打一個“信任”的思想,認為事務之間的數據競爭很小,所以在操作數據時不會加鎖,直到所有操作都完成到提交時才去檢查是否有事務更新(通常是通過版本號來判斷),如果沒有則提交,否則進行回滾。

在高并發場景下,由于數據操作的請求很多,所以樂觀鎖的吞吐量更大一些。但是從業務來看,可能會帶來一些額外的問題:

  1. 搶紅包時大量用戶涌入,但只有一個可以成功,其它的都會失敗并給用戶報錯,導致用戶體驗極差;
  2. 搶紅包時,如果第一時間有很多用戶涌入,都失敗回滾了。過一段時間并發減小后,反而讓手慢的用戶搶到了紅包;
  3. 大量無效的更新請求和事務回滾,可能給 DB 造成額外的壓力,拖慢處理性能。

總的來說,樂觀鎖適用于數據競爭小,沖突較少的業務場景,而悲觀鎖也不適用于高并發場景的數據更新。

因此對于搶紅包系統來說,加鎖是非常不適合的。

3、異步分治

綜上所述,搶紅包時不僅要解決高并發問題、還得保障并發的順序性,所以我們考慮從隊列的角度來設計。

我們知道,每次包紅包、發紅包、搶紅包時,也有先后依賴關系,因此我們可以將紅包 ID 作為一個唯一 Key,將發一次紅包看作一個單獨的 set,各個 set 相互獨立處理。

圖片圖片

這樣,我們就把海量的搶紅包系統分成一個個的小型秒殺系統,在調度處理中,通過對紅包 ID 哈希取模,將一個個請求打到多臺服務器上解耦處理。

然后,為了保證每個用戶搶紅包的先后順序,我們把一個紅包相關的操作串行起來,放到一個隊列里面,依次消費。

從上述 set 分流我們可以看出,一臺服務器可能會同時處理多個紅包的操作,所以,為了保證消費者處理 DB 不被高并發打崩,我們還需要在消費隊列時用緩存來限制并發消費數量。

搶紅包業務消費時由于不存儲數據,只是用緩存來控制并發。所以我們可以選用大數據量下性能更好的 Memcached。

除此之外,在數據存儲上,我們可以用紅包 ID 進行哈希分表,用時間維度對 DB 進行冷熱分離,以此來提升單 set 的處理性能。

綜上所述,搶紅包系統在解決高并發問題上采用了 set 分治、串行化隊列、雙維度分庫分表 等方案,使得單組 DB 的并發性能得到了有效提升,在應對數億級用戶請求時取得了良好的效果。

4.2 紅包分配算法

搶紅包后,我們需要進行拆紅包,接下來我們討論一下紅包系統的紅包分配算法。

紅包金額分配時,由于是隨機分配,所以有兩種實現方案:實時拆分和預先生成。

1、實時拆分

實時拆分,指的是在搶紅包時實時計算每個紅包的金額,以實現紅包的拆分過程。

這個對系統性能和拆分算法要求較高,例如拆分過程要一直保證后續待拆分紅包的金額不能為空,不容易做到拆分的紅包金額服從正態分布規律。

2、預先生成

預先生成,指的是在紅包開搶之前已經完成了紅包的金額拆分,搶紅包時只是依次取出拆分好的紅包金額。

這種方式對拆分算法要求較低,可以拆分出隨機性很好的紅包金額,但通常需要結合隊列使用。

3、二倍均值法

綜合上述優缺點考慮,以及微信群聊中的人數不多(目前最高 500 人),所以我們采用實時拆分的方式,用二倍均值法來生成隨機紅包,只滿足隨機即可,不需要正態分布。

故可能出現很大的紅包差額,但這更刺激不是嗎??

使用二倍均值法生成的隨機數,每次隨機金額會在 0.01 ~ 剩余平均值*2 之間。

假設當前紅包剩余金額為 10 元,剩余個數為 5,10/5 = 2,則當前用戶可以搶到的紅包金額為:0.01 ~ 4 元之間。

4、算法優化

用二倍均值法生成的隨機紅包雖然接近平均值,但是在實際場景下:微信紅包金額的隨機性和領取的順序有關系,尤其是金額不高的情況下。

于是,小?耗費 “巨資” 在微信群發了多個紅包,得出了這樣一個結論:如果發出的 紅包總額 = 紅包數*0.01 + 0.01,比如:發了 4 個紅包,總額為 0.05,則最后一個人領取的紅包金額一定是 0.02。

圖片圖片

無一例外:

圖片圖片

所以,紅包金額算法大概率不是隨機分配,而是在派發紅包之前已經做了處理。比如在紅包金額生成前,先生成一個不存在的紅包,這個紅包的總額為 0.01 * 紅包總數。

而在紅包金額分配的時候,會對每個紅包的隨機值基礎上加上 0.01,以此來保證每個紅包的最小值不為 0。

所以,假設用戶發了總額為 0.04 的個數為 3 的紅包時,需要先提取 3*0.01 到 "第四個" 不存在的紅包里面,于是第一個人搶到的紅包隨機值是 0 ~ (0.04-3*0.01)/3。

由于擔心紅包超額,所以除數的商是向下取二位小數,0 ~ (0.04-3*0.01)/3 ==> (0 ~ 0) = 0,再加上之前提取的保底值 0.01,于是前兩個搶到的紅包金額都是 0.01。最后一個紅包的金額為紅包余額,即 0.02。

算法邏輯用 Go 語言實現如下:

import (
   "fmt"
   "math"
   "math/rand"
   "strconv"
)

type RedPack struct {
    SurplusAmount float64 // 剩余金額
     SurplusTotal int // 紅包剩余個數
}

// 取兩位小數
func remainTwoDecimal(num float64) float64 {
    numStr := strconv.FormatFloat(num, 'f', 2, 64)
    num, _ = strconv.ParseFloat(numStr, 64)
    return num
}

// 獲取隨機金額的紅包
func getRandomRedPack(rp *RedPack) float64 {
    if rp.SurplusTotal <= 0 {
        // 該紅包已經被搶完了
        return 0
    }

    if rp.SurplusTotal == 1 {
        return remainTwoDecimal(rp.SurplusAmount + 0.01)
    }

       // 向下取整
    avgAmount := math.Floor(100*(rp.SurplusAmount/float64(rp.SurplusTotal))) / float64(100)
    avgAmount = remainTwoDecimal(avgAmount)

       // 生成隨機數種子
    rand.NewSource(time.Now().UnixNano())

    var max float64
    if avgAmount > 0 {
        max = 2*avgAmount - 0.01
    } else {
        max = 0
    }
    money := remainTwoDecimal(rand.Float64()*(max) + 0.01)

    rp.SurplusTotal -= 1
    rp.SurplusAmount = remainTwoDecimal(rp.SurplusAmount + 0.01 - money)

    return money
}

// 實現主函數
func main() {
    rp := &RedPack{
        SurplusAmount: 0.06,
        SurplusTotal:  5,
    }
    rp.SurplusAmount -= 0.01 * float64(rp.SurplusTotal)
    total := rp.SurplusTotal
    for i := 0; i < total; i++ {
        fmt.Println(getRandomRedPack(rp))
    }
}

打印結果:

0.01、0.01、0.01、0.01、0.02

喜大普奔,符合預期!

5. 總結

設計一個紅包系統不僅要考慮海量用戶的并發體驗和數據一致性,還得保障用戶資金的安全。

這種技術難點,對于傳統的 “秒殺系統” 有過之而無不及。

責任編輯:武曉燕 來源: xin猿意碼
相關推薦

2023-11-08 07:05:07

架構設計群聊系統

2025-03-17 02:00:00

2023-12-29 11:32:27

2023-10-08 22:38:52

2023-11-01 18:10:45

架構設計技術

2022-12-25 18:58:53

架構RabbitMQ

2024-03-01 18:55:54

內存調試Go 語言

2024-08-28 08:38:51

2025-05-27 10:15:00

Go開發軟件架構

2021-04-28 08:52:22

高并發架構設高并發系統

2019-08-22 10:54:05

分布式系統架構

2022-05-17 20:37:41

MyPick泛型對象類型

2019-01-28 11:46:53

架構運維技術

2024-04-24 10:38:22

2025-04-29 02:00:00

高并發系統場景

2023-07-05 08:00:52

MetrAuto系統架構

2025-06-10 01:00:00

分布式日志系統

2024-06-21 08:15:25

2025-01-22 08:00:00

架構秒殺系統Java

2022-02-17 08:57:18

內存設計進程
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91精品国产91久久综合桃花 | 欧美成人专区 | 国产亚洲网站 | 九九热这里只有精品6 | 色女人天堂 | 国产精品一区二区无线 | 99精品在线 | 久久精品亚洲精品国产欧美kt∨ | 天天天天天天天干 | 久久视频免费看 | 久久久国产精品网站 | 高清人人天天夜夜曰狠狠狠狠 | 日本一区视频在线观看 | 国产激情自拍视频 | 99视频免费 | 成人精品一区亚洲午夜久久久 | 亚洲欧美一区二区三区在线 | 91视频久久 | 日韩视频在线一区二区 | 国产美女高潮 | 日本黄色大片免费 | 日韩视频一区二区 | 国产日韩精品久久 | 国产成人久久精品一区二区三区 | 久久精品国产精品青草 | 日韩精品一区二区久久 | 91精品国产综合久久国产大片 | 在线观看亚洲精品视频 | 日韩中文在线视频 | a级大毛片 | 色综合网站 | 国产一区二区三区四区区 | 亚洲狠狠爱 | 99久久久久久久久 | 一区二区三区回区在观看免费视频 | 国产成人精品一区二区三区网站观看 | 国产视频不卡一区 | 超碰在线人人 | 综合五月| 毛片av免费看 | www.黄色在线观看 |