Flash Sale and Ticket Reservation System
0. 秒殺
秒殺業務特點:
source from 实践出真知:全网最强秒杀系统架构解密,不是所有的秒杀都是秒杀!! : https://developer.aliyun.com/article/791806#slide-6
限時、限量、限價 在規定的時間內進行;秒殺活動中商品的數量有限;商品的價格會遠遠低於原來的價格,也就是說,在秒殺活動中,商品會以遠遠低於原來的價格出售。 例如,秒殺活動的時間僅限於某天上午10點到10點半,商品數量只有10萬件,售完為止,而且商品的價格非常低,例如:1元購等業務場景。 限時、限量和限價可以單獨存在,也可以組合存在。
活動預熱 需要提前配置活動;活動還未開始時,用戶可以查看活動的相關信息;秒殺活動開始前,對活動進行大力宣傳。
持續時間短 購買的人數數量龐大;商品會迅速售完。 在系統流量呈現上,就會出現一個突刺現象,此時的並發訪問量是非常高的,大部分秒殺場景下,商品會在極短的時間內售完。
秒殺技術特點:
瞬時並發量非常高 大量用戶會在同一時間搶購商品;瞬間並發峰值非常高。
讀多寫少 系統中商品頁的訪問量巨大;商品的可購買數量非常少;庫存的查詢訪問數量遠遠大於商品的購買數量。
在商品頁中往往會加入一些限流措施,例如早期的秒殺系統商品頁會加入驗證碼來平滑前端對系統的訪問流量,近期的秒殺系統商品詳情頁會在用戶打開頁面時,提示用戶登錄系統。這都是對系統的訪問進行限流的一些措施。
- 流程簡單 秒殺系統的業務流程一般比較簡單;總體上來說,秒殺系統的業務流程可以概括為:下單減庫存。
秒殺三個階段:
從秒殺開始到結束,往往會經曆三個階段:
- 準備階段:這個階段也叫作系統預熱階段,此時會提前預熱秒殺系統的業務數據,往往這個時候,用戶會不斷刷新秒殺頁面,來查看秒殺活動是否已經開始。在一定程度上,通過用戶不斷刷新頁面的操作,可以將一些數據存儲到Redis中進行預熱。

- 秒殺階段:這個階段主要是秒殺活動的過程,會產生瞬時的高並發流量,對系統資源會造成巨大的衝擊,所以,在秒殺階段一定要做好系統防護。
- 結算階段:完成秒殺後的數據處理工作,比如數據的一致性問題處理,異常情況處理,商品的回倉處理等。
針對這種短時間內大流量的系統來說,就不太適合使用系統擴容了,因為即使系統擴容了,也就是在很短的時間內會使用到擴容後的系統,大部分時間內,系統無需擴容即可正常訪問。
秒殺系統時序圖
同步下單流程
這種方式支撐的並發量並不是太高

異步下單流程

引入了異步處理機制,在異步處理中,系統使用多少資源,分配多少線程來處理相應的任務,是可以進行控制的
採用短輪詢查詢的方式,會不會存在直到超時也查詢不到是否具有秒殺資格的狀態呢? 答案是:有可能!這裡我們試想一下秒殺的真實場景,商家參加秒殺活動本質上不是為了賺錢,而是提升商品的銷量和商家的知名度,吸引更多的用戶來買自己的商品。 所以,我們不必保證用戶能夠100%的查詢到是否具有秒殺資格的狀態。
為什麼只在異步下單流程的粉色部分採用異步處理,而沒有在其他部分採取異步削峰和填谷的措施呢? 這是因為在異步下單流程的設計中,無論是在產品設計上還是在接口設計上,我們在用戶發起秒殺請求階段對用戶的請求進行了限流操作,可以說,系統的限流操作是非常前置的。 在用戶發起秒殺請求時進行了限流,系統的高峰流量已經被平滑解決了,再往後走,其實係統的並發量和系統流量並不是非常高了。 所以,網上很多的文章和帖子中在介紹秒殺系統時,說是在下單時使用異步削峰來進行一些限流操作,那都是在扯淡! 因為下單操作在整個秒殺系統的流程中屬於比較靠後的操作了,限流操作一定要前置處理,在秒殺業務後面的流程中做限流操作是沒啥卵用的。
高並發“黑科技”與致胜奇招
假設,在秒殺系統中我們使用Redis實現緩存,假設Redis的讀寫並發量在5萬左右。 我們的商城秒殺業務需要支持的並發量在100萬左右。如果這100萬的並發全部打入Redis中,Redis很可能就會掛掉, 那麼,我們如何解決這個問題呢?接下來,我們就一起來探討這個問題。
在高並發的秒殺系統中,如果採用Redis緩存數據,則Redis緩存的並發處理能力是關鍵,因為很多的前綴操作都需要訪問Redis。 而異步削峰只是基本的操作,關鍵還是要保證Redis的並發處理能力。 解決這個問題的關鍵思想就是:分而治之,將商品庫存分開放。
暗度陳倉
我們在Redis中存儲秒殺商品的庫存數量時,可以將秒殺商品的庫存進行“分割”存儲來提升Redis的讀寫並發量。
例如,原來的秒殺商品的id為10001,庫存為1000件,在Redis中的存儲為(10001, 1000),我們將原有的庫存分割為5份,則每份的庫存為200件,此時,我們在Redia中存儲的信息為(10001_0, 200),(10001_1, 200),(10001_2, 200),(10001_3, 200),(10001_4, 200)。

此時,我們將庫存進行分割後,每個分割後的庫存使用商品id加上一個數字標識來存儲,這樣,在對存儲商品庫存的每個Key進行Hash運算時,得出的Hash結果是不同的,這就說明,存儲商品庫存的Key有很大概率不在Redis的同一個槽位中,這就能夠提升Redis處理請求的性能和並發量。
分割庫存後,我們還需要在Redis中存儲一份商品id和分割庫存後的Key的映射關係,
此時映射關係的Key為商品的id,也就是10001,Value為分割庫存後存儲庫存信息的Key,也就是10001_0,10001_1,10001_2,10001_3,10001_4。
在Redis中我們可以使用List來存儲這些值。
在真正處理庫存信息時,我們可以先從Redis中查詢出秒殺商品對應的分割庫存後的所有Key,同時使用AtomicLong(JAVA)或Golang的sync/atomicpackage來記錄當前的請求數量,
使用請求數量對從Redia中查詢出的秒殺商品對應的分割庫存後的所有Key的長度進行求模運算,得出的結果為0,1,2,3,4。
再在前面拼接上商品id就可以得出真正的庫存緩存的Key。
此時,就可以根據這個Key直接到Redis中獲取相應的庫存信息。
移花接木
在高並發業務場景中,我們可以直接使用Lua腳本庫(OpenResty)從負載均衡層直接訪問緩存。
這裡,我們思考一個場景:如果在秒殺業務場景中,秒殺的商品被瞬間搶購一空。此時,用戶再發起秒殺請求時,如果系統由負載均衡層請求應用層的各個服務,再由應用層的各個服務訪問緩存和數據庫,其實,本質上已經沒有任何意義了,因為商品已經賣完了,再通過系統的應用層進行層層校驗已經沒有太多意義了!!而應用層的並發訪問量是以百為單位的,這又在一定程度上會降低系統的並發度。
為了解決這個問題,此時,我們可以在系統的負載均衡層取出用戶發送請求時攜帶的用戶id,商品id和秒殺活動id等信息,直接通過Lua腳本等技術來訪問緩存中的庫存信息。如果秒殺商品的庫存小於或者等於0,則直接返回用戶商品已售完的提示信息,而不用再經過應用層的層層校驗了。
Redis助力秒殺系統
我們可以在Redis中設計一個Hash數據結構,來支持商品庫存的扣減操作,如下所示。
seckill:goodsStock:${goodsId}{
totalCount:200,
initStatus:0,
seckillCount:0
}
在我們設計的Hash數據結構中,有三個非常主要的屬性。
- totalCount:表示參與秒殺的商品的總數量,在秒殺活動開始前,我們就需要提前將此值加載到Redis緩存中。
- initStatus:我們把這個值設計成一個布爾值。秒殺開始前,這個值為0,表示秒殺未開始。可以通過定時任務或者後台操作,將此值修改為1,則表示秒殺開始。
- seckillCount:表示秒殺的商品數量,在秒殺過程中,此值的上限為totalCount,當此值達到totalCount時,表示商品已經秒殺完畢。
我們可以通過下面的代碼片段在秒殺預熱階段,將要參與秒殺的商品數據加載的緩存。
Golang:
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
type SeckillCacheBuilder struct {
RedisClient *redis.Client
}
func (s *SeckillCacheBuilder) GetCacheKey(id string) string {
return "seckill:goodsStock:" + id
}
func (s *SeckillCacheBuilder) Prepare(id string, totalCount int) error {
ctx := context.Background()
key := s.GetCacheKey(id)
goods := map[string]interface{}{
"totalCount": totalCount,
"initStatus": 0,
"seckillCount": 0,
}
err := s.RedisClient.HMSet(ctx, key, goods).Err()
if err != nil {
return fmt.Errorf("failed to set hash: %s", err)
}
return nil
}
JAVA:
/**
* @author binghe
* @description 秒杀前构建商品缓存代码示例
*/
public class SeckillCacheBuilder{
private static final String GOODS_CACHE = "seckill:goodsStock:";
private String getCacheKey(String id) {
return GOODS_CACHE.concat(id);
}
public void prepare(String id, int totalCount) {
String key = getCacheKey(id);
Map<String, Integer> goods = new HashMap<>();
goods.put("totalCount", totalCount);
goods.put("initStatus", 0);
goods.put("seckillCount", 0);
redisTemplate.opsForHash().putAll(key, goods);
}
}
秒殺開始的時候,我們需要在代碼中首先判斷緩存中的seckillCount值是否小於totalCount值,如果seckillCount值確實小於totalCount值,我們才能夠對庫存進行鎖定。 在我們的程序中,這兩步其實並不是原子性的。 如果在分佈式環境中,我們通過多台機器同時操作Redis緩存,就會發生同步問題,進而引起“超賣”的嚴重後果。
在電商領域,有一個專業名詞叫作“超賣”。 顧名思義:“超賣”就是說賣出的商品數量比商品的庫存數量多,這在電商領域是一個非常嚴重的問題。那麼,我們如何解決“超賣”問題呢?
Lua腳本完美解決超賣問題
我們如何解決多台機器同時操作Redis出現的同步問題呢?一個比較好的方案就是使用Lua腳本。我們可以使用Lua腳本將Redis中扣減庫存的操作封裝成一個原子操作,這樣就能夠保證操作的原子性,從而解決高並發環境下的同步問題。
例如,我們可以編寫如下的Lua腳本代碼,來執行Redis中的庫存扣減操作。 Lua腳本代碼如下所示。
local resultFlag = "0"
local n = tonumber(ARGV[1])
local key = KEYS[1]
local goodsInfo = redis.call("HMGET",key,"totalCount","seckillCount")
local total = tonumber(goodsInfo[1])
local alloc = tonumber(goodsInfo[2])
if not total then
return resultFlag
end
if total >= alloc + n then
local ret = redis.call("HINCRBY",key,"seckillCount",n)
return tostring(ret)
end
return resultFlag
Golang:
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
type SeckillService struct {
RedisClient *redis.Client
}
func (s *SeckillService) GetCacheKey(id string) string {
return "seckill:goodsStock:" + id
}
func (s *SeckillService) SecKill(id string, number int) (int, error) {
ctx := context.Background()
key := s.GetCacheKey(id)
script := redis.NewScript(`
local seckillCount = redis.call("HINCRBY", KEYS[1], "seckillCount", ARGV[1])
return seckillCount
`)
result, err := script.Run(ctx, s.RedisClient, []string{key}, number).Result()
if err != nil {
return 0, fmt.Errorf("failed to execute script: %s", err)
}
seckillCount, ok := result.(int64)
if !ok {
return 0, fmt.Errorf("invalid result type")
}
return int(seckillCount), nil
}
JAVA:
public int secKill(String id, int number) {
String key = getCacheKey(id);
Object seckillCount = redisTemplate.execute(script, Arrays.asList(key), String.valueOf(number));
return Integer.valueOf(seckillCount.toString());
}
這樣,我們在執行秒殺活動時,就能夠保證操作的原子性,從而有效的避免數據的同步問題,進而有效的解決了“超賣”問題。

限購
MySQL

Redis

為了應對秒殺系統高並發大流量的業務場景,除了秒殺系統本身的業務架構外,我們還要進一步優化服務器硬件的性能,接下來,我們就一起來看一下如何優化服務器的性能。
付款和減庫存的數據一致性 - 分佈式事務
3PC提交,超時機制
優化服務器性能
OS
這裡,我使用的操作系統為CentOS 8,我們可以輸入如下命令來查看操作系統的版本。
CentOS Linux release 8.0.1905 (Core)
對於高並發的場景,我們主要還是優化操作系統的網絡性能,而操作系統中,有很多關於網絡協議的參數,我們對於服務器網絡性能的優化,主要是對這些系統參數進行調優,以達到提升我們應用訪問性能的目的。
系統參數
在CentOS 操作系統中,我們可以通過如下命令來查看所有的系統參數。
/sbin/sysctl -a
部分輸出結果如下所示。

這裡的參數太多了,大概有一千多個,在高並發場景下,我們不可能對操作系統的所有參數進行調優。我們更多的是關注與網絡相關的參數。如果想獲得與網絡相關的參數,那麼,我們首先需要獲取操作系統參數的類型,如下命令可以獲取操作系統參數的類型。
/sbin/sysctl -a|awk -F "." '{print $1}'|sort -k1|uniq
運行命令輸出的結果信息如下所示。
abi
crypto
debug
dev
fs
kernel
net
sunrpc
user
vm
其中的net類型就是我們要關注的與網絡相關的操作系統參數。我們可以獲取net類型下的子類型,如下所示。
/sbin/sysctl -a|grep "^net."|awk -F "[.| ]" '{print $2}'|sort -k1|uniq
運行命令輸出的結果信息如下所示。
bridge
core
ipv4
ipv6
netfilter
nf_conntrack_max
unix
在Linux操作系統中,這些與網絡相關的參數都可以在/etc/sysctl.conf文件裡修改,如果/etc/sysctl.conf 文件中不存在這些參數,我們可以自行在/etc/sysctl.conf 文件中添加這些參數。
在net類型的子類型中,我們需要重點關注的子類型有:core和ipv4。
優化套接字(Socket)緩衝區
如果服務器的網絡套接字(Socket)緩衝區太小,就會導致應用程序讀寫多次才能將數據處理完,這會大大影響我們程序的性能。如果網絡套接字緩衝區設置的足夠大,從一定程度上能夠提升我們程序的性能。
我們可以在服務器的命令行輸入如下命令,來獲取有關服務器套接字緩衝區的信息。
/sbin/sysctl -a|grep "^net."|grep "[r|w|_]mem[_| ]"
運行命令輸出的結果信息如下所示。
net.core.rmem_default = 212992
net.core.rmem_max = 212992
net.core.wmem_default = 212992
net.core.wmem_max = 212992
net.ipv4.tcp_mem = 43545 58062 87090
net.ipv4.tcp_rmem = 4096 87380 6291456
net.ipv4.tcp_wmem = 4096 16384 4194304
net.ipv4.udp_mem = 87093 116125 174186
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096
其中,帶有max、default、min關鍵字的為分別代表:最大值、默認值和最小值; 帶有mem、rmem、wmem關鍵字的分別為:總內存、接收緩衝區內存、發送緩衝區內存。
這裡需要注意的是:帶有rmem 和 wmem關鍵字的單位都是“字節”,而帶有mem關鍵字的單位是“頁”。 “頁”是操作系統管理內存的最小單位,在 Linux 系統裡,默認一頁是 4KB 大小。
如何優化頻繁收發大文件
如果在高並發場景下,需要頻繁的收發大文件,我們該如何優化服務器的性能呢?
這裡,我們可以修改的系統參數如下所示。
net.core.rmem_default
net.core.rmem_max
net.core.wmem_default
net.core.wmem_max
net.ipv4.tcp_mem
net.ipv4.tcp_rmem
net.ipv4.tcp_wmem
這裡,我們做個假設,假設系統最大可以給TCP分配 2GB 內存,最小值為 256MB,壓力值為 1.5GB。按照一頁為 4KB 來計算, tcp_mem 的最小值、壓力值、最大值分別是 65536、393216、524288,單位是“頁” 。
算法
假設系統最大可以分配 2GB 內存,即 2 * 1024MB = 2048MB。最小值為 256MB,壓力值為 1.5GB,即 1536MB。 以 4KB 為一頁的大小來計算,我們可以將內存限制轉換為頁數。 最小值:256MB = 256 * 1024KB = 256 * 1024 * 1024B = 256 * 1024 * 1024B / 4KB (每頁大小) = 65536 頁 = 65536 頁 * 4KB (每頁大小) = 262144KB = 256MB = 256 * 1024MB 壓力值:1.5GB = 1.5 * 1024MB = 1536MB = 1536MB = 1536 * 1024KB = 1572864KB = 1572864KB / 4KB (每頁大小) = 393216 頁 = 393216 頁 * 4KB (每頁大小) = 1572864KB = 1536MB = 1.5 * 1024MB 最大值:2GB = 2 * 1024MB = 2048MB = 2048MB = 2048 * 1024KB = 2097152KB = 2097152KB / 4KB (每頁大小) = 524288 頁 = 524288 頁 * 4KB (每頁大小) = 2097152KB = 2048MB = 2 * 1024MB 所以,tcp_mem 的最小值為 65536 頁、壓力值為 393216 頁、最大值為 524288 頁。假如平均每個文件數據包為 512KB,每個套接字讀寫緩衝區最小可以各容納 2 個數據包,默認可以各容納 4 個數據包,最大可以各容納 10 個數據包,那我們可以算出 tcp_rmem 和 tcp_wmem 的最小值、默認值、最大值分別是 1048576、2097152、5242880,單位是“字節”。 而 rmem_default 和 wmem_default 是 2097152,rmem_max 和 wmem_max 是 5242880。
這裡,還需要注意的是:緩衝區超過了 65535,還需要將 net.ipv4.tcp_window_scaling 參數設置為 1。
經過上面的分析後,我們最終得出的系統調優參數如下所示。
net.core.rmem_default = 2097152
net.core.rmem_max = 5242880
net.core.wmem_default = 2097152
net.core.wmem_max = 5242880
net.ipv4.tcp_mem = 65536 393216 524288
net.ipv4.tcp_rmem = 1048576 2097152 5242880
net.ipv4.tcp_wmem = 1048576 2097152 5242880
算法: tcp_rmem tcp_rmem,它的值由三個參數組成:min, default 和 max。根據題目給出的信息,最小值 min 是 2 個數據包,默認值 default 是 4 個數據包,最大值 max 是 10 個數據包。由於每個數據包大小為 512KB,我們可以計算出 tcp_rmem 的值如下:
tcp_rmem = min * 每個數據包大小, default * 每個數據包大小, max * 每個數據包大小
= 2 * 512KB, 4 * 512KB, 10 * 512KB
= 1024KB, 2048KB, 5120KB
所以,tcp_rmem 的值為 1024KB、2048KB 和 5120KB。
tcp_wmem tcp_wmem,計算方法與 tcp_rmem 類似,只是使用的是寫緩衝區的容量限制。根據題目給出的信息,最小值 min 是 2 個數據包,默認值 default 是 4 個數據包,最大值 max 是 10 個數據包。使用相同的計算方式,我們可以得出 tcp_wmem 的值:
tcp_wmem = min * 每個數據包大小, default * 每個數據包大小, max * 每個數據包大小
= 2 * 512KB, 4 * 512KB, 10 * 512KB
= 1024KB, 2048KB, 5120KB
因此,tcp_wmem 的值為 1024KB、2048KB 和 5120KB。
優化TCP連接
對計算機網絡有一定了解的小伙伴都知道,TCP的連接需要經過“三次握手”和“四次揮手”的,還要經過慢啟動、滑動窗口、粘包算法等支持可靠性傳輸的一系列技術支持。雖然,這些能夠保證TCP協議的可靠性,但有時這會影響我們程序的性能。
那麼,在高並發場景下,我們該如何優化TCP連接呢?
- 關閉粘包算法
如果用戶對於請求的耗時很敏感,我們就需要在TCP套接字上添加tcp_nodelay參數來關閉粘包算法,以便數據包能夠立刻發送出去。此時,我們也可以設置net.ipv4.tcp_syncookies的參數值為1。
- 避免頻繁的創建和回收連接資源
網絡連接的創建和回收是非常消耗性能的,我們可以通過關閉空閒的連接、重複利用已經分配的連接資源來優化服務器的性能。重複利用已經分配的連接資源大家其實並不陌生,像:線程池、數據庫連接池就是複用了線程和數據庫連接。
我們可以通過如下參數來關閉服務器的空閒連接和復用已分配的連接資源。
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time=1800
- 避免重複發送數據包
TCP支持超時重傳機制。如果發送方將數據包已經發送給接收方,但發送方並未收到反饋,此時,如果達到設置的時間間隔,就會觸發TCP的超時重傳機制。為了避免發送成功的數據包再次發送,我們需要將服務器的net.ipv4.tcp_sack參數設置為1。
- 增大服務器文件描述符(file descriptors)數量
在Linux操作系統中,一個網絡連接也會佔用一個文件描述符,連接越多,佔用的文件描述符也就越多。如果文件描述符設置的比較小,也會影響我們服務器的性能。此時,我們就需要增大服務器文件描述符的數量。
例如:fs.file-max = 10240000,表示服務器最多可以打開10240000個文件。
1. 需求理解
秒殺系統場景:
- 2023 年 1 月 8 日 0 點開始,限量 100 台,以 5566 元的價格,搶購最新 iPhone,先到先得,一人限購一台,售完即止
- 搶紅包
- 搶春節車票
Functional Requirement
- 用戶註冊:允許用戶創建帳戶或註冊系統。
- 商品/活動展示:顯示可供搶購或秒殺的商品或活動。
- 庫存管理:追蹤商品或票券的可用數量,並在購買或預訂時實時更新。
- 用戶認證:提供安全的登錄和認證機制,以保護用戶帳戶並防止未經授權的訪問。
- 購物車管理:允許用戶在購買或預訂之前將商品或票券添加到購物車中。
- 下單/預訂:允許用戶下單購買或預訂所選的商品或票券。
- 支付處理:與安全的支付網關集成,處理在線交易並處理支付。
- 訂單/預訂管理:提供管理和跟蹤訂單或預訂的功能,包括訂單歷史、狀態更新和取消選項。
- 庫存保護:實施措施以防止超賣,例如使用鎖或並發控制機制,確保可用數量的準確更新和預訂。
- 通知系統:向用戶發送有關訂單或預訂狀態更新、支付確認和其他相關信息的通知。
- 性能優化:設計系統以處理高並發和大流量,確保快速響應時間,並將停機時間降到最低。
- 報告和分析:生成報告並收集銷售、用戶行為和系統性能的分析數據,用於分析和決策。
English
1. User Registration: Allow users to create accounts or register for the system. 2. Product/Event Listing: Display the available products or events for flash sale or ticket reservation. 3. Inventory Management: Track the available quantity of products or tickets and update it in real-time as purchases or reservations are made. 4. User Authentication: Ensure secure login and authentication mechanisms to protect user accounts and prevent unauthorized access. 5. Cart Management: Enable users to add desired products or tickets to their carts before making a purchase or reservation. 6. Order Placement: Allow users to place orders or reservations for the selected products or tickets. 7. Payment Processing: Integrate with a secure payment gateway to handle online transactions and process payments. 8. Order/Reservation Management: Provide functionality to manage and track orders or reservations, including order history, status updates, and cancellation options. 9. Inventory Protection: Implement measures to prevent overselling, such as using locks or concurrency control mechanisms to ensure that the available quantity is accurately updated and reserved. 10. Notification System: Send notifications to users regarding order or reservation status updates, payment confirmation, and other relevant information. 11. Performance Optimization: Design the system to handle high concurrency and heavy traffic, ensuring fast response times and minimizing downtime. 12. Reporting and Analytics: Generate reports and gather analytics data on sales, user behavior, and system performance for analysis and decision-making.Non-Functional Requirement
- 平日每秒 1000 人訪問該頁面。
- 秒殺時每秒數10萬人訪問該頁面。
QPS 增加 100 倍以上。
可靠性:系統應該具有高度可靠性,能夠持續運行並處理高負載,不容易發生故障或崩潰。
- 性能:系統需要具有高性能,能夠處理大量的並發訪問,並以快速的速度響應用戶的請求。
- 可擴展性:系統應該具有良好的可擴展性,能夠根據需要擴展並處理不斷增長的用戶數量和流量。
- 安全性:系統應該實施適當的安全措施,保護用戶數據和交易信息的機密性和完整性。
- 可用性:系統應該保持高可用性,能夠隨時提供服務,並具有妥善的故障恢復和故障處理機制。
- 可維護性:系統應該易於維護和管理,包括容易進行代碼更改、故障排除和性能調優。
- 日誌和監控:系統應該具有適當的日誌記錄和監控機制,以便跟踪系統的運行狀態、性能指標和潛在的問題。
- 可移植性:系統應該支持多種用戶設備和平台,並提供良好的用戶體驗。
- 隱私保護:系統應該遵守隱私法規,保護用戶的個人信息和隱私。
English
1. Reliability: The system should have high reliability, ensuring continuous operation and handling high loads without frequent failures or crashes. 2. Performance: The system needs to have high performance, capable of handling a large number of concurrent accesses and responding to user requests quickly. 3. Scalability: The system should exhibit good scalability, allowing it to scale up or out to accommodate growing user numbers and traffic demands. 4. Security: The system should implement appropriate security measures to safeguard user data and transaction information, ensuring confidentiality and integrity. 5. Availability: The system should maintain high availability, being accessible at all times, and having proper fault recovery and handling mechanisms. 6. Maintainability: The system should be easy to maintain and manage, including making code changes, troubleshooting issues, and optimizing performance. 7. Logging and Monitoring: The system should have adequate logging and monitoring mechanisms in place to track system operation, performance metrics, and identify potential issues. 8. Portability: The system should support multiple user devices and platforms, providing a seamless user experience. 9. Privacy Protection: The system should comply with privacy regulations and protect user personal information and privacy.2. 資源估算(optional)
- 假如負載均衡層使用的是高性能的Nginx,則我們可以預估Nginx最大的並發度為:10W+,這裡是以萬為單位。
- 假設應用層我們使用的是Gin,預估可承受 800。
- 假設持久層的緩存使用的是Redis,數據庫使用的是MySQL,MySQL的最大並發度可以預估為1000左右,以千為單位。 Redis的最大並發度可以預估為5W左右,以萬為單位。
3. High-Level Diagram

- 雲計算:考慮採用雲服務提供商(如AWS、Azure、Google Cloud等)來構建和擴展搶票系統。這樣可以根據需要動態分配資源,降低運維成本,並且能夠快速應對用戶流量的增長。
- 水平擴展:利用雲服務提供商的彈性伸縮功能,根據需求增加或減少應用服務器和數據庫實例的數量,以滿足並發處理能力的要求。
- 分佈式架構:設計一個分佈式系統,將不同的組件分離並部署在多個節點上。這樣可以提高系統的可擴展性和容錯性。
- 異步消息隊列:使用消息隊列來處理並發請求。將用戶的搶票請求放入消息隊列中,後台的工作進程可以異步處理這些請求,減少用戶等待時間。
- 緩存層:引入緩存來減輕數據庫的負載並提高系統的響應速度。使用分佈式緩存(如Redis集群)來緩存熱門活動信息、座位狀態和用戶會話等數據。
- 優化數據庫:選擇高性能的數據庫(如MySQL、PostgreSQL)或使用數據庫集群來處理大量的並發讀寫請求。合理設計數據庫架構和索引,以提高查詢性能。
- 監控和調優:使用合適的監控工具和指標來實時監測系統的性能和健康狀態。根據監控結果進行調優和優化,以保持系統的高並發處理能力。
前端方案
瀏覽器端(js):
- 頁面靜態化:將活動頁面上的所有可以靜態的元素全部靜態化,並儘量減少動態元素。通過CDN來抗峰值。
- 禁止重複提交:用戶提交之後按鈕置灰,禁止重複提交
- 用戶限流:在某一時間段內只允許用戶提交一次請求,比如可以採取IP限流
- 填寫驗證碼
後端方案
服務端控制器層(網關層)
限制uid(UserID)訪問頻率:我們上面攔截了瀏覽器訪問的請求,但針對某些惡意攻擊或其它插件,在服務端控制層需要針對同一個訪問uid,限制訪問頻率。
- 同一個uid,限制訪問頻度,做頁面緩存,x秒內到達站點層的請求,均返回同一頁面
- 同一個item的查詢,例如手機車次,做頁面緩存,x秒內到達站點層的請求,均返回同一頁面
服務層
上面只攔截了一部分訪問請求,當秒殺的用戶量很大時,即使每個用戶只有一個請求,到服務層的請求數量還是很大。比如我們有100W用戶同時搶100台手機,服務層並發請求壓力至少為100W。
- 採用消息隊列緩存請求:既然服務層知道庫存只有100台手機,那完全沒有必要把100W個請求都傳遞到數據庫啊,那麼可以先把這些請求都寫到消息隊列緩存一下,數據庫層訂閱消息減庫存,減庫存成功的請求返回秒殺成功,失敗的返回秒殺結束。
- 利用緩存應對讀請求:對類似於火車搶票等購票業務,是典型的讀多寫少業務,大部分請求是查詢請求,所以可以利用緩存分擔數據庫壓力。
- 利用緩存應對寫請求:緩存也是可以應對寫請求的,比如我們就可以把數據庫中的庫存數據轉移到Redis緩存中,所有減庫存操作都在Redis中進行,然後再通過後台進程把Redis中的用戶秒殺請求同步到數據庫中。
Cache
Redis是一個分佈式緩存系統,支持多種數據結構,我們可以利用Redis輕鬆實現一個強大的秒殺系統。
我們可以採用Redis 最簡單的key-value數據結構,用一個原子類型的變量值(AtomicInteger)作為key,把用戶id作為value,庫存數量便是原子變量的最大值。對於每個用戶的秒殺,我們使用 RPUSH key value插入秒殺請求, 當插入的秒殺請求數達到上限時,停止所有後續插入。
然後我們可以在台啟動多個工作線程,使用 LPOP key 讀取秒殺成功者的用戶id,然後再操作數據庫做最終的下訂單減庫存操作。
當然,上面Redis也可以替換成消息中間件如ActiveMQ、RabbitMQ等,也可以將緩存和消息中間件 組合起來,緩存系統負責接收記錄用戶請求,消息中間件負責將緩存中的請求同步到數據庫。
對於寫請求,做請求隊列,每次只透有限的寫請求去數據層,如果均成功再放下一批,如果庫存不夠則隊列裡的寫請求全部返回“已售完”
數據庫層
數據庫層是最脆弱的一層,一般在應用設計時在上游就需要把請求攔截掉,數據庫層只承擔“能力範圍內”的訪問請求。 所以,上面通過在服務層引入隊列和緩存,讓最底層的數據庫高枕無憂。
4. 核心子服務設計
- 預訂管理服務(Booking Management Service):負責管理用戶的預訂信息,包括檢查座位的可用性、處理預訂請求、生成預訂單號等。該服務可以與數據庫交互,更新座位狀態並記錄預訂信息。
- 支付服務(Payment Service):處理用戶的支付請求,確保支付的安全性和可靠性。該服務可能與第三方支付提供商集成,處理支付事務並更新訂單的支付狀態。
- 庫存管理服務(Inventory Management Service):跟踪和管理座位的庫存情況,確保座位的可用性和一致性。該服務可以使用緩存來提高座位查詢的性能,並與預訂管理服務進行協調,更新座位的狀態和數量。
- 隊列服務(Queue Service):處理用戶的排隊請求,確保公平和有序的處理順序。該服務可以使用消息隊列來管理用戶的排隊順序,並根據用戶的請求時間戳記或其他標識進行排序。
- 通知服務(Notification Service):向用戶發送相關的通知,例如預訂成功通知、支付成功通知等。該服務可以使用消息推送、郵件或短信等通信方式來發送通知。
- 監控和分析服務(Monitoring and Analytics Service):收集系統的監控數據和日誌信息,進行性能分析和故障排查。該服務可以使用日誌收集工具和監控系統來收集數據,並提供分析報告和告警機制。
5. 接口設計
API設計:
- RESTful架構:使用RESTful風格的API設計,採用清晰的URL結構和HTTP動詞來表示資源和操作,以提供易於理解和使用的接口。
- 資源定義:確定係統中的主要資源,如用戶、活動、票務、訂單等,並設計相應的API端點來處理這些資源的創建、讀取、更新和刪除操作。
- 授權和身份驗證:實施合適的身份驗證和授權機制,確保只有經過認證的用戶可以訪問敏感數據和執行特定操作。
- 輸入驗證和錯誤處理:對於API的輸入參數進行驗證,確保數據的完整性和一致性。實現適當的錯誤處理和錯誤狀態碼返回,以便客戶端能夠正確處理錯誤情況。
- 版本管理:考慮在API設計中引入版本管理,以便在未來進行適度的變更和擴展,同時保持向後兼容性。
/api/v1/users
用戶相關API:
- 創建用戶:POST /api/users
- 獲取用戶詳情:GET /api/users/{userId}
- 更新用戶信息:PUT /api/users/{userId}
- 刪除用戶:DELETE /api/users/{userId}
活動相關API:
- 創建活動:POST /api/events
- 獲取活動詳情:GET /api/events/{eventId}
- 更新活動信息:PUT /api/events/{eventId}
- 刪除活動:DELETE /api/events/{eventId}
- 獲取活動列表:GET /api/events
票務相關API:
- 創建票務:POST /api/tickets
- 獲取票務詳情:GET /api/tickets/{ticketId}
- 更新票務信息:PUT /api/tickets/{ticketId}
- 刪除票務:DELETE /api/tickets/{ticketId}
- 獲取可用票務列表:GET /api/tickets
訂單相關API:
- 創建訂單:POST /api/orders
- 獲取訂單詳情:GET /api/orders/{orderId}
- 更新訂單信息:PUT /api/orders/{orderId}
- 取消訂單:DELETE /api/orders/{orderId}
- 獲取用戶訂單列表:GET /api/orders/user/{userId}
6. 數據結構和Storage
數據庫設計:
- 數據模型設計:根據系統需求和業務規則,設計合適的實體和關係模型,以便存儲用戶、活動、票務和訂單等相關數據。
- 數據庫範式:使用合適的數據庫範式來確保數據的一致性和有效性。根據數據訪問模式和查詢需求,選擇適當的範式級別。
- 索引設計:根據經常進行的查詢操作,設計合理的索引來提高查詢性能。避免過多或不必要的索引,以減少寫操作的性能開銷。
- 關係定義和約束:定義合適的關係和約束,以保持數據的完整性。使用外鍵約束來建立表之間的關聯,並定義適當的級聯操作。
- 查詢優化:通過合理的查詢設計、使用合適的查詢語句和索引,以及定期分析和優化查詢執行計劃,提高數據庫查詢性能。
- 數據備份和恢復:實施定期的數據備份和恢復策略,確保數據的安全性和可靠性。考慮使用冷備份和熱備份等方法來防止數據丟失。
Users (用戶) 表格
| ID | 姓名 | 電子郵件 | 密碼 |
|---|---|---|---|
| 1 | 張三 | user1@example.com | password1 |
| 2 | 李四 | user2@example.com | password2 |
| 3 | 王五 | user3@example.com | password3 |
Events (活動) 表格
| ID | 名稱 | 日期 | 地點 |
|---|---|---|---|
| 1 | 演唱會 | 2023-06-01 | 國家體育館 |
| 2 | 音樂節 | 2023-07-15 | 公園 |
| 3 | 電影首映 | 2023-08-10 | 影院 |
Tickets(票券) 表格
| ID | 活動ID | 票券名稱 | 狀態 |
|---|---|---|---|
| 1 | 1 | A1 | 已售 |
| 2 | 1 | A2 | 可用 |
| 3 | 1 | B1 | 可用 |
| 4 | 2 | C1 | 已售 |
| 5 | 2 | C2 | 可用 |
| 6 | 2 | D1 | 已售 |
| 7 | 3 | E1 | 已售 |
| 8 | 3 | E2 | 已售 |
| 9 | 3 | F1 | 可用 |
Orders (訂單) 表格
| ID | 用戶ID | 活動ID | 票券ID | 時間戳 | 狀態 |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 2 | 2023-05-29 10:00 | 待付款 |
| 2 | 2 | 2 | 5 | 2023-05-29 11:30 | 已付款 |
| 3 | 3 | 3 | 7 | 2023-05-29 12:45 | 已付款 |
Inventory(庫存) 表
| ID | ticket_id | available_qty | reserved_qty |
|---|---|---|---|
ticket_id: 票券的唯一識別碼 available_qty: 票券的可用數量 reserved_qty: 票券的預訂數量
Payment Table
| 欄位名稱 | 類型 | 描述 |
|---|---|---|
| payment_id | int | 支付的唯一識別碼 |
| order_id | int | 訂單的唯一識別碼 |
| amount | decimal | 支付金額 |
| payment_time | datetime | 支付時間 |
| status | varchar | 支付狀態 (支付成功、待支付、支付失敗等) |
7. 擴展性, 容錯性, 延遲需求
擴展性:
- 使用水平擴展策略,將系統的各個組件(如應用服務器、數據庫、緩存等)部署在多個節點上,以分散負載並提高系統的並發處理能力。
- 使用彈性伸縮功能,根據實際需求動態調整應用服務器和數據庫實例的數量。
- 考慮使用雲服務提供商(如AWS、Azure、Google Cloud等)來構建和擴展系統,以便根據需要動態分配資源。
容錯性:
- 使用分佈式架構,將不同組件部署在多個節點上,以提高系統的容錯性和可用性。
- 設計冗餘組件和數據備份策略,以防止單點故障和數據丟失。
- 實施故障檢測和自動恢復機制,以快速檢測和修復系統中的故障。
延遲需求:
- 使用緩存層來減輕數據庫負載並提高系統的響應速度。適當地緩存熱門活動信息、座位狀態和用戶會話等數據。
- 使用非阻塞和異步處理的設計模式,以減少用戶等待時間。
- 優化數據庫架構和索引,以提高查詢性能和降低延遲。
水平擴展:使用分佈式架構和水平擴展的方式來應對用戶數量的增加。將系統拆分為多個可獨立運行的組件,如負載均衡器、應用服務器、數據庫集群等,以提高系統的吞吐量和性能。 緩存優化:使用緩存來減輕數據庫的負載並提高系統的響應速度。常用的技術如Redis或Memcached可用於緩存熱門的活動信息、座位狀態和用戶會話等數據。 異步處理:採用異步處理的方式來提高系統的並發性能。例如,將用戶的預訂請求放入消息隊列,後台異步處理訂單生成、座位鎖定等操作,以減少用戶等待時間。 分佈式鎖:為了避免多用戶同時預訂同一個座位,可以使用分佈式鎖來保證操作的原子性和排他性。這樣可以避免搶票時的衝突和數據不一致問題。 預留機制:為了避免用戶在搶票過程中的長時間等待和不確定性,可以採用預留機制。用戶在選擇座位後,預留一段時間給用戶完成支付操作,如果超時未支付,則釋放座位給其他用戶。 彈性伸縮:根據用戶流量的變化,自動調整系統的資源配置,以保證系統的穩定性和性能。使用自動化工具和雲服務提供商的彈性伸縮功能可以更好地應對用戶數量的波動。
8. Deep dive
如何避免超賣呢?
- 座位鎖定機制:在用戶選擇座位後,將座位標記為鎖定狀態,以防止其他用戶同時選擇同一座位。這樣可以保證一個座位只會被一個用戶購買。
- 並發控制:使用並發控制機制,如數據庫的事務或樂觀鎖,來處理多個用戶同時提交訂單的情況。通過合適的並發控製手段,可以確保只有一個用戶能夠成功購買某個座位或庫存。
- 實時庫存更新:在用戶購買成功後,即時更新座位或庫存的數量,確保系統能夠準確地反映可用的座位數。這樣可以避免多個用戶同時購買同一座位的情況。
- 預留機制:在用戶選擇座位後,預留一定的時間給用戶完成支付操作。如果用戶在規定時間內未支付,釋放座位給其他用戶。這樣可以避免長時間佔用座位而導致其他用戶無法購買的情況。
- 實時監控和報警:實施實時監控系統,監測座位狀態和庫存數量的變化。如果座位或庫存接近售罄或出現異常情況,及時發出警報並採取相應的措施。
- 合理的銷售策略:根據實際的座位容量和庫存數量,制定合理的銷售策略和配額,避免過度售賣或超過實際可用的座位數量。
- 數據一致性和容錯處理:在系統設計和實現中,考慮數據一致性和容錯處理的機制。即使出現異常情況,系統也能夠回滾操作並恢復到之前的狀態,以避免數據不一致或座位狀態混亂的問題。
分布式鎖你會怎麼做
使用分佈式鎖是一種有效的方法來避免超賣問題。分佈式鎖可以確保在分佈式環境下只有一個節點可以同時操作某個資源,從而避免多個用戶同時購買同一座位或庫存。
下面是一個簡單的示例,展示如何使用分佈式鎖來避免超賣:
- 使用可靠的分佈式鎖實現:選擇一個可靠的分佈式鎖實現,例如基於Redis的分佈式鎖。 Redis提供了高性能和可靠的分佈式鎖機制,可以用於協調不同節點的並發操作。
- 在購買流程中獲取分佈式鎖:當用戶選擇座位並準備購買時,首先嘗試獲取分佈式鎖。只有一個用戶能夠成功獲取鎖,而其他用戶將被阻塞等待或放棄購買。
- 鎖定座位或庫存:成功獲取分佈式鎖後,將座位或庫存標記為已鎖定狀態,以防止其他用戶同時購買。這可以確保同一座位或庫存只會被一個用戶購買。
- 完成購買操作並釋放鎖:用戶完成購買操作後,釋放分佈式鎖,並更新座位或庫存的狀態。其他用戶可以繼續購買其他可用的座位或庫存。
除了使用分佈式鎖,還可以結合其他的並發控制機制和系統設計原則來提高系統的可靠性和性能,例如合適的數據庫事務、樂觀鎖、消息隊列等。
消息隊列在處理超賣問題中的應用方式
消息隊列是一種常用的技術來處理系統中的異步消息傳遞和任務處理。 儘管消息隊列本身不能直接解決超賣問題,但可以在搶票系統中起到一定的輔助作用,幫助實現可靠性和流量控制。
以下是一些消息隊列在處理超賣問題中的應用方式:
- 庫存控制:使用消息隊列來處理庫存的變更和控制。在用戶成功購買一張票後,將相應的庫存數量發佈到消息隊列中。消費者從消息隊列中獲取庫存變更消息,並更新系統中的庫存狀態。通過消息隊列的順序處理能力,可以確保庫存變更的原子性和順序性,避免超賣問題。
- 限流和流量控制:使用消息隊列作為緩衝層來控制系統的並發請求和流量。當用戶發起購買請求時,請求可以先進入消息隊列中進行排隊,再由消費者逐個處理。通過控制消息隊列的消費速率和並發處理能力,可以有效限制系統的並發訪問量,避免超賣和系統崩潰。
- 異步通知和退款處理:使用消息隊列來處理退款和通知操作。當某個座位被多個用戶同時購買時,可以將退款請求放入消息隊列中,由消費者處理退款操作並通知用戶。這樣可以實現退款的異步處理,避免影響購買流程的實時性和性能。
需要注意的是,雖然消息隊列可以提供一些輔助措施來減輕超賣問題,但仍然需要在系統的其他組件和設計中綜合考慮。例如,結合分佈式鎖、事務處理、數據一致性等機制,以確保系統的可靠性和正確性。
此外,選擇適當的消息隊列技術和配置,以及優化消息的處理和消費能力,也是確保消息隊列在處理超賣問題中有效的關鍵因素。 消息隊列並非直接解決超賣問題的工具,但在搶票系統中的合理應用可以提供輔助手段,增強系統的可靠性和擴展性。
MySQL DB 的優化, 你會怎麼做跟優化哪些表?
數據庫的優化是提高系統性能和響應速度的重要方面。以下是一些常見的數據庫優化策略和可優化的表:
- 索引優化:分析查詢頻率高、經常用於過濾和排序的字段,為這些字段創建合適的索引。確保索引的選擇和創建是基於實際查詢需求和數據訪問模式的分析,避免創建過多或不必要的索引。常見的優化表包括用戶表、票務表、訂單表等。
- 查詢優化:審查慢查詢日誌,分析查詢語句的執行計劃,確認是否存在性能較差的查詢。對於復雜的查詢,考慮使用適當的查詢優化技術,如索引覆蓋、連接優化、子查詢優化等。優化常用的查詢語句,例如獲取用戶訂單列表、獲取活動詳情等。
- 數據庫分區:對於數據量巨大的表,考慮使用數據庫分區技術進行水平分割。將數據分散存儲在多個物理存儲單元上,可以提高查詢和維護的效率。例如,根據活動日期對票務表進行分區,每個分區存儲一段時間內的票務數據。
- 冗餘數據和緩存:根據業務需求,評估是否可以在數據庫中引入冗餘數據來減少查詢和連接的複雜性。此外,使用緩存技術如Redis來緩存頻繁讀取的數據,減輕數據庫的負載。例如,緩存熱門活動信息、座位狀態和用戶會話等數據。
- 批量操作和事務優化:對於批量插入、更新或刪除操作,使用批量處理技術和合適的事務管理,減少與數據庫的交互次數,提高效率。例如,批量更新票務庫存或批量插入用戶訂單。
- 定期維護和優化:定期執行數據庫維護任務,如索引重建、統計信息更新、碎片整理等,以保持數據庫的健康狀態和性能穩定。優化表的結構和數據類型,確保最佳的存儲和查詢效率。
可能需要優化的常見表:
- 用戶表(User Table):這是存儲用戶信息的表,包括用戶ID、用戶名、聯繫方式等。優化該表可以包括為經常用於查詢和過濾的字段創建索引,以及合理設計數據類型和約束。
- 活動表(Event Table):這是存儲搶票活動信息的表,包括活動ID、活動名稱、活動時間等。優化該表可能涉及對經常查詢的字段創建索引,以及合理地劃分數據分區。
- 票務表(Ticket Table):這是存儲票務信息的表,包括票務ID、活動ID、座位信息、庫存數量等。優化該表可能包括對活動ID和座位信息字段創建索引,以及考慮數據庫分區和冗餘數據的應用。
- 訂單表(Order Table):這是存儲用戶訂單信息的表,包括訂單ID、用戶ID、活動ID、票務ID等。優化該表可能包括對用戶ID和活動ID字段創建索引,以及適當的事務管理和批量操作優化。
支付表(Payment Table):這是存儲用戶支付信息的表,包括支付ID、訂單ID、支付狀態等。優化該表可能包括對訂單ID字段創建索引,以及適當的事務管理和緩存應用。
慢查詢?
MySQL 使用 UPDATE 語句自帶行鎖
在 MySQL 中,使用 UPDATE 語句可以自帶行鎖,以確保在更新期間其他會話不能修改相同的行數據。要使用 UPDATE 語句自帶的行鎖,請確保滿足以下條件:
事務隔離級別:你需要將事務隔離級別設置為支持行級鎖的級別,例如 READ COMMITTED 或更高級別,以確保行級鎖生效。可以使用以下語句設置事務隔離級別:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;WHERE 子句:在 UPDATE 語句中,使用 WHERE 子句指定要更新的行。行級鎖將在 WHERE 子句中指定的行上設置。例如:
UPDATE table_name SET column1 = value1 WHERE id = 1;上述語句將更新 table_name 表中 id 為 1 的行,並自動應用行級鎖。
當執行 UPDATE 語句時,MySQL 會自動為涉及的行設置行級鎖,以確保數據的一致性和並發性。其他會話想要修改相同行的數據將被阻塞,直到持有行鎖的會話釋放鎖。
需要注意的是,使用行級鎖可能會影響數據庫的並發性和性能,尤其在高並發的情況下。因此,在設計數據庫應用程序時,應根據實際需求和性能要求來選擇是否使用行級鎖。
更多數據庫的隔離級別
支付表的緩存應用是什麼?
在支付表中應用緩存可以提高系統的性能和響應速度,減少對數據庫的頻繁讀取操作。以下是支付表緩存的一些應用方式:
- 支付狀態緩存:將支付表中的支付狀態進行緩存,例如將支付ID和支付狀態的鍵值對存儲在緩存中。當系統需要查詢某個支付狀態時,首先檢查緩存中是否存在對應的支付狀態,如果存在則直接返回,避免了對數據庫的查詢操作。
- 支付詳情緩存:緩存支付表中的支付詳情,包括訂單ID、支付金額、支付時間等信息。當系統需要查詢支付詳情時,先檢查緩存中是否存在對應的支付詳情,如果存在則直接返回,減少了對數據庫的查詢操作。
- 支付驗證緩存:將支付表中的支付驗證信息進行緩存,例如將訂單ID和支付驗證結果的鍵值對存儲在緩存中。當系統需要驗證某個訂單的支付情況時,首先檢查緩存中是否存在對應的支付驗證結果,如果存在則直接使用緩存結果,避免了對數據庫的查詢操作。
- 過期時間管理:為緩存中的支付信息設置適當的過期時間,以確保緩存中的數據與數據庫中的數據保持一致性。可以根據業務需求和數據更新頻率來設置合適的過期時間,例如支付狀態可能不經常變動,可以設置較長的過期時間。
需要注意的是,支付信息屬於敏感數據,緩存的設計應該考慮數據的安全性和保護措施。可以採用合適的緩存策略,如對數據進行加密或使用訪問控制機制,以確保支付數據的機密性和完整性。
如何在使用者搶票時出現排隊中,需等x個人 之類的提示?
在設計搶票系統時,可以通過以下方式實現在用戶搶票時顯示排隊信息,包括需等待的人數:
- 用戶請求排隊接口:當用戶請求搶票時,他們將發送一個請求到後端服務器的排隊接口。該接口用於處理用戶的搶票請求並返回相應的排隊信息。
- 排隊信息存儲:後端服務器可以使用數據庫或緩存來存儲排隊信息。每個用戶請求將被記錄,並分配一個唯一的排隊編號或標識符。
- 排隊邏輯處理:後端服務器根據當前排隊隊列的長度,確定用戶在隊列中的位置。可以使用類似先進先出(FIFO)的隊列數據結構來管理排隊順序。
- 排隊信息返回:後端服務器返回用戶的排隊信息,包括用戶在隊列中的位置、需等待的人數等。可以將這些信息作為響應的一部分返回給用戶。
- 前端界面顯示:前端界面接收到排隊信息後,根據返回的信息更新界面上的排隊提示,告知用戶當前的位置和需等待的人數。
通過以上步驟,用戶在搶票時可以得到實時的排隊信息,並知道自己在隊列中的位置和需等待的人數。這樣的提示可以幫助用戶了解當前的狀況,提高用戶體驗並減少用戶的不確定感。
需要注意的是,排隊系統的實現可能會涉及到並發訪問、鎖定機制等問題,以確保數據的一致性和並發處理的正確性。此外,還可以考慮使用消息隊列等技術來處理排隊請求,以提高系統的可擴展性和性能。
用消息隊列處理 怎麼知道有多少人在排隊跟要等待多少時間
使用消息隊列處理排隊系統時,可以通過以下方法了解有多少人在排隊以及預計等待時間:
- 隊列長度:消息隊列中的消息數量可以表示當前正在排隊的人數。通過查詢消息隊列的長度或者獲取消息隊列的消息數量,可以得知有多少人在排隊。
- 預估等待時間:通過根據排隊系統的業務邏輯和處理速度,結合當前隊列的長度,可以估計用戶需要等待的時間。例如,假設每個請求的處理時間為t,隊列中有n個請求,那麼預計等待時間可以估計為 t × n。
- 實時監控:使用監控工具或儀錶盤來實時監控隊列長度和處理速度。這可以幫助系統管理員或運維團隊實時了解排隊情況,以及預測和調整系統資源以滿足需求。
- 消息處理時間記錄:在消息隊列中,可以記錄每個請求進入隊列的時間戳,並在請求處理完成後計算實際等待時間。通過對歷史數據的分析,可以得出平均等待時間、最長等待時間等指標,用於更準確地估計用戶的等待時間。
- 提供排隊信息接口:為了讓用戶能夠實時了解自己在隊列中的位置和預計等待時間,可以提供一個接口供用戶查詢排隊信息。用戶可以通過調用該接口獲取自己在隊列中的位置以及預計的等待時間。
Kafka 當作消息隊列, 如果用go來寫怎麼算出隊列長度
Kafka 是一種分佈式的消息隊列系統,它被廣泛用於高吞吐量、可擴展的數據流處理場景。以下是一些可以使用 Kafka 作為消息隊列的常見應用場景:
- 實時日誌收集和分析:Kafka 可以作為日誌收集器,將多個日誌源發送到中央集群進行實時處理和分析。
- 異步消息傳遞:Kafka 提供可靠的消息傳遞機制,允許應用程序之間進行異步通信。
- 流式處理:Kafka 可以與流處理框架(如Apache Flink、Apache Spark等)集成,用於實時數據處理和分析。
- 事件驅動架構:Kafka 可以作為事件發布/訂閱系統,用於構建高度可擴展和松耦合的應用程序架構。
關於如何使用 Go 語言來獲取 Kafka 隊列的長度,你可以使用 Kafka Go 客戶端庫(如sarama)來實現。 https://github.com/Shopify/sarama
下面是一個示例代碼,演示如何獲取 Kafka 隊列的長度:
package main
import (
"fmt"
"github.com/Shopify/sarama"
)
func main() {
config := sarama.NewConfig()
brokers := []string{"localhost:9092"} // Kafka brokers地址
// 創建一個 Kafka consumer
consumer, err := sarama.NewConsumer(brokers, config)
if err != nil {
fmt.Println("Failed to create Kafka consumer:", err)
return
}
defer consumer.Close()
topic := "my-topic" // 隊列的主題
// 獲取指定主題的分區列表
partitions, err := consumer.Partitions(topic)
if err != nil {
fmt.Println("Failed to get partitions for topic:", err)
return
}
totalLength := 0
// 遍歷分區列表,獲取每個分區的當前隊列長度
for _, partition := range partitions {
// 獲取指定主題和分區的最新消費偏移量
offset, err := consumer.GetOffset(topic, partition, sarama.OffsetNewest)
if err != nil {
fmt.Println("Failed to get offset for partition:", err)
return
}
// 獲取指定主題和分區的當前隊列長度
length := offset + 1
totalLength += int(length)
}
fmt.Println("Queue length:", totalLength)
}
以上代碼示例中,我們創建了一個 Kafka consumer,連接到指定的 Kafka brokers。 然後,我們獲取指定主題的分區列表,並遍歷每個分區,獲取每個分區的當前隊列長度。最後,我們計算所有分區長度的總和,即為整個隊列的長度。
Reference
Source from : https://blog.csdn.net/a93119311/article/details/121009571