<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          “12306”的架構(gòu)到底有多牛逼?

          共 6250字,需瀏覽 13分鐘

           ·

          2020-12-23 02:37

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



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


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


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


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


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


          Github代碼地址:
          https://github.com/GuoZhaoran/spikeSystem

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


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


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

          負載均衡簡介


          上圖中描述了用戶請求到服務器經(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)移到不同的服務器上執(zhí)行,且調(diào)度器自動屏蔽掉服務器的故障,從而將一組服務器構(gòu)成一個高性能的、高可用的虛擬服務器。


          ③Nginx


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


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

          • 輪詢

          • 加權(quán)輪詢

          • IP?Hash?輪詢


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


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


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

          下面是一個加權(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)聽服務,下面是監(jiān)聽在 3001 端口的 Go 程序,其他幾個只需要修改端口即可:

          package?main

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

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

          //處理請求函數(shù),根據(jù)請求將響應結(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)
          }


          我將請求的端口日志信息寫到了 ./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

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


          回到我們最初提到的問題中來:火車票秒殺系統(tǒng)如何在高并發(fā)情況下提供正常、穩(wěn)定的服務呢?

          從上面的介紹我們知道用戶秒殺流量通過層層的負載均衡,均勻到了不同的服務器上,即使如此,集群中的單機所承受的 QPS 也是非常高的。如何將單機性能優(yōu)化到極致呢?

          要解決這個問題,我們就要想明白一件事:通常訂票系統(tǒng)要處理生成訂單、減扣庫存、用戶支付這三個基本的階段。


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


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

          下單減庫存


          當用戶并發(fā)請求到達服務端時,首先創(chuàng)建訂單,然后扣除庫存,等待用戶支付。

          這種順序是我們一般人首先會想到的解決方案,這種情況下也能保證訂單不會超賣,因為創(chuàng)建訂單之后就會減庫存,這是一個原子操作。

          但是這樣也會產(chǎn)生一些問題:
          • 在極限并發(fā)情況下,任何一個內(nèi)存操作的細節(jié)都至關(guān)影響性能,尤其像創(chuàng)建訂單這種邏輯,一般都需要存儲到磁盤數(shù)據(jù)庫的,對數(shù)據(jù)庫的壓力是可想而知的。


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




          支付減庫存


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


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

          預扣庫存


          從上邊兩種方案的考慮,我們可以得出結(jié)論:只要創(chuàng)建訂單,就要頻繁操作數(shù)據(jù)庫 IO。

          那么有沒有一種不需要直接操作數(shù)據(jù)庫 IO 的方案呢,這就是預扣庫存。先扣除了庫存,保證不超賣,然后異步生成用戶訂單,這樣響應給用戶的速度就會快很多;那么怎么保證不少賣呢?用戶拿到了訂單,不支付怎么辦?

          我們都知道現(xiàn)在訂單都有有效期,比如說用戶五分鐘內(nèi)不支付,訂單就失效了,訂單一旦失效,就會加入新的庫存,這也是現(xiàn)在很多網(wǎng)上零售企業(yè)保證商品不少賣采用的方案。

          訂單的生成是異步的,一般都會放到 MQ、Kafka 這樣的即時消費隊列中處理,訂單量比較少的情況下,生成訂單非常快,用戶幾乎不用排隊。

          扣庫存的藝術(shù)


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

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

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


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


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


          改進過之后的單機系統(tǒng)是這樣的:

          這樣就避免了對數(shù)據(jù)庫頻繁的 IO 操作,只在內(nèi)存中做運算,極大的提高了單機抗并發(fā)的能力。

          但是百萬的用戶請求量單機是無論如何也抗不住的,雖然 Nginx 處理網(wǎng)絡(luò)請求使用 Epoll 模型,c10k 的問題在業(yè)界早已得到了解決。

          但是 Linux 系統(tǒng)下,一切資源皆文件,網(wǎng)絡(luò)請求也是這樣,大量的文件描述符會使操作系統(tǒng)瞬間失去響應。

          上面我們提到了 Nginx 的加權(quán)均衡策略,我們不妨假設(shè)將 100W 的用戶請求量平均均衡到 100 臺服務器上,這樣單機所承受的并發(fā)量就小了很多。

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

          問題接踵而至,在高并發(fā)情況下,現(xiàn)在我們還無法保證系統(tǒng)的高可用,假如這 100?臺服務器上有兩三臺機器因為扛不住并發(fā)的流量或者其他的原因宕機了。那么這些服務器上的訂單就賣不出去了,這就造成了訂單的少賣。

          要解決這個問題,我們需要對總訂單量做統(tǒng)一的管理,這就是接下來的容錯方案。服務器不僅要在本地減庫存,另外要遠程統(tǒng)一減庫存。

          有了遠程統(tǒng)一減庫存的操作,我們就可以根據(jù)機器負載情況,為每臺機器分配一些多余的“Buffer 庫存”用來防止機器中有機器宕機的情況。

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

          我們采用 Redis 存儲統(tǒng)一庫存,因為 Redis 的性能非常高,號稱單機 QPS 能抗 10W 的并發(fā)。

          在本地減庫存以后,如果本地有訂單,我們再去請求 Redis 遠程減庫存,本地減庫存和遠程減庫存都成功了,才返回給用戶搶票成功的提示,這樣也能有效的保證訂單不會超賣。

          當機器中有機器宕機時,因為每個機器上有預留的 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)的負載能力做認真的考量。


          代碼演示


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

          初始化工作


          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
          }


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


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

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


          注意這里對共享數(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 值。


          在啟動服務之前,我們需要初始化 Redis 的初始庫存信息:
          hmset?ticket_hash_key?"ticket_total_nums"?10000?"ticket_sold_nums"?0


          響應用戶信息


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

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


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

          package?main
          //處理請求函數(shù),根據(jù)請求將響應結(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)
          }


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

          單機服務壓測


          開啟服務,我們使用 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+ 的請求,正常服務器都是多核配置,處理 1W+ 的請求根本沒有問題。

          而且查看日志發(fā)現(xiàn)整個服務過程中,請求都很正常,流量均勻,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é)回顧


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


          完整的訂單系統(tǒng)還有訂單進度的查看,每臺服務器上都有一個任務,定時的從總庫存同步余票和庫存信息展示給用戶,還有用戶在訂單有效期內(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 問題以來,異步越來越被服務端開發(fā)人員所接受,能夠用異步來做的工作,就用異步來做,在功能拆解上能達到意想不到的效果。


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

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


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



          源:juejin.im/post/5d84e21f6fb9a06ac8248149

          版權(quán)申明:內(nèi)容來源網(wǎng)絡(luò),版權(quán)歸原創(chuàng)者所有。除非無法確認,我們都會標明作者及出處,如有侵權(quán)煩請告知,我們會立即刪除并表示歉意。謝謝!





          感謝閱讀



          瀏覽 21
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美操逼视频大全 | 做爰 视频毛片下载蜜桃视频 | 日本精品 码喷水在线看 | 亚洲视频免费在线收看 | 日韩成人精品中文字幕 |