一 12306深度優(yōu)化整體架構(gòu)
在節(jié)假日和春節(jié)時(shí)候,火車票提前預(yù)售良拼。在預(yù)售的點(diǎn)會(huì)有大量人們搶購車票战得。由于高并發(fā),導(dǎo)致服務(wù)癱瘓庸推。
1.1 解決方案
內(nèi)存計(jì)算余票
異步交易系統(tǒng)(削峰方案)
數(shù)據(jù)庫進(jìn)行高可用搭建(讀寫分離)
1.1.1 削峰解決方案
1. 削峰方案:
對于瞬時(shí)流量我們最先想到的是中間件進(jìn)行削峰常侦,把直接調(diào)用轉(zhuǎn)化為間接異步推送。中間隊(duì)列在一瞬間接受流量鋒贬媒,在另外一端平滑的將消息推送聋亡。
2. 答題:
在下單時(shí)候,我們需要答題际乘。因?yàn)槊總€(gè)人的答題速度不一樣坡倔,錯(cuò)開搶票時(shí)間。
3. 分時(shí)間段發(fā)放票:
將票分多個(gè)時(shí)間段進(jìn)行發(fā)放
1.1.2 數(shù)據(jù)同步方案架構(gòu)
后臺管理員按照日期生成乘車計(jì)劃、座位信息罪塔、車次信息投蝉,并通過logstash將數(shù)據(jù)同步到ES和redis中。
二 用戶下單分析
用戶在下單時(shí)候征堪,用戶經(jīng)歷下單瘩缆、扣庫存、支付请契。在高并發(fā)場景下保證咳榜,車票不多買也不少賣,且支付后車票真實(shí)有效爽锥。
2.1 對下單操作分析(異步下單的優(yōu)勢)
1. 方案一
用戶在下單完涌韩,立馬扣除庫存,等待用戶支付氯夷。且創(chuàng)建訂單和扣除庫存是原子操作臣樱。能保證不超賣問題。
方案問題:
在極度并發(fā)請求下腮考,每次創(chuàng)建訂單對內(nèi)存操作對性能影響很大雇毫。訂單數(shù)據(jù)需要保存到數(shù)據(jù)庫,對數(shù)據(jù)庫壓力很大踩蔚。
如果很多人下了訂單棚放,但是不支付。會(huì)導(dǎo)致很多票沒有賣掉馅闽。
2. 方案二
在極度并發(fā)場景下飘蚯,庫存減為0時(shí)候,很多用戶搶到訂單卻不能支付福也。而且也不能避免數(shù)據(jù)的IO操作局骤。
3. 方案三
用戶選擇乘車計(jì)劃,點(diǎn)擊下單-->進(jìn)入下單服務(wù)集群中暴凑,--》判斷redis庫存是否充足峦甩,否(直接響應(yīng)票已售空)、是(預(yù)扣庫存)-》把下單信息
發(fā)送給mq现喳,es同步庫存信息-》如果用戶支付凯傲,訂單處理服務(wù)進(jìn)行訂單信息入庫,并響應(yīng)成功信息拿穴。如果超時(shí)未支付泣洞,redis進(jìn)行庫存回退,es進(jìn)行庫存回退默色。
2.1 下單操作
2.1.1 nginx進(jìn)行限流配置
為了防止一些搶票助手發(fā)送無用請求球凰,采用nginx進(jìn)行限流操作狮腿。
1. 限制訪問頻率:
limit_req_zone:單位時(shí)間內(nèi)請求數(shù),采用漏斗算法呕诉。
2. 限制并發(fā)連接數(shù):
limit_conn_zone:同一時(shí)間的連接數(shù)缘厢。
2.1.2 下單流程
1. 訂單生成:
預(yù)扣庫存
找對應(yīng)日期對應(yīng)車次對應(yīng)座位類型的乘車計(jì)劃庫存(站票,坐票甩挫,硬臥贴硫、軟臥等),進(jìn)行庫存扣減伊者。
分配座位
遍歷所有指定座位類型車廂的key(集合)英遭。
遍歷集合獲取每個(gè)車廂的座位(集合)。
遍歷集合獲取每一個(gè)座位對應(yīng)狀態(tài)亦渗。
如果座位沒有售出挖诸,標(biāo)記座位并更改座位的狀態(tài)。
生成訂單
在創(chuàng)建訂單時(shí)候法精,我們可以采用一個(gè)線程完成多律。且線程執(zhí)行完畢,我們要獲取線程的執(zhí)行結(jié)果搂蜓,采用Callable執(zhí)行訂單創(chuàng)建狼荞。
在線程中創(chuàng)建訂單對象。
將指定日期的乘車計(jì)劃封裝到一個(gè)對象中帮碰。
生成訂單相味。
Redis排隊(duì)
采用 Redis 中的 ZSet 集合存儲(chǔ)排隊(duì)信息,使用:列車編號 + 乘車日期 + 用戶 id 作為 key殉挽,使用當(dāng)前系統(tǒng)時(shí)間的納秒值作為 value
2. 同步ES庫存:
下單服務(wù)發(fā)送同步信息(乘車日期攻走、座位性質(zhì)、列車車次)到mq中此再。
es同步服務(wù)監(jiān)聽mq,獲取發(fā)送信息。根據(jù)搜索條件查詢到數(shù)據(jù)玲销,并進(jìn)行庫存扣減输拇。
3. 發(fā)送訂單數(shù)據(jù):
創(chuàng)建訂單和用戶信息,并設(shè)置交換機(jī)和隊(duì)列贤斜。
發(fā)送訂單信息策吠,發(fā)送完訂單數(shù)據(jù),跳轉(zhuǎn)到下單成功界面瘩绒。下單成功界面調(diào)用排隊(duì)接口猴抹,顯示排隊(duì)信息。
2.1.3 訂單處理流程
1. 環(huán)境準(zhǔn)備:
因?yàn)橄聠嗡螅瑢懙牟僮鬟h(yuǎn)大于讀的操作蟀给,因此mysql采用雙主雙從搭建模式。
2. 保存訂單:
下單服務(wù)監(jiān)聽mq,如果有訂單生成跋理,對訂單信息進(jìn)行入庫择克。
3. 刪除排隊(duì)信息:
訂單保存成功后,刪除排隊(duì)信息前普。
4. 將下單結(jié)果通過websocket發(fā)送給客戶端:
保存完訂單數(shù)據(jù)以后肚邢,調(diào)用 WebSocketServer ,完成消息推送拭卿。
2.2 下單優(yōu)化
用戶已經(jīng)下單了骡湖,再次提交訂單是否扣除訂單?
用戶雖然下單了但是一直沒有支付峻厚。
預(yù)扣減庫存是否存在線程安全問題响蕴。
2.2.1 預(yù)扣庫存優(yōu)化
當(dāng)該用戶已經(jīng)購買了指定列車的火車票,那么我們就不能再進(jìn)行預(yù)扣庫.
根據(jù)用戶信息和乘車計(jì)劃id查詢訂單信息目木,查詢到信息换途,不進(jìn)行庫存扣減。
2.2.2 庫存回退
1. 方案一(延遲隊(duì)列):
延遲隊(duì)列:消息發(fā)送后刽射,特定時(shí)間后消費(fèi)者才能拿到消息進(jìn)行消費(fèi)军拟。
2. 方案二(死信隊(duì)列):
死信隊(duì)列:一個(gè)消息在隊(duì)列中變成死信隊(duì)列之后,消息會(huì)被從新發(fā)送到另外一個(gè)交換機(jī)中誓禁,這個(gè)交換機(jī)就是死信隊(duì)列懈息。一個(gè)消息成為死信隊(duì)列情況:
消息被拒絕,并且設(shè)置 requeue 參數(shù)為 false
消息過期
隊(duì)列達(dá)到最大長度
2.2.3 庫存安全性判斷(分布式鎖)
在進(jìn)行庫存扣減時(shí)候摹恰,加鎖辫继。因?yàn)榭鄢龓齑媸嵌喾?wù),因此需要用分布式鎖來解決(mysql實(shí)現(xiàn)俗慈,zookeeper實(shí)現(xiàn)姑宽,redis實(shí)現(xiàn))。