“12306”是如何支撐百萬QPS的械拍?

作者:繪你一世傾城

鏈接:https://juejin.im/post/5d84e21f6fb9a06ac8248149

每到節(jié)假日期間傍妒,一二線城市返鄉(xiāng)、外出游玩的人們幾乎都面臨著一個問題:搶火車票拒迅!

12306 搶票,極限并發(fā)帶來的思考

雖然現(xiàn)在大多數(shù)情況下都能訂到票她倘,但是放票瞬間即無票的場景璧微,相信大家都深有體會。

尤其是春節(jié)期間硬梁,大家不僅使用 12306前硫,還會考慮“智行”和其他的搶票軟件,全國上下幾億人在這段時間都在搶票荧止。

“12306 服務(wù)”承受著這個世界上任何秒殺系統(tǒng)都無法超越的 QPS屹电,上百萬的并發(fā)再正常不過了!

筆者專門研究了一下“12306”的服務(wù)端架構(gòu)跃巡,學習到了其系統(tǒng)設(shè)計上很多亮點危号,在這里和大家分享一下并模擬一個例子:如何在 100 萬人同時搶 1 萬張火車票時,系統(tǒng)提供正常素邪、穩(wěn)定的服務(wù)葱色。

Github代碼地址:

https://github.com/GuoZhaoran/spikeSystem

1、大型高并發(fā)系統(tǒng)架構(gòu)

高并發(fā)的系統(tǒng)架構(gòu)都會采用分布式集群部署娘香,服務(wù)上層有著層層負載均衡苍狰,并提供各種容災(zāi)手段(雙火機房、節(jié)點容錯烘绽、服務(wù)器災(zāi)備等)保證系統(tǒng)的高可用淋昭,流量也會根據(jù)不同的負載能力和配置策略均衡到不同的服務(wù)器上。

下邊是一個簡單的示意圖:

image

1.1 負載均衡簡介

上圖中描述了用戶請求到服務(wù)器經(jīng)歷了三層的負載均衡安接,下邊分別簡單介紹一下這三種負載均衡翔忽。

①OSPF(開放式最短鏈路優(yōu)先)是一個內(nèi)部網(wǎng)關(guān)協(xié)議(Interior Gateway Protocol,簡稱 IGP)

OSPF 通過路由器之間通告網(wǎng)絡(luò)接口的狀態(tài)來建立鏈路狀態(tài)數(shù)據(jù)庫,生成最短路徑樹歇式,OSPF 會自動計算路由接口上的 Cost 值驶悟,但也可以通過手工指定該接口的 Cost 值,手工指定的優(yōu)先于自動計算的值材失。

OSPF 計算的 Cost痕鳍,同樣是和接口帶寬成反比,帶寬越高龙巨,Cost 值越小笼呆。到達目標相同 Cost 值的路徑,可以執(zhí)行負載均衡旨别,最多 6 條鏈路同時執(zhí)行負載均衡诗赌。

②LVS (Linux Virtual Server)

它是一種集群(Cluster)技術(shù),采用 IP 負載均衡技術(shù)和基于內(nèi)容請求分發(fā)技術(shù)秸弛。

調(diào)度器具有很好的吞吐率铭若,將請求均衡地轉(zhuǎn)移到不同的服務(wù)器上執(zhí)行,且調(diào)度器自動屏蔽掉服務(wù)器的故障递览,從而將一組服務(wù)器構(gòu)成一個高性能的奥喻、高可用的虛擬服務(wù)器。

③Nginx

想必大家都很熟悉了非迹,是一款非常高性能的 HTTP 代理/反向代理服務(wù)器环鲤,服務(wù)開發(fā)中也經(jīng)常使用它來做負載均衡。

Nginx 實現(xiàn)負載均衡的方式主要有三種:

  • 輪詢

  • 加權(quán)輪詢

  • IP Hash 輪詢

下面我們就針對 Nginx 的加權(quán)輪詢做專門的配置和測試憎兽。

1.2 Nginx 加權(quán)輪詢的演示

Nginx 實現(xiàn)負載均衡通過 Upstream 模塊實現(xiàn)冷离,其中加權(quán)輪詢的配置是可以給相關(guān)的服務(wù)加上一個權(quán)重值,配置的時候可能根據(jù)服務(wù)器的性能纯命、負載能力設(shè)置相應(yīng)的負載西剥。

下面是一個加權(quán)輪詢負載的配置,我將在本地的監(jiān)聽 3001-3004 端口亿汞,分別配置 1瞭空,2,3疗我,4 的權(quán)重:

#配置負載均衡
    upstream load_rule {
       server 127.0.0.1:3001 weight=1;
       server 127.0.0.1:3002 weight=2;
       server 127.0.0.1:3003 weight=3;
       server 127.0.0.1:3004 weight=4;
    }
    ...
    server {
    listen       80;
    server_name  load_balance.com www.load_balance.com;
    location / {
       proxy_pass http://load_rule;
    }
}

我在本地 /etc/hosts 目錄下配置了 www.load_balance.com 的虛擬域名地址咆畏。

接下來使用 Go 語言開啟四個 HTTP 端口監(jiān)聽服務(wù),下面是監(jiān)聽在 3001 端口的 Go 程序吴裤,其他幾個只需要修改端口即可:

package main

import (
    "net/http"
    "os"
    "strings"
)

func main() {
    http.HandleFunc("/buy/ticket", handleReq)
    http.ListenAndServe(":3001", nil)
}

//處理請求函數(shù),根據(jù)請求將響應(yīng)結(jié)果信息寫入日志
func handleReq(w http.ResponseWriter, r *http.Request) {
    failedMsg :=  "handle in port:"
    writeLog(failedMsg, "./stat.log")
}

//寫入日志
func writeLog(msg string, logPath string) {
    fd, _ := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
    defer fd.Close()
    content := strings.Join([]string{msg, "
"}, "3001")
    buf := []byte(content)
    fd.Write(buf)
}

我將請求的端口日志信息寫到了 ./stat.log 文件當中旧找,然后使用 AB 壓測工具做壓測:

ab -n 1000 -c 100 http://www.load_balance.com/buy/ticket

統(tǒng)計日志中的結(jié)果,3001-3004 端口分別得到了 100麦牺、200钮蛛、300鞭缭、400 的請求量。這和我在 Nginx 中配置的權(quán)重占比很好的吻合在了一起魏颓,并且負載后的流量非常的均勻岭辣、隨機。

具體的實現(xiàn)大家可以參考 Nginx 的 Upsteam 模塊實現(xiàn)源碼甸饱,這里推薦一篇文章《Nginx 中 Upstream 機制的負載均衡》:

https://www.kancloud.cn/digest/understandingnginx/202607

2沦童、秒殺搶購系統(tǒng)選型

回到我們最初提到的問題中來:火車票秒殺系統(tǒng)如何在高并發(fā)情況下提供正常、穩(wěn)定的服務(wù)呢柜候?從上面的介紹我們知道用戶秒殺流量通過層層的負載均衡搞动,均勻到了不同的服務(wù)器上躏精,即使如此渣刷,集群中的單機所承受的 QPS 也是非常高的。如何將單機性能優(yōu)化到極致呢矗烛?要解決這個問題辅柴,我們就要想明白一件事:通常訂票系統(tǒng)要處理生成訂單、減扣庫存瞭吃、用戶支付這三個基本的階段碌嘀。我們系統(tǒng)要做的事情是要保證火車票訂單不超賣、不少賣歪架,每張售賣的車票都必須支付才有效股冗,還要保證系統(tǒng)承受極高的并發(fā)。這三個階段的先后順序該怎么分配才更加合理呢和蚪?我們來分析一下:

2.2 下單減庫存

image

當用戶并發(fā)請求到達服務(wù)端時止状,首先創(chuàng)建訂單,然后扣除庫存攒霹,等待用戶支付怯疤。這種順序是我們一般人首先會想到的解決方案,這種情況下也能保證訂單不會超賣催束,因為創(chuàng)建訂單之后就會減庫存集峦,這是一個原子操作。但是這樣也會產(chǎn)生一些問題:

  • 在極限并發(fā)情況下抠刺,任何一個內(nèi)存操作的細節(jié)都至關(guān)影響性能塔淤,尤其像創(chuàng)建訂單這種邏輯,一般都需要存儲到磁盤數(shù)據(jù)庫的速妖,對數(shù)據(jù)庫的壓力是可想而知的凯沪。

  • 如果用戶存在惡意下單的情況,只下單不支付這樣庫存就會變少买优,會少賣很多訂單妨马,雖然服務(wù)端可以限制 IP 和用戶的購買訂單數(shù)量挺举,這也不算是一個好方法。

2.2 支付減庫存

image

如果等待用戶支付了訂單在減庫存烘跺,第一感覺就是不會少賣湘纵。但是這是并發(fā)架構(gòu)的大忌,因為在極限并發(fā)情況下滤淳,用戶可能會創(chuàng)建很多訂單梧喷。當庫存減為零的時候很多用戶發(fā)現(xiàn)搶到的訂單支付不了了,這也就是所謂的“超賣”脖咐。也不能避免并發(fā)操作數(shù)據(jù)庫磁盤 IO铺敌。

2.3 預(yù)扣庫存

image

從上邊兩種方案的考慮,我們可以得出結(jié)論:只要創(chuàng)建訂單屁擅,就要頻繁操作數(shù)據(jù)庫 IO偿凭。那么有沒有一種不需要直接操作數(shù)據(jù)庫 IO 的方案呢,這就是預(yù)扣庫存派歌。先扣除了庫存弯囊,保證不超賣,然后異步生成用戶訂單胶果,這樣響應(yīng)給用戶的速度就會快很多匾嘱;那么怎么保證不少賣呢?用戶拿到了訂單早抠,不支付怎么辦霎烙?我們都知道現(xiàn)在訂單都有有效期,比如說用戶五分鐘內(nèi)不支付蕊连,訂單就失效了悬垃,訂單一旦失效,就會加入新的庫存咪奖,這也是現(xiàn)在很多網(wǎng)上零售企業(yè)保證商品不少賣采用的方案盗忱。訂單的生成是異步的,一般都會放到 MQ羊赵、Kafka 這樣的即時消費隊列中處理趟佃,訂單量比較少的情況下,生成訂單非趁两荩快闲昭,用戶幾乎不用排隊。

3靡挥、扣庫存的藝術(shù)

從上面的分析可知序矩,顯然預(yù)扣庫存的方案最合理。我們進一步分析扣庫存的細節(jié)跋破,這里還有很大的優(yōu)化空間簸淀,庫存存在哪里瓶蝴?怎樣保證高并發(fā)下,正確的扣庫存租幕,還能快速的響應(yīng)用戶請求舷手?

在單機低并發(fā)情況下,我們實現(xiàn)扣庫存通常是這樣的:

image

為了保證扣庫存和生成訂單的原子性劲绪,需要采用事務(wù)處理男窟,然后取庫存判斷、減庫存贾富,最后提交事務(wù)歉眷,整個流程有很多 IO,對數(shù)據(jù)庫的操作又是阻塞的颤枪。這種方式根本不適合高并發(fā)的秒殺系統(tǒng)汗捡。接下來我們對單機扣庫存的方案做優(yōu)化:本地扣庫存。我們把一定的庫存量分配到本地機器汇鞭,直接在內(nèi)存中減庫存凉唐,然后按照之前的邏輯異步創(chuàng)建訂單庸追。改進過之后的單機系統(tǒng)是這樣的:

image

這樣就避免了對數(shù)據(jù)庫頻繁的 IO 操作霍骄,只在內(nèi)存中做運算,極大的提高了單機抗并發(fā)的能力淡溯。但是百萬的用戶請求量單機是無論如何也抗不住的读整,雖然 Nginx 處理網(wǎng)絡(luò)請求使用 Epoll 模型,c10k 的問題在業(yè)界早已得到了解決咱娶。但是 Linux 系統(tǒng)下米间,一切資源皆文件,網(wǎng)絡(luò)請求也是這樣膘侮,大量的文件描述符會使操作系統(tǒng)瞬間失去響應(yīng)屈糊。上面我們提到了 Nginx 的加權(quán)均衡策略,我們不妨假設(shè)將 100W 的用戶請求量平均均衡到 100 臺服務(wù)器上琼了,這樣單機所承受的并發(fā)量就小了很多逻锐。

然后我們每臺機器本地庫存 100 張火車票,100 臺服務(wù)器上的總庫存還是 1 萬雕薪,這樣保證了庫存訂單不超賣昧诱,下面是我們描述的集群架構(gòu):

image

問題接踵而至,在高并發(fā)情況下所袁,現(xiàn)在我們還無法保證系統(tǒng)的高可用盏档,假如這 100 臺服務(wù)器上有兩三臺機器因為扛不住并發(fā)的流量或者其他的原因宕機了。那么這些服務(wù)器上的訂單就賣不出去了燥爷,這就造成了訂單的少賣蜈亩。要解決這個問題懦窘,我們需要對總訂單量做統(tǒng)一的管理,這就是接下來的容錯方案稚配。服務(wù)器不僅要在本地減庫存奶赠,另外要遠程統(tǒng)一減庫存。有了遠程統(tǒng)一減庫存的操作药有,我們就可以根據(jù)機器負載情況毅戈,為每臺機器分配一些多余的“Buffer 庫存”用來防止機器中有機器宕機的情況。

我們結(jié)合下面架構(gòu)圖具體分析一下:

image

我們采用 Redis 存儲統(tǒng)一庫存愤惰,因為 Redis 的性能非常高苇经,號稱單機 QPS 能抗 10W 的并發(fā)。在本地減庫存以后宦言,如果本地有訂單扇单,我們再去請求 Redis 遠程減庫存,本地減庫存和遠程減庫存都成功了奠旺,才返回給用戶搶票成功的提示蜘澜,這樣也能有效的保證訂單不會超賣。當機器中有機器宕機時响疚,因為每個機器上有預(yù)留的 Buffer 余票鄙信,所以宕機機器上的余票依然能夠在其他機器上得到彌補,保證了不少賣忿晕。Buffer 余票設(shè)置多少合適呢装诡,理論上 Buffer 設(shè)置的越多,系統(tǒng)容忍宕機的機器數(shù)量就越多践盼,但是 Buffer 設(shè)置的太大也會對 Redis 造成一定的影響鸦采。雖然 Redis 內(nèi)存數(shù)據(jù)庫抗并發(fā)能力非常高,請求依然會走一次網(wǎng)絡(luò) IO咕幻,其實搶票過程中對 Redis 的請求次數(shù)是本地庫存和 Buffer 庫存的總量渔伯。因為當本地庫存不足時,系統(tǒng)直接返回用戶“已售罄”的信息提示肄程,就不會再走統(tǒng)一扣庫存的邏輯锣吼。這在一定程度上也避免了巨大的網(wǎng)絡(luò)請求量把 Redis 壓跨,所以 Buffer 值設(shè)置多少绷耍,需要架構(gòu)師對系統(tǒng)的負載能力做認真的考量吐限。

4、代碼演示

Go 語言原生為并發(fā)設(shè)計褂始,我采用 Go 語言給大家演示一下單機搶票的具體流程诸典。

4.1 初始化工作

Go 包中的 Init 函數(shù)先于 Main 函數(shù)執(zhí)行,在這個階段主要做一些準備性工作崎苗。我們系統(tǒng)需要做的準備工作有:初始化本地庫存狐粱、初始化遠程 Redis 存儲統(tǒng)一庫存的 Hash 鍵值舀寓、初始化 Redis 連接池。另外還需要初始化一個大小為 1 的 Int 類型 Chan肌蜻,目的是實現(xiàn)分布式鎖的功能互墓。也可以直接使用讀寫鎖或者使用 Redis 等其他的方式避免資源競爭,但使用 Channel 更加高效蒋搜,這就是 Go 語言的哲學:不要通過共享內(nèi)存來通信篡撵,而要通過通信來共享內(nèi)存。

Redis 庫使用的是 Redigo豆挽,下面是代碼實現(xiàn):

...
//localSpike包結(jié)構(gòu)體定義
package localSpike

type LocalSpike struct {
    LocalInStock     int64
    LocalSalesVolume int64
}
...
//remoteSpike對hash結(jié)構(gòu)的定義和redis連接池
package remoteSpike
//遠程訂單存儲健值
type RemoteSpikeKeys struct {
    SpikeOrderHashKey string    //redis中秒殺訂單hash結(jié)構(gòu)key
    TotalInventoryKey string    //hash結(jié)構(gòu)中總訂單庫存key
    QuantityOfOrderKey string   //hash結(jié)構(gòu)中已有訂單數(shù)量key
}

//初始化redis連接池
func NewPool() *redis.Pool {
    return &redis.Pool{
        MaxIdle:   10000,
        MaxActive: 12000, // max number of connections
        Dial: func() (redis.Conn, error) {
            c, err := redis.Dial("tcp", ":6379")
            if err != nil {
                panic(err.Error())
            }
            return c, err
        },
    }
}
...
func init() {
    localSpike = localSpike2.LocalSpike{
        LocalInStock:     150,
        LocalSalesVolume: 0,
    }
    remoteSpike = remoteSpike2.RemoteSpikeKeys{
        SpikeOrderHashKey:  "ticket_hash_key",
        TotalInventoryKey:  "ticket_total_nums",
        QuantityOfOrderKey: "ticket_sold_nums",
    }
    redisPool = remoteSpike2.NewPool()
    done = make(chan int, 1)
    done <- 1
}

4.2 本地扣庫存和統(tǒng)一扣庫存

本地扣庫存邏輯非常簡單育谬,用戶請求過來,添加銷量帮哈,然后對比銷量是否大于本地庫存膛檀,返回 Bool 值:

package localSpike
//本地扣庫存,返回bool值
func (spike *LocalSpike) LocalDeductionStock() bool{
    spike.LocalSalesVolume = spike.LocalSalesVolume + 1
    return spike.LocalSalesVolume < spike.LocalInStock
}

注意這里對共享數(shù)據(jù) LocalSalesVolume 的操作是要使用鎖來實現(xiàn)的,但是因為本地扣庫存和統(tǒng)一扣庫存是一個原子性操作娘侍,所以在最上層使用 Channel 來實現(xiàn)咖刃,這塊后邊會講。

統(tǒng)一扣庫存操作 Redis憾筏,因為 Redis 是單線程的嚎杨,而我們要實現(xiàn)從中取數(shù)據(jù),寫數(shù)據(jù)并計算一些列步驟踩叭,我們要配合 Lua 腳本打包命令磕潮,保證操作的原子性:

package remoteSpike
......
const LuaScript = `
        local ticket_key = KEYS[1]
        local ticket_total_key = ARGV[1]
        local ticket_sold_key = ARGV[2]
        local ticket_total_nums = tonumber(redis.call('HGET', ticket_key, ticket_total_key))
        local ticket_sold_nums = tonumber(redis.call('HGET', ticket_key, ticket_sold_key))
        -- 查看是否還有余票,增加訂單數(shù)量,返回結(jié)果值
       if(ticket_total_nums >= ticket_sold_nums) then
            return redis.call('HINCRBY', ticket_key, ticket_sold_key, 1)
        end
        return 0
`
//遠端統(tǒng)一扣庫存
func (RemoteSpikeKeys *RemoteSpikeKeys) RemoteDeductionStock(conn redis.Conn) bool {
    lua := redis.NewScript(1, LuaScript)
    result, err := redis.Int(lua.Do(conn, RemoteSpikeKeys.SpikeOrderHashKey, RemoteSpikeKeys.TotalInventoryKey, RemoteSpikeKeys.QuantityOfOrderKey))
    if err != nil {
        return false
    }
    return result != 0
}

我們使用 Hash 結(jié)構(gòu)存儲總庫存和總銷量的信息翠胰,用戶請求過來時容贝,判斷總銷量是否大于庫存,然后返回相關(guān)的 Bool 值之景。在啟動服務(wù)之前斤富,我們需要初始化 Redis 的初始庫存信息:

hmset ticket_hash_key "ticket_total_nums" 10000 "ticket_sold_nums" 0

4.3 響應(yīng)用戶信息

我們開啟一個 HTTP 服務(wù),監(jiān)聽在一個端口上:

package main
...
func main() {
    http.HandleFunc("/buy/ticket", handleReq)
    http.ListenAndServe(":3005", nil)
}

上面我們做完了所有的初始化工作锻狗,接下來 handleReq 的邏輯非常清晰满力,判斷是否搶票成功,返回給用戶信息就可以了轻纪。

package main
//處理請求函數(shù),根據(jù)請求將響應(yīng)結(jié)果信息寫入日志
func handleReq(w http.ResponseWriter, r *http.Request) {
    redisConn := redisPool.Get()
    LogMsg := ""
    <-done
    //全局讀寫鎖
    if localSpike.LocalDeductionStock() && remoteSpike.RemoteDeductionStock(redisConn) {
        util.RespJson(w, 1,  "搶票成功", nil)
        LogMsg = LogMsg + "result:1,localSales:" + strconv.FormatInt(localSpike.LocalSalesVolume, 10)
    } else {
        util.RespJson(w, -1, "已售罄", nil)
        LogMsg = LogMsg + "result:0,localSales:" + strconv.FormatInt(localSpike.LocalSalesVolume, 10)
    }
    done <- 1

    //將搶票狀態(tài)寫入到log中
    writeLog(LogMsg, "./stat.log")
}

func writeLog(msg string, logPath string) {
    fd, _ := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
    defer fd.Close()
    content := strings.Join([]string{msg, "
"}, "")
    buf := []byte(content)
    fd.Write(buf)
}

前邊提到我們扣庫存時要考慮競態(tài)條件油额,我們這里是使用 Channel 避免并發(fā)的讀寫,保證了請求的高效順序執(zhí)行刻帚。我們將接口的返回信息寫入到了 ./stat.log 文件方便做壓測統(tǒng)計潦嘶。

4.4 單機服務(wù)壓測

開啟服務(wù),我們使用 AB 壓測工具進行測試:

ab -n 10000 -c 100 http://127.0.0.1:3005/buy/ticket

下面是我本地低配 Mac 的壓測信息:

This is ApacheBench, Version 2.3 <$Revision: 1826891 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:
Server Hostname:        127.0.0.1
Server Port:            3005

Document Path:          /buy/ticket
Document Length:        29 bytes

Concurrency Level:      100
Time taken for tests:   2.339 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      1370000 bytes
HTML transferred:       290000 bytes
Requests per second:    4275.96 [#/sec] (mean)
Time per request:       23.387 [ms] (mean)
Time per request:       0.234 [ms] (mean, across all concurrent requests)
Transfer rate:          572.08 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    8  14.7      6     223
Processing:     2   15  17.6     11     232
Waiting:        1   11  13.5      8     225
Total:          7   23  22.8     18     239

Percentage of the requests served within a certain time (ms)
  50%     18
  66%     24
  75%     26
  80%     28
  90%     33
  95%     39
  98%     45
  99%     54
 100%    239 (longest request)

根據(jù)指標顯示崇众,我單機每秒就能處理 4000+ 的請求掂僵,正常服務(wù)器都是多核配置航厚,處理 1W+ 的請求根本沒有問題。

而且查看日志發(fā)現(xiàn)整個服務(wù)過程中锰蓬,請求都很正常幔睬,流量均勻,Redis 也很正常:

//stat.log
...
result:1,localSales:145
result:1,localSales:146
result:1,localSales:147
result:1,localSales:148
result:1,localSales:149
result:1,localSales:150
result:0,localSales:151
result:0,localSales:152
result:0,localSales:153
result:0,localSales:154
result:0,localSales:156
...

5芹扭、總結(jié)回顧

總體來說麻顶,秒殺系統(tǒng)是非常復雜的。我們這里只是簡單介紹模擬了一下單機如何優(yōu)化到高性能舱卡,集群如何避免單點故障澈蚌,保證訂單不超賣、不少賣的一些策略灼狰,完整的訂單系統(tǒng)還有訂單進度的查看宛瞄,每臺服務(wù)器上都有一個任務(wù),定時的從總庫存同步余票和庫存信息展示給用戶交胚,還有用戶在訂單有效期內(nèi)不支付份汗,釋放訂單,補充到庫存等等蝴簇。

我們實現(xiàn)了高并發(fā)搶票的核心邏輯杯活,可以說系統(tǒng)設(shè)計的非常的巧妙,巧妙的避開了對 DB 數(shù)據(jù)庫 IO 的操作熬词。

對 Redis 網(wǎng)絡(luò) IO 的高并發(fā)請求旁钧,幾乎所有的計算都是在內(nèi)存中完成的,而且有效的保證了不超賣互拾、不少賣歪今,還能夠容忍部分機器的宕機。

我覺得其中有兩點特別值得學習總結(jié):

①負載均衡颜矿,分而治之
通過負載均衡寄猩,將不同的流量劃分到不同的機器上,每臺機器處理好自己的請求骑疆,將自己的性能發(fā)揮到極致田篇。

這樣系統(tǒng)的整體也就能承受極高的并發(fā)了,就像工作的一個團隊箍铭,每個人都將自己的價值發(fā)揮到了極致泊柬,團隊成長自然是很大的。

②合理的使用并發(fā)和異步
自 Epoll 網(wǎng)絡(luò)架構(gòu)模型解決了 c10k 問題以來诈火,異步越來越被服務(wù)端開發(fā)人員所接受兽赁,能夠用異步來做的工作,就用異步來做,在功能拆解上能達到意想不到的效果闸氮。

這點在 Nginx剪况、Node.JS、Redis 上都能體現(xiàn)蒲跨,他們處理網(wǎng)絡(luò)請求使用的 Epoll 模型译断,用實踐告訴了我們單線程依然可以發(fā)揮強大的威力。

服務(wù)器已經(jīng)進入了多核時代或悲,Go 語言這種天生為并發(fā)而生的語言孙咪,完美的發(fā)揮了服務(wù)器多核優(yōu)勢,很多可以并發(fā)處理的任務(wù)都可以使用并發(fā)來解決巡语,比如 Go 處理 HTTP 請求時每個請求都會在一個 Goroutine 中執(zhí)行翎蹈。

總之,怎樣合理的壓榨 CPU男公,讓其發(fā)揮出應(yīng)有的價值荤堪,是我們一直需要探索學習的方向。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末枢赔,一起剝皮案震驚了整個濱河市澄阳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌踏拜,老刑警劉巖碎赢,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異速梗,居然都是意外死亡肮塞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門姻锁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枕赵,“玉大人,你說我怎么就攤上這事屋摔∷干瑁” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵钓试,是天一觀的道長疯攒。 經(jīng)常有香客問我斯入,道長,這世上最難降的妖魔是什么场斑? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任糠睡,我火速辦了婚禮挽鞠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己信认,他們只是感情好材义,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嫁赏,像睡著了一般其掂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上潦蝇,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天款熬,我揣著相機與錄音,去河邊找鬼攘乒。 笑死贤牛,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的则酝。 我是一名探鬼主播殉簸,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼沽讹!你這毒婦竟也來了喂链?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤妥泉,失蹤者是張志新(化名)和其女友劉穎椭微,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盲链,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡蝇率,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了刽沾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片本慕。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖侧漓,靈堂內(nèi)的尸體忽然破棺而出锅尘,到底是詐尸還是另有隱情,我是刑警寧澤布蔗,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布藤违,位于F島的核電站,受9級特大地震影響纵揍,放射性物質(zhì)發(fā)生泄漏顿乒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一泽谨、第九天 我趴在偏房一處隱蔽的房頂上張望璧榄。 院中可真熱鬧特漩,春花似錦、人聲如沸骨杂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搓蚪。三九已至蛤售,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陕凹,已是汗流浹背悍抑。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留杜耙,地道東北人搜骡。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像佑女,于是被迫代替她去往敵國和親记靡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容