最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

程序猿哥哥告訴我12306有多厲害。

2021-03-26 09:31 作者:花易冷_涼涼  | 我要投稿





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


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


雖然現(xiàn)在大多數(shù)情況下都能訂到票,但是放票瞬間即無(wú)票的場(chǎng)景,相信大家都深有體會(huì)。


尤其是春節(jié)期間,大家不僅使用 12306,還會(huì)考慮“智行”和其他的搶票軟件,全國(guó)上下幾億人在這段時(shí)間都在搶票。


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


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

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


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


下邊是一個(gè)簡(jiǎn)單的示意圖:

負(fù)載均衡簡(jiǎn)介


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


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


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


OSPF 計(jì)算的 Cost,同樣是和接口帶寬成反比,帶寬越高,Cost 值越小。到達(dá)目標(biāo)相同 Cost 值的路徑,可以執(zhí)行負(fù)載均衡,最多 6 條鏈路同時(shí)執(zhí)行負(fù)載均衡。


②LVS (Linux Virtual Server


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


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


③Nginx


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


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

  • 輪詢

  • 加權(quán)輪詢

  • IP Hash 輪詢


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


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


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

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

#配置負(fù)載均衡
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 的虛擬域名地址。


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

package main

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

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

//處理請(qǐng)求函數(shù),根據(jù)請(qǐng)求將響應(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, "\r\n"}, "3001")
buf := []byte(content)
fd.Write(buf)
}


我將請(qǐng)求的端口日志信息寫到了 ./stat.log 文件當(dāng)中,然后使用 AB 壓測(cè)工具做壓測(cè):

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


統(tǒng)計(jì)日志中的結(jié)果,3001-3004 端口分別得到了 100、200、300、400 的請(qǐng)求量。
這和我在 Nginx 中配置的權(quán)重占比很好的吻合在了一起,并且負(fù)載后的流量非常的均勻、隨機(jī)。

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

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

秒殺搶購(gòu)系統(tǒng)選型


回到我們最初提到的問題中來(lái):火車票秒殺系統(tǒng)如何在高并發(fā)情況下提供正常、穩(wěn)定的服務(wù)呢?
從上面的介紹我們知道用戶秒殺流量通過(guò)層層的負(fù)載均衡,均勻到了不同的服務(wù)器上,即使如此,集群中的單機(jī)所承受的 QPS 也是非常高的。如何將單機(jī)性能優(yōu)化到極致呢?
要解決這個(gè)問題,我們就要想明白一件事:通常訂票系統(tǒng)要處理生成訂單、減扣庫(kù)存、用戶支付這三個(gè)基本的階段。


我們系統(tǒng)要做的事情是要保證火車票訂單不超賣、不少賣,每張售賣的車票都必須支付才有效,還要保證系統(tǒng)承受極高的并發(fā)。


這三個(gè)階段的先后順序該怎么分配才更加合理呢?我們來(lái)分析一下:

下單減庫(kù)存


當(dāng)用戶并發(fā)請(qǐng)求到達(dá)服務(wù)端時(shí),首先創(chuàng)建訂單,然后扣除庫(kù)存,等待用戶支付。
這種順序是我們一般人首先會(huì)想到的解決方案,這種情況下也能保證訂單不會(huì)超賣,因?yàn)閯?chuàng)建訂單之后就會(huì)減庫(kù)存,這是一個(gè)原子操作。
但是這樣也會(huì)產(chǎn)生一些問題:
  • 在極限并發(fā)情況下,任何一個(gè)內(nèi)存操作的細(xì)節(jié)都至關(guān)影響性能,尤其像創(chuàng)建訂單這種邏輯,一般都需要存儲(chǔ)到磁盤數(shù)據(jù)庫(kù)的,對(duì)數(shù)據(jù)庫(kù)的壓力是可想而知的。

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




支付減庫(kù)存


如果等待用戶支付了訂單在減庫(kù)存,第一感覺就是不會(huì)少賣。但是這是并發(fā)架構(gòu)的大忌,因?yàn)樵跇O限并發(fā)情況下,用戶可能會(huì)創(chuàng)建很多訂單。


當(dāng)庫(kù)存減為零的時(shí)候很多用戶發(fā)現(xiàn)搶到的訂單支付不了了,這也就是所謂的“超賣”。也不能避免并發(fā)操作數(shù)據(jù)庫(kù)磁盤 IO。

預(yù)扣庫(kù)存


從上邊兩種方案的考慮,我們可以得出結(jié)論:只要?jiǎng)?chuàng)建訂單,就要頻繁操作數(shù)據(jù)庫(kù) IO。
那么有沒有一種不需要直接操作數(shù)據(jù)庫(kù) IO 的方案呢,這就是預(yù)扣庫(kù)存。先扣除了庫(kù)存,保證不超賣,然后異步生成用戶訂單,這樣響應(yīng)給用戶的速度就會(huì)快很多;那么怎么保證不少賣呢?用戶拿到了訂單,不支付怎么辦?
我們都知道現(xiàn)在訂單都有有效期,比如說(shuō)用戶五分鐘內(nèi)不支付,訂單就失效了,訂單一旦失效,就會(huì)加入新的庫(kù)存,這也是現(xiàn)在很多網(wǎng)上零售企業(yè)保證商品不少賣采用的方案。
訂單的生成是異步的,一般都會(huì)放到 MQ、Kafka 這樣的即時(shí)消費(fèi)隊(duì)列中處理,訂單量比較少的情況下,生成訂單非???,用戶幾乎不用排隊(duì)。

扣庫(kù)存的藝術(shù)


從上面的分析可知,顯然預(yù)扣庫(kù)存的方案最合理。我們進(jìn)一步分析扣庫(kù)存的細(xì)節(jié),這里還有很大的優(yōu)化空間,庫(kù)存存在哪里?怎樣保證高并發(fā)下,正確的扣庫(kù)存,還能快速的響應(yīng)用戶請(qǐng)求?

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


為了保證扣庫(kù)存和生成訂單的原子性,需要采用事務(wù)處理,然后取庫(kù)存判斷、減庫(kù)存,最后提交事務(wù),整個(gè)流程有很多 IO,對(duì)數(shù)據(jù)庫(kù)的操作又是阻塞的。


這種方式根本不適合高并發(fā)的秒殺系統(tǒng)。接下來(lái)我們對(duì)單機(jī)扣庫(kù)存的方案做優(yōu)化:本地扣庫(kù)存。


我們把一定的庫(kù)存量分配到本地機(jī)器,直接在內(nèi)存中減庫(kù)存,然后按照之前的邏輯異步創(chuàng)建訂單。


改進(jìn)過(guò)之后的單機(jī)系統(tǒng)是這樣的:

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

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

問題接踵而至,在高并發(fā)情況下,現(xiàn)在我們還無(wú)法保證系統(tǒng)的高可用,假如這 100 臺(tái)服務(wù)器上有兩三臺(tái)機(jī)器因?yàn)榭覆蛔〔l(fā)的流量或者其他的原因宕機(jī)了。那么這些服務(wù)器上的訂單就賣不出去了,這就造成了訂單的少賣。
要解決這個(gè)問題,我們需要對(duì)總訂單量做統(tǒng)一的管理,這就是接下來(lái)的容錯(cuò)方案。服務(wù)器不僅要在本地減庫(kù)存,另外要遠(yuǎn)程統(tǒng)一減庫(kù)存。
有了遠(yuǎn)程統(tǒng)一減庫(kù)存的操作,我們就可以根據(jù)機(jī)器負(fù)載情況,為每臺(tái)機(jī)器分配一些多余的“Buffer 庫(kù)存”用來(lái)防止機(jī)器中有機(jī)器宕機(jī)的情況。

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

我們采用 Redis 存儲(chǔ)統(tǒng)一庫(kù)存,因?yàn)?Redis 的性能非常高,號(hào)稱單機(jī) QPS 能抗 10W 的并發(fā)。
在本地減庫(kù)存以后,如果本地有訂單,我們?cè)偃フ?qǐng)求 Redis 遠(yuǎn)程減庫(kù)存,本地減庫(kù)存和遠(yuǎn)程減庫(kù)存都成功了,才返回給用戶搶票成功的提示,這樣也能有效的保證訂單不會(huì)超賣。
當(dāng)機(jī)器中有機(jī)器宕機(jī)時(shí),因?yàn)槊總€(gè)機(jī)器上有預(yù)留的 Buffer 余票,所以宕機(jī)機(jī)器上的余票依然能夠在其他機(jī)器上得到彌補(bǔ),保證了不少賣。
Buffer 余票設(shè)置多少合適呢,理論上 Buffer 設(shè)置的越多,系統(tǒng)容忍宕機(jī)的機(jī)器數(shù)量就越多,但是 Buffer 設(shè)置的太大也會(huì)對(duì) Redis 造成一定的影響。
雖然 Redis 內(nèi)存數(shù)據(jù)庫(kù)抗并發(fā)能力非常高,請(qǐng)求依然會(huì)走一次網(wǎng)絡(luò) IO,其實(shí)搶票過(guò)程中對(duì) Redis 的請(qǐng)求次數(shù)是本地庫(kù)存和 Buffer 庫(kù)存的總量。


因?yàn)楫?dāng)本地庫(kù)存不足時(shí),系統(tǒng)直接返回用戶“已售罄”的信息提示,就不會(huì)再走統(tǒng)一扣庫(kù)存的邏輯。


這在一定程度上也避免了巨大的網(wǎng)絡(luò)請(qǐng)求量把 Redis 壓跨,所以 Buffer 值設(shè)置多少,需要架構(gòu)師對(duì)系統(tǒng)的負(fù)載能力做認(rèn)真的考量。


代碼演示


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

初始化工作


Go 包中的 Init 函數(shù)先于 Main 函數(shù)執(zhí)行,在這個(gè)階段主要做一些準(zhǔn)備性工作。
我們系統(tǒng)需要做的準(zhǔn)備工作有:初始化本地庫(kù)存、初始化遠(yuǎn)程 Redis 存儲(chǔ)統(tǒng)一庫(kù)存的 Hash 鍵值、初始化 Redis 連接池。


另外還需要初始化一個(gè)大小為 1 的 Int 類型 Chan,目的是實(shí)現(xiàn)分布式鎖的功能。


也可以直接使用讀寫鎖或者使用 Redis 等其他的方式避免資源競(jìng)爭(zhēng),但使用 Channel 更加高效,這就是 Go 語(yǔ)言的哲學(xué):不要通過(guò)共享內(nèi)存來(lái)通信,而要通過(guò)通信來(lái)共享內(nèi)存。

Redis 庫(kù)使用的是 Redigo,下面是代碼實(shí)現(xiàn):

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

type LocalSpike struct {
LocalInStock int64
LocalSalesVolume int64
}
...
//remoteSpike對(duì)hash結(jié)構(gòu)的定義和redis連接池
package remoteSpike
//遠(yuǎn)程訂單存儲(chǔ)健值
type RemoteSpikeKeys struct {
SpikeOrderHashKey string //redis中秒殺訂單hash結(jié)構(gòu)key
TotalInventoryKey string //hash結(jié)構(gòu)中總訂單庫(kù)存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
}


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


本地扣庫(kù)存邏輯非常簡(jiǎn)單,用戶請(qǐng)求過(guò)來(lái),添加銷量,然后對(duì)比銷量是否大于本地庫(kù)存,返回 Bool 值:

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


注意這里對(duì)共享數(shù)據(jù) LocalSalesVolume 的操作是要使用鎖來(lái)實(shí)現(xiàn)的,但是因?yàn)楸镜乜蹘?kù)存和統(tǒng)一扣庫(kù)存是一個(gè)原子性操作,所以在最上層使用 Channel 來(lái)實(shí)現(xiàn),這塊后邊會(huì)講。

統(tǒng)一扣庫(kù)存操作 Redis,因?yàn)?Redis 是單線程的,而我們要實(shí)現(xiàn)從中取數(shù)據(jù),寫數(shù)據(jù)并計(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
`
//遠(yuǎn)端統(tǒng)一扣庫(kù)存
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)存儲(chǔ)總庫(kù)存和總銷量的信息,用戶請(qǐng)求過(guò)來(lái)時(shí),判斷總銷量是否大于庫(kù)存,然后返回相關(guān)的 Bool 值。


在啟動(dòng)服務(wù)之前,我們需要初始化 Redis 的初始庫(kù)存信息:hmset ticket_hash_key "ticket_total_nums" 10000 "ticket_sold_nums" 0


響應(yīng)用戶信息


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

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


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

package main
//處理請(qǐng)求函數(shù),根據(jù)請(qǐng)求將響應(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, "\r\n"}, "")
buf := []byte(content)
fd.Write(buf)
}


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

單機(jī)服務(wù)壓測(cè)


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

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


下面是我本地低配 Mac 的壓測(cè)信息:

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ù)指標(biāo)顯示,我單機(jī)每秒就能處理 4000+ 的請(qǐng)求,正常服務(wù)器都是多核配置,處理 1W+ 的請(qǐng)求根本沒有問題。

而且查看日志發(fā)現(xiàn)整個(gè)服務(wù)過(guò)程中,請(qǐng)求都很正常,流量均勻,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
...


總結(jié)回顧


總體來(lái)說(shuō),秒殺系統(tǒng)是非常復(fù)雜的。我們這里只是簡(jiǎn)單介紹模擬了一下單機(jī)如何優(yōu)化到高性能,集群如何避免單點(diǎn)故障,保證訂單不超賣、不少賣的一些策略


完整的訂單系統(tǒng)還有訂單進(jìn)度的查看,每臺(tái)服務(wù)器上都有一個(gè)任務(wù),定時(shí)的從總庫(kù)存同步余票和庫(kù)存信息展示給用戶,還有用戶在訂單有效期內(nèi)不支付,釋放訂單,補(bǔ)充到庫(kù)存等等。


我們實(shí)現(xiàn)了高并發(fā)搶票的核心邏輯,可以說(shuō)系統(tǒng)設(shè)計(jì)的非常的巧妙,巧妙的避開了對(duì) DB 數(shù)據(jù)庫(kù) IO 的操作。
對(duì) Redis 網(wǎng)絡(luò) IO 的高并發(fā)請(qǐng)求,幾乎所有的計(jì)算都是在內(nèi)存中完成的,而且有效的保證了不超賣、不少賣,還能夠容忍部分機(jī)器的宕機(jī)。
我覺得其中有兩點(diǎn)特別值得學(xué)習(xí)總結(jié):
①負(fù)載均衡,分而治之


通過(guò)負(fù)載均衡,將不同的流量劃分到不同的機(jī)器上,每臺(tái)機(jī)器處理好自己的請(qǐng)求,將自己的性能發(fā)揮到極致。


這樣系統(tǒng)的整體也就能承受極高的并發(fā)了,就像工作的一個(gè)團(tuán)隊(duì),每個(gè)人都將自己的價(jià)值發(fā)揮到了極致,團(tuán)隊(duì)成長(zhǎng)自然是很大的。


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


這點(diǎn)在 Nginx、Node.JS、Redis 上都能體現(xiàn),他們處理網(wǎng)絡(luò)請(qǐng)求使用的 Epoll 模型,用實(shí)踐告訴了我們單線程依然可以發(fā)揮強(qiáng)大的威力。
服務(wù)器已經(jīng)進(jìn)入了多核時(shí)代,Go 語(yǔ)言這種天生為并發(fā)而生的語(yǔ)言,完美的發(fā)揮了服務(wù)器多核優(yōu)勢(shì),很多可以并發(fā)處理的任務(wù)都可以使用并發(fā)來(lái)解決,比如 Go 處理 HTTP 請(qǐng)求時(shí)每個(gè)請(qǐng)求都會(huì)在一個(gè) Goroutine 中執(zhí)行。


總之,怎樣合理的壓榨 CPU,讓其發(fā)揮出應(yīng)有的價(jià)值,是我們一直需要探索學(xué)習(xí)的方向。
-END-

然鵝:哥哥說(shuō)這沒多我還是不明白。為什么我還是搶不到票 ( i_i) !!!

程序猿哥哥告訴我12306有多厲害。的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
舞阳县| 乌什县| 余庆县| 武城县| 伊金霍洛旗| 通榆县| 攀枝花市| 长丰县| 长宁区| 湘乡市| 镇雄县| 安庆市| 旬邑县| 综艺| 凤阳县| 仁布县| 墨脱县| 互助| 饶平县| 阳信县| 永昌县| 河南省| 安乡县| 长海县| 河曲县| 江华| 远安县| 定安县| 新宁县| 桃园市| 长兴县| 萨嘎县| 哈尔滨市| 凭祥市| 阜康市| 新源县| 双桥区| 喜德县| 新乡市| 黄石市| 松江区|