系統(tǒng)要求
高性能
秒殺涉及大量的并發(fā)讀和并發(fā)寫,因此要求性能必須高;
一致性
秒殺中商品減庫(kù)存的實(shí)現(xiàn)方式同樣關(guān)鍵。可想而知,有限數(shù)量的商品同一時(shí)刻被很多倍的請(qǐng)求同時(shí)來(lái)減庫(kù)存。在大并發(fā)更新數(shù)據(jù)的過(guò)程中保證數(shù)據(jù)的準(zhǔn)確性,難度可想而知;
高可用
出現(xiàn)問(wèn)題的時(shí)候要保證可用;
架構(gòu)原則
1. 數(shù)據(jù)要盡量少
所謂“數(shù)據(jù)要盡量少”,就是指用戶請(qǐng)求的數(shù)據(jù)能少則少。請(qǐng)求的數(shù)據(jù)包括上傳給系統(tǒng)的數(shù)據(jù)和系統(tǒng)返回給用戶的數(shù)據(jù)(通常就是網(wǎng)頁(yè))。
因?yàn)檫@些數(shù)據(jù)在網(wǎng)絡(luò)上傳輸需要時(shí)間,其次不管是請(qǐng)求數(shù)據(jù)還是返回?cái)?shù)據(jù)都需要服務(wù)器來(lái)處理,而服務(wù)器在寫網(wǎng)絡(luò)時(shí)都要做壓縮和編碼,這些都非常消耗CPU,所以減少傳輸?shù)臄?shù)據(jù)量可以顯著的減少CPU的使用。例如,我們可以簡(jiǎn)化秒殺頁(yè)面的大小,去掉不必要的頁(yè)面裝修效果等。
其次,“數(shù)據(jù)要盡量少”還要求系統(tǒng)依賴的數(shù)據(jù)能少就少,包括系統(tǒng)完成某些業(yè)務(wù)邏輯需要讀取和保存的數(shù)據(jù),這些數(shù)據(jù)一般是和后臺(tái)服務(wù)和數(shù)據(jù)庫(kù)打交道的。調(diào)用其他服務(wù)會(huì)涉及到數(shù)據(jù)的序列化和反序列化,這也是CPU的一大殺手,同樣也會(huì)增加延時(shí)。而且,數(shù)據(jù)庫(kù)本身也容易成為一個(gè)瓶頸,所以和數(shù)據(jù)庫(kù)打交道越少越好,越簡(jiǎn)單越好。
2. 請(qǐng)求數(shù)要盡量少
用戶請(qǐng)求的頁(yè)面返回后,瀏覽器渲染頁(yè)面還包含其他的額外請(qǐng)求,比如說(shuō)這個(gè)頁(yè)面依賴的CSS/Javascript,圖片以及Ajax請(qǐng)求等,這些額外請(qǐng)求應(yīng)該盡量少。因?yàn)闉g覽器每發(fā)出一個(gè)請(qǐng)求都多少會(huì)有一些消耗,例如建立連接要做三次握手,另外不同請(qǐng)求訪問(wèn)的域名不一樣,還需要做DNS域名解析,可能會(huì)耗時(shí)更久。
3. 路徑要盡量短
所謂“路徑”,就是用戶發(fā)出請(qǐng)求到返回?cái)?shù)據(jù)這個(gè)過(guò)程中,需求經(jīng)過(guò)的中間的節(jié)點(diǎn)數(shù)。
4. 依賴要盡量少
所謂依賴,指的是要完成一次用戶請(qǐng)求必須依賴的系統(tǒng)或者服務(wù),這里的依賴指的是強(qiáng)依賴。
5. 不要有單點(diǎn)
系統(tǒng)中的單點(diǎn)可以說(shuō)是系統(tǒng)架構(gòu)上的一個(gè)大忌,因?yàn)閱吸c(diǎn)意味著沒有備份,風(fēng)險(xiǎn)不可控,我們?cè)O(shè)計(jì)分布式系統(tǒng)最重要的原則就是“消除單點(diǎn)”。
做好動(dòng)靜分離
何為動(dòng)靜數(shù)據(jù)
簡(jiǎn)單來(lái)說(shuō),“靜態(tài)數(shù)據(jù)”和“動(dòng)態(tài)數(shù)據(jù)”的主要區(qū)別就是看頁(yè)面中輸出的數(shù)據(jù)是否和URL、瀏覽器、時(shí)間、地域相關(guān),以及是否含有Cookie等私密信息。
如何做動(dòng)靜分離的改造
下面我們從5個(gè)方面來(lái)分離出動(dòng)態(tài)內(nèi)容:
1. URL唯一化。商品詳情系統(tǒng)天然地就可以做到 URL 唯一化,比如每個(gè)商品都由 ID 來(lái)標(biāo)識(shí),那么?
http://item.xxx.com/item.htm?id=xxxx 就可以作為唯一的 URL 標(biāo)識(shí)。為啥要 URL 唯一呢?前面說(shuō)了我們是要緩存整個(gè) HTTP 連接,那么以什么作為 Key 呢?就以 URL 作為緩存的 Key,例如以 id=xxx 這個(gè)格式進(jìn)行區(qū)分。
2. 分離瀏覽者相關(guān)的因素。瀏覽者相關(guān)的因素包括是否已登錄,以及登錄身份等,這些因素可以單獨(dú)拆分出來(lái),通過(guò)動(dòng)態(tài)請(qǐng)求來(lái)獲取。
3. 分離時(shí)間因素。服務(wù)器輸出的時(shí)間也要?jiǎng)討B(tài)請(qǐng)求來(lái)獲取。
4. 異步化地域因素。詳情頁(yè)上與地域相關(guān)的因素做成異步獲取。
5. 服務(wù)端輸出的頁(yè)面包含的 Cookie 可以通過(guò)代碼軟件來(lái)刪除,如 Web 服務(wù)器 Varnish 可以通過(guò) unset req.http.cookie 命令去掉 Cookie。注意,這里說(shuō)的去掉 Cookie 并不是用戶端收到的頁(yè)面就不含 Cookie 了,而是說(shuō),在緩存的靜態(tài)數(shù)據(jù)中不含有 Cookie。
靜態(tài)數(shù)據(jù)如何緩存
第一,你應(yīng)該把靜態(tài)數(shù)據(jù)緩存到離用戶最近的地方。靜態(tài)數(shù)據(jù)就是那些相對(duì)不會(huì)變化的數(shù)據(jù),因此我們可以把它們緩存起來(lái)。緩存到哪里呢?常見的就三種,用戶瀏覽器里、CDN 上或者在服務(wù)端的 Cache 中。你應(yīng)該根據(jù)情況,把它們盡量緩存到離用戶最近的地方。
第二,靜態(tài)化改造就是要直接緩存 HTTP 連接。相較于普通的數(shù)據(jù)緩存而言,你肯定還聽過(guò)系統(tǒng)的靜態(tài)化改造。靜態(tài)化改造是直接緩存 HTTP 連接而不是僅僅緩存數(shù)據(jù),如下圖所示,Web 代理服務(wù)器根據(jù)請(qǐng)求 URL,直接取出對(duì)應(yīng)的 HTTP 響應(yīng)頭和響應(yīng)體然后直接返回,這個(gè)響應(yīng)過(guò)程簡(jiǎn)單得連 HTTP 協(xié)議都不用重新組裝,甚至連 HTTP 請(qǐng)求頭也不需要解析。
動(dòng)態(tài)數(shù)據(jù)如何處理
1.?ESI 方案(或者 SSI):即在 Web 代理服務(wù)器上做動(dòng)態(tài)內(nèi)容請(qǐng)求,并將請(qǐng)求插入到靜態(tài)頁(yè)面中,當(dāng)用戶拿到頁(yè)面時(shí)已經(jīng)是一個(gè)完整的頁(yè)面了。這種方式對(duì)服務(wù)端性能有些影響,但是用戶體驗(yàn)較好。
2.?CSI 方案。即單獨(dú)發(fā)起一個(gè)異步 JavaScript 請(qǐng)求,以向服務(wù)端獲取動(dòng)態(tài)內(nèi)容。這種方式服務(wù)端性能更佳,但是用戶端頁(yè)面可能會(huì)延時(shí),體驗(yàn)稍差。
動(dòng)靜分離的幾種架構(gòu)方案
1. 實(shí)體機(jī)單機(jī)部署
2. 統(tǒng)一Cache層
3. 上CDN
如何處理熱點(diǎn)數(shù)據(jù)
處理熱點(diǎn)數(shù)據(jù)的通常幾個(gè)思路:一是優(yōu)化,二是限制,三是隔離。
先來(lái)說(shuō)說(shuō)優(yōu)化。優(yōu)化熱點(diǎn)數(shù)據(jù)最有效的辦法就是緩存熱點(diǎn)數(shù)據(jù),如果熱點(diǎn)數(shù)據(jù)做了動(dòng)靜分離,那么可以長(zhǎng)期緩存靜態(tài)數(shù)據(jù)。但是,緩存熱點(diǎn)數(shù)據(jù)更多的是“臨時(shí)”緩存,即不管是靜態(tài)數(shù)據(jù)還是動(dòng)態(tài)數(shù)據(jù),都用一個(gè)隊(duì)列短暫地緩存數(shù)秒鐘,由于隊(duì)列長(zhǎng)度有限,可以采用 LRU 淘汰算法替換。
再來(lái)說(shuō)說(shuō)限制。限制更多的是一種保護(hù)機(jī)制,限制的辦法也有很多,例如對(duì)被訪問(wèn)商品的 ID 做一致性 Hash,然后根據(jù) Hash 做分桶,每個(gè)分桶設(shè)置一個(gè)處理隊(duì)列,這樣可以把熱點(diǎn)商品限制在一個(gè)請(qǐng)求隊(duì)列里,防止因某些熱點(diǎn)商品占用太多的服務(wù)器資源,而使其他請(qǐng)求始終得不到服務(wù)器的處理資源。
最后介紹一下隔離。秒殺系統(tǒng)設(shè)計(jì)的第一個(gè)原則就是將這種熱點(diǎn)數(shù)據(jù)隔離出來(lái),不要讓 1% 的請(qǐng)求影響到另外的 99%,隔離出來(lái)后也更方便對(duì)這 1% 的請(qǐng)求做針對(duì)性的優(yōu)化。
具體到“秒殺”業(yè)務(wù),我們可以在以下幾個(gè)層次實(shí)現(xiàn)隔離。
業(yè)務(wù)隔離。把秒殺做成一種營(yíng)銷活動(dòng),賣家要參加秒殺這種營(yíng)銷活動(dòng)需要單獨(dú)報(bào)名,從技術(shù)上來(lái)說(shuō),賣家報(bào)名后對(duì)我們來(lái)說(shuō)就有了已知熱點(diǎn),因此可以提前做好預(yù)熱。
系統(tǒng)隔離。系統(tǒng)隔離更多的是運(yùn)行時(shí)的隔離,可以通過(guò)分組部署的方式和另外 99% 分開。秒殺可以申請(qǐng)單獨(dú)的域名,目的也是讓請(qǐng)求落到不同的集群中。
數(shù)據(jù)隔離。秒殺所調(diào)用的數(shù)據(jù)大部分都是熱點(diǎn)數(shù)據(jù),比如會(huì)啟用單獨(dú)的 Cache 集群或者 MySQL 數(shù)據(jù)庫(kù)來(lái)放熱點(diǎn)數(shù)據(jù),目的也是不想 0.01% 的數(shù)據(jù)有機(jī)會(huì)影響 99.99% 數(shù)據(jù)。
當(dāng)然了,實(shí)現(xiàn)隔離有很多種辦法。比如,你可以按照用戶來(lái)區(qū)分,給不同的用戶分配不同的 Cookie,在接入層,路由到不同的服務(wù)接口中;再比如,你還可以在接入層針對(duì) URL 中的不同 Path 來(lái)設(shè)置限流策略。服務(wù)層調(diào)用不同的服務(wù)接口,以及數(shù)據(jù)層通過(guò)給數(shù)據(jù)打標(biāo)來(lái)區(qū)分等等這些措施,其目的都是把已經(jīng)識(shí)別出來(lái)的熱點(diǎn)請(qǐng)求和普通的請(qǐng)求區(qū)分開。
流量削峰該怎么做
常用的一些思路:排隊(duì),答題,分層過(guò)濾。
排隊(duì)
要對(duì)流量進(jìn)行削峰,最容易想到的解決方案就是用消息隊(duì)列來(lái)緩沖瞬時(shí)流量,把同步的直接調(diào)用轉(zhuǎn)換成異步的間接推送,中間通過(guò)一個(gè)隊(duì)列在一端承接瞬時(shí)的流量洪峰,在另一端平滑地將消息推送出去。在這里,消息隊(duì)列就像“水庫(kù)”一樣,攔蓄上游的洪水,削減進(jìn)入下游河道的洪峰流量,從而達(dá)到減免洪水災(zāi)害的目的。
答題
答題的目的有兩個(gè):
1. 防止用戶作弊;
2. 延緩用戶請(qǐng)求;
分層過(guò)濾
分層校驗(yàn)的目的是:在讀系統(tǒng)中,盡量減少由于一致性校驗(yàn)帶來(lái)的系統(tǒng)瓶頸,但是盡量將不影響性能的檢查條件提前,如用戶是否具有秒殺資格、商品狀態(tài)是否正常、用戶答題是否正確、秒殺是否已經(jīng)結(jié)束、是否非法請(qǐng)求、營(yíng)銷等價(jià)物是否充足等;在寫數(shù)據(jù)系統(tǒng)中,主要對(duì)寫的數(shù)據(jù)(如“庫(kù)存”)做一致性檢查,最后在數(shù)據(jù)庫(kù)層保證數(shù)據(jù)的最終準(zhǔn)確性(如“庫(kù)存”不能減為負(fù)數(shù))。
減庫(kù)存的核心邏輯
減庫(kù)存的幾種方式
下單減庫(kù)存,即當(dāng)買家下單后,在商品的總庫(kù)存中減去買家購(gòu)買數(shù)量。下單減庫(kù)存是最簡(jiǎn)單的減庫(kù)存方式,也是控制最精確的一種,下單時(shí)直接通過(guò)數(shù)據(jù)庫(kù)的事務(wù)機(jī)制控制商品庫(kù)存,這樣一定不會(huì)出現(xiàn)超賣的情況。但是你要知道,有些人下完單可能并不會(huì)付款。
付款減庫(kù)存,即買家下單后,并不立即減庫(kù)存,而是等到有用戶付款后才真正減庫(kù)存,否則庫(kù)存一直保留給其他買家。但因?yàn)楦犊顣r(shí)才減庫(kù)存,如果并發(fā)比較高,有可能出現(xiàn)買家下單后付不了款的情況,因?yàn)榭赡苌唐芬呀?jīng)被其他人買走了。
預(yù)扣庫(kù)存,這種方式相對(duì)復(fù)雜一些,買家下單后,庫(kù)存為其保留一定的時(shí)間(如 10 分鐘),超過(guò)這個(gè)時(shí)間,庫(kù)存將會(huì)自動(dòng)釋放,釋放后其他買家就可以繼續(xù)購(gòu)買。在買家付款前,系統(tǒng)會(huì)校驗(yàn)該訂單的庫(kù)存是否還有保留:如果沒有保留,則再次嘗試預(yù)扣;如果庫(kù)存不足(也就是預(yù)扣失?。﹦t不允許繼續(xù)付款;如果預(yù)扣成功,則完成付款并實(shí)際地減去庫(kù)存。
減庫(kù)存可能存在的問(wèn)題
假如我們采用“下單減庫(kù)存”的方式,即用戶下單后就減去庫(kù)存,正常情況下,買家下單后付款的概率會(huì)很高,所以不會(huì)有太大問(wèn)題。但是有一種場(chǎng)景例外,就是當(dāng)賣家參加某個(gè)活動(dòng)時(shí),此時(shí)活動(dòng)的有效時(shí)間是商品的黃金售賣時(shí)間,如果有競(jìng)爭(zhēng)對(duì)手通過(guò)惡意下單的方式將該賣家的商品全部下單,讓這款商品的庫(kù)存減為零,那么這款商品就不能正常售賣了。要知道,這些惡意下單的人是不會(huì)真正付款的,這正是“下單減庫(kù)存”方式的不足之處。
既然“下單減庫(kù)存”可能導(dǎo)致惡意下單,從而影響賣家的商品銷售,那么有沒有辦法解決呢?你可能會(huì)想,采用“付款減庫(kù)存”的方式是不是就可以了?的確可以。但是,“付款減庫(kù)存”又會(huì)導(dǎo)致另外一個(gè)問(wèn)題:庫(kù)存超賣。
假如有 100 件商品,就可能出現(xiàn) 300 人下單成功的情況,因?yàn)橄聠螘r(shí)不會(huì)減庫(kù)存,所以也就可能出現(xiàn)下單成功數(shù)遠(yuǎn)遠(yuǎn)超過(guò)真正庫(kù)存數(shù)的情況,這尤其會(huì)發(fā)生在做活動(dòng)的熱門商品上。這樣一來(lái),就會(huì)導(dǎo)致很多買家下單成功但是付不了款,買家的購(gòu)物體驗(yàn)自然比較差。
那么,既然“下單減庫(kù)存”和“付款減庫(kù)存”都有缺點(diǎn),我們能否把兩者相結(jié)合,將兩次操作進(jìn)行前后關(guān)聯(lián)起來(lái),下單時(shí)先預(yù)扣,在規(guī)定時(shí)間內(nèi)不付款再釋放庫(kù)存,即采用“預(yù)扣庫(kù)存”這種方式呢?
這種方案確實(shí)可以在一定程度上緩解上面的問(wèn)題。但是否就徹底解決了呢?其實(shí)沒有!針對(duì)惡意下單這種情況,雖然把有效的付款時(shí)間設(shè)置為 10 分鐘,但是惡意買家完全可以在 10 分鐘后再次下單,或者采用一次下單很多件的方式把庫(kù)存減完。針對(duì)這種情況,解決辦法還是要結(jié)合安全和反作弊的措施來(lái)制止。
例如,給經(jīng)常下單不付款的買家進(jìn)行識(shí)別打標(biāo)(可以在被打標(biāo)的買家下單時(shí)不減庫(kù)存)、給某些類目設(shè)置最大購(gòu)買件數(shù)(例如,參加活動(dòng)的商品一人最多只能買 3 件),以及對(duì)重復(fù)下單不付款的操作進(jìn)行次數(shù)限制等。
針對(duì)“庫(kù)存超賣”這種情況,在 10 分鐘時(shí)間內(nèi)下單的數(shù)量仍然有可能超過(guò)庫(kù)存數(shù)量,遇到這種情況我們只能區(qū)別對(duì)待:對(duì)普通的商品下單數(shù)量超過(guò)庫(kù)存數(shù)量的情況,可以通過(guò)補(bǔ)貨來(lái)解決;但是有些賣家完全不允許庫(kù)存為負(fù)數(shù)的情況,那只能在買家付款時(shí)提示庫(kù)存不足。
大型秒殺中如何減庫(kù)存
目前來(lái)看,業(yè)務(wù)系統(tǒng)中最常見的就是預(yù)扣庫(kù)存方案,像你在買機(jī)票、買電影票時(shí),下單后一般都有個(gè)“有效付款時(shí)間”,超過(guò)這個(gè)時(shí)間訂單自動(dòng)釋放,這都是典型的預(yù)扣庫(kù)存方案。而具體到秒殺這個(gè)場(chǎng)景,應(yīng)該采用哪種方案比較好呢?
由于參加秒殺的商品,一般都是“搶到就是賺到”,所以成功下單后卻不付款的情況比較少,再加上賣家對(duì)秒殺商品的庫(kù)存有嚴(yán)格限制,所以秒殺商品采用“下單減庫(kù)存”更加合理。另外,理論上由于“下單減庫(kù)存”比“預(yù)扣庫(kù)存”以及涉及第三方支付的“付款減庫(kù)存”在邏輯上更為簡(jiǎn)單,所以性能上更占優(yōu)勢(shì)。
"下單減庫(kù)存"在數(shù)據(jù)一致性上,主要就是保證大并發(fā)請(qǐng)求時(shí)庫(kù)存數(shù)據(jù)不能為負(fù)數(shù),也就是要保證數(shù)據(jù)庫(kù)中的庫(kù)存字段值不能為負(fù)數(shù),一般我們有多種解決方案:一種是在應(yīng)用程序中通過(guò)事務(wù)來(lái)判斷,即保證減后庫(kù)存不能為負(fù)數(shù),否則就回滾;另一種辦法是直接設(shè)置數(shù)據(jù)庫(kù)的字段數(shù)據(jù)為無(wú)符號(hào)整數(shù),這樣減后庫(kù)存字段值小于零時(shí)會(huì)直接執(zhí)行 SQL 語(yǔ)句來(lái)報(bào)錯(cuò);再有一種就是使用 CASE WHEN 判斷語(yǔ)句,例如這樣的 SQL 語(yǔ)句:
UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END
秒殺減庫(kù)存的極致優(yōu)化
在交易環(huán)節(jié)中,“庫(kù)存”是個(gè)關(guān)鍵數(shù)據(jù),也是個(gè)熱點(diǎn)數(shù)據(jù),因?yàn)榻灰椎母鱾€(gè)環(huán)節(jié)中都可能涉及對(duì)庫(kù)存的查詢。但是,我在前面介紹分層過(guò)濾時(shí)提到過(guò),秒殺中并不需要對(duì)庫(kù)存有精確的一致性讀,把庫(kù)存數(shù)據(jù)放到緩存(Cache)中,可以大大提升讀性能。
解決大并發(fā)讀問(wèn)題,可以采用 LocalCache(即在秒殺系統(tǒng)的單機(jī)上緩存商品相關(guān)的數(shù)據(jù))和對(duì)數(shù)據(jù)進(jìn)行分層過(guò)濾的方式,但是像減庫(kù)存這種大并發(fā)寫無(wú)論如何還是避免不了,這也是秒殺場(chǎng)景下最為核心的一個(gè)技術(shù)難題。
因此,這里我想專門來(lái)說(shuō)一下秒殺場(chǎng)景下減庫(kù)存的極致優(yōu)化思路,包括如何在緩存中減庫(kù)存以及如何在數(shù)據(jù)庫(kù)中減庫(kù)存。
秒殺商品和普通商品的減庫(kù)存還是有些差異的,例如商品數(shù)量比較少,交易時(shí)間段也比較短,因此這里有一個(gè)大膽的假設(shè),即能否把秒殺商品減庫(kù)存直接放到緩存系統(tǒng)中實(shí)現(xiàn),也就是直接在緩存中減庫(kù)存或者在一個(gè)帶有持久化功能的緩存系統(tǒng)(如 Redis)中完成呢?
如果你的秒殺商品的減庫(kù)存邏輯非常單一,比如沒有復(fù)雜的 SKU 庫(kù)存和總庫(kù)存這種聯(lián)動(dòng)關(guān)系的話,我覺得完全可以。但是如果有比較復(fù)雜的減庫(kù)存邏輯,或者需要使用事務(wù),你還是必須在數(shù)據(jù)庫(kù)中完成減庫(kù)存。
由于 MySQL 存儲(chǔ)數(shù)據(jù)的特點(diǎn),同一數(shù)據(jù)在數(shù)據(jù)庫(kù)里肯定是一行存儲(chǔ)(MySQL),因此會(huì)有大量線程來(lái)競(jìng)爭(zhēng) InnoDB 行鎖,而并發(fā)度越高時(shí)等待線程會(huì)越多,TPS(Transaction Per Second,即每秒處理的消息數(shù))會(huì)下降,響應(yīng)時(shí)間(RT)會(huì)上升,數(shù)據(jù)庫(kù)的吞吐量就會(huì)嚴(yán)重受影響。
這就可能引發(fā)一個(gè)問(wèn)題,就是單個(gè)熱點(diǎn)商品會(huì)影響整個(gè)數(shù)據(jù)庫(kù)的性能, 導(dǎo)致 0.01% 的商品影響 99.99% 的商品的售賣,這是我們不愿意看到的情況。一個(gè)解決思路是遵循前面介紹的原則進(jìn)行隔離,把熱點(diǎn)商品放到單獨(dú)的熱點(diǎn)庫(kù)中。但是這無(wú)疑會(huì)帶來(lái)維護(hù)上的麻煩,比如要做熱點(diǎn)數(shù)據(jù)的動(dòng)態(tài)遷移以及單獨(dú)的數(shù)據(jù)庫(kù)等。
而分離熱點(diǎn)商品到單獨(dú)的數(shù)據(jù)庫(kù)還是沒有解決并發(fā)鎖的問(wèn)題,我們應(yīng)該怎么辦呢?要解決并發(fā)鎖的問(wèn)題,有兩種辦法:
應(yīng)用層做排隊(duì)。按照商品維度設(shè)置隊(duì)列順序執(zhí)行,這樣能減少同一臺(tái)機(jī)器對(duì)數(shù)據(jù)庫(kù)同一行記錄進(jìn)行操作的并發(fā)度,同時(shí)也能控制單個(gè)商品占用數(shù)據(jù)庫(kù)連接的數(shù)量,防止熱點(diǎn)商品占用太多的數(shù)據(jù)庫(kù)連接。
數(shù)據(jù)庫(kù)層做排隊(duì)。應(yīng)用層只能做到單機(jī)的排隊(duì),但是應(yīng)用機(jī)器數(shù)本身很多,這種排隊(duì)方式控制并發(fā)的能力仍然有限,所以如果能在數(shù)據(jù)庫(kù)層做全局排隊(duì)是最理想的。阿里的數(shù)據(jù)庫(kù)團(tuán)隊(duì)開發(fā)了針對(duì)這種 MySQL 的 InnoDB 層上的補(bǔ)丁程序(patch),可以在數(shù)據(jù)庫(kù)層上對(duì)單行記錄做到并發(fā)排隊(duì)。
另外,數(shù)據(jù)更新問(wèn)題除了前面介紹的熱點(diǎn)隔離和排隊(duì)處理之外,還有些場(chǎng)景(如對(duì)商品的 lastmodifytime 字段的)更新會(huì)非常頻繁,在某些場(chǎng)景下這些多條 SQL 是可以合并的,一定時(shí)間內(nèi)只要執(zhí)行最后一條 SQL 就行了,以便減少對(duì)數(shù)據(jù)庫(kù)的更新操作。