2020重新出發(fā),NOSQL,redis高并發(fā)系統(tǒng)的分析和設(shè)計(jì)

高并發(fā)系統(tǒng)的分析和設(shè)計(jì)

任何系統(tǒng)都不是獨(dú)立于業(yè)務(wù)進(jìn)行開發(fā)的,真正的系統(tǒng)是為了實(shí)現(xiàn)業(yè)務(wù)而開發(fā)的,所以開發(fā)高并發(fā)網(wǎng)站搶購(gòu)時(shí),都應(yīng)該先分析業(yè)務(wù)需求和實(shí)際的場(chǎng)景,在完善這些需求之后才能進(jìn)入系統(tǒng)開發(fā)階段。

沒(méi)有對(duì)業(yè)務(wù)進(jìn)行分析就貿(mào)然開發(fā)系統(tǒng)是開發(fā)者的大忌。對(duì)于業(yè)務(wù)分析,首先是有效請(qǐng)求和無(wú)效請(qǐng)求,有效請(qǐng)求是指真實(shí)的需求,而無(wú)效請(qǐng)求則是虛假的搶購(gòu)請(qǐng)求。

有效請(qǐng)求和無(wú)效請(qǐng)求

無(wú)效請(qǐng)求有很多種類,比如通過(guò)腳本連續(xù)刷新網(wǎng)站首頁(yè),使得網(wǎng)站頻繁訪問(wèn)數(shù)據(jù)庫(kù)和其他資源,造成性能持續(xù)下降,還有一些為了得到搶購(gòu)商品,使用刷票軟件連續(xù)請(qǐng)求的行為。

鑒別有效請(qǐng)求和無(wú)效請(qǐng)求是獲取有效請(qǐng)求的高并發(fā)網(wǎng)站業(yè)務(wù)分析的第一步,我們現(xiàn)在來(lái)分析哪些是無(wú)效請(qǐng)求的場(chǎng)景,以及應(yīng)對(duì)方法。

首先,一個(gè)賬號(hào)連續(xù)請(qǐng)求,對(duì)于一些懂技術(shù)或者使用作弊軟件的用戶,可以使用軟件對(duì)請(qǐng)求的服務(wù)接口連續(xù)請(qǐng)求,使得后臺(tái)壓力變大,甚至在一秒內(nèi)發(fā)送成百上千個(gè)請(qǐng)求到服務(wù)器。

這樣的請(qǐng)求顯然可以認(rèn)為是無(wú)效請(qǐng)求,應(yīng)對(duì)它的方法很多,常見的做法是加入驗(yàn)證碼。一般而言,首次無(wú)驗(yàn)證碼以便用戶減少錄入,第二次請(qǐng)求開始加入驗(yàn)證碼,可以是圖片驗(yàn)證碼、等式運(yùn)算等。

使用圖片驗(yàn)證碼可能存在識(shí)別圖片作弊軟件的攻擊,所以在一些互聯(lián)網(wǎng)網(wǎng)站中,圖片驗(yàn)證碼還會(huì)被加工成為東倒西歪的形式,這樣增加了圖片識(shí)別作弊軟件的辨別難度,以壓制作弊軟件的使用。簡(jiǎn)單的等式運(yùn)算,也會(huì)使圖片識(shí)別作弊軟件更加難以辨認(rèn)。

其次,使用短信服務(wù),把驗(yàn)證碼發(fā)送到短信平臺(tái)以規(guī)避部分作弊軟件。

在企業(yè)應(yīng)用中,這類問(wèn)題的邏輯判斷,不應(yīng)該放在Web服務(wù)器中實(shí)現(xiàn),而應(yīng)放在負(fù)載均衡器上完成,即在進(jìn)入 Web 服務(wù)器之前完成,做完這一步就能避免大量的無(wú)效請(qǐng)求,對(duì)保證高并發(fā)服務(wù)器可用性很有效果。

僅僅做這一步或許還不夠,畢竟驗(yàn)證碼或許還有其他作弊軟件可以快速讀取圖片或者短信信息,從而發(fā)送大量的請(qǐng)求。進(jìn)一步的限制請(qǐng)求,比如限制用戶在單位時(shí)間的購(gòu)買次數(shù)以壓制其請(qǐng)求量,使得這些請(qǐng)求排除在服務(wù)器之外。判斷驗(yàn)證碼邏輯,如圖 1 所示。

判斷驗(yàn)證碼邏輯

圖 1 判斷驗(yàn)證碼邏輯

這里的判斷是在負(fù)載均衡轉(zhuǎn)發(fā)給 Web 服務(wù)器前,對(duì)驗(yàn)證碼和單位時(shí)間單個(gè)賬號(hào)請(qǐng)求數(shù)量進(jìn)行判斷。這里使用了 C 語(yǔ)言和 Redis 進(jìn)行判斷,那么顯然這套方案會(huì)比 Java 語(yǔ)言和數(shù)據(jù)庫(kù)機(jī)制的性能要高得多,通過(guò)這套體系,基本能夠壓制一個(gè)用戶對(duì)系統(tǒng)的作弊,也提高了整個(gè)系統(tǒng)驗(yàn)證的性能。

這是對(duì)一個(gè)賬號(hào)連續(xù)無(wú)效請(qǐng)求的壓制,有時(shí)候有些用戶可能申請(qǐng)多個(gè)賬號(hào)來(lái)迷惑服務(wù)器,使得他可以避開對(duì)單個(gè)賬戶的驗(yàn)證,從而獲得更多的服務(wù)器資源。

一個(gè)人多個(gè)賬戶的場(chǎng)景還是比較好應(yīng)付的,可以通過(guò)提高賬戶的等級(jí)來(lái)壓制多個(gè)請(qǐng)求,比如對(duì)于支付交易的網(wǎng)站,可以通過(guò)銀行卡驗(yàn)證,實(shí)名制獲取相關(guān)證件號(hào)碼,從而使用證件號(hào)碼使得多個(gè)賬戶歸結(jié)為一人,通過(guò)這層關(guān)系來(lái)屏蔽多個(gè)賬號(hào)的頻繁請(qǐng)求,這樣就有效地規(guī)避了一個(gè)人多個(gè)賬號(hào)的頻繁請(qǐng)求。

對(duì)于有組織的請(qǐng)求,則不是那么容易了,因?yàn)閷?duì)于一些黃牛組織,可能通過(guò)多人的賬號(hào)來(lái)發(fā)送請(qǐng)求,統(tǒng)一組織偽造有效請(qǐng)求,如圖 2 所示。

統(tǒng)一組織偽造有效請(qǐng)求

圖 2 統(tǒng)一組織偽造有效請(qǐng)求

對(duì)于這樣的請(qǐng)求,我們會(huì)考慮使用僵尸賬號(hào)排除法對(duì)可交易的賬號(hào)進(jìn)行排除,所謂僵尸賬號(hào),是指那些平時(shí)沒(méi)有任何交易的賬號(hào),只是在特殊的日子交易,比如春運(yùn)期間進(jìn)行大批量搶購(gòu)的賬號(hào)。

當(dāng)請(qǐng)求達(dá)到服務(wù)器,我們通過(guò)僵尸賬號(hào),排除掉一些無(wú)效請(qǐng)求。當(dāng)然還能使用 IP 封禁,尤其是通過(guò)同一 IP 或者網(wǎng)段頻繁請(qǐng)求的,但是這樣也許會(huì)誤傷有效請(qǐng)求,所以使用 IP 封禁還是要慎重一些。

系統(tǒng)設(shè)計(jì)

高并發(fā)系統(tǒng)往往需要分布式的系統(tǒng)分?jǐn)傉?qǐng)求的壓力,這就需要使用負(fù)載均衡服務(wù)了,它進(jìn)行簡(jiǎn)易判斷后就會(huì)分發(fā)到具體 Web 服務(wù)器。

我們要盡量根據(jù) Web 服務(wù)器的性能進(jìn)行均衡分配請(qǐng)求,使得單個(gè) Web 服務(wù)器壓力不至于過(guò)大,導(dǎo)致服務(wù)癱瘓,這可以參考 Nginx 的請(qǐng)求分發(fā),這樣使得請(qǐng)求能夠均衡發(fā)布到服務(wù)器中去,服務(wù)器可以按業(yè)務(wù)劃分。

比如當(dāng)前的購(gòu)買分為產(chǎn)品維護(hù)、交易維護(hù)、資金維護(hù)、報(bào)表統(tǒng)計(jì)和用戶維護(hù)等模塊,按照功能模塊進(jìn)行區(qū)分,使得它們相互隔離,就可以降低數(shù)據(jù)的復(fù)雜性,圖 3 就是一種典型的按業(yè)務(wù)劃分,或者稱為水平分法。

按業(yè)務(wù)劃分

圖 3 按業(yè)務(wù)劃分

按照業(yè)務(wù)劃分的好處是:首先,一個(gè)服務(wù)管理一種業(yè)務(wù),業(yè)務(wù)簡(jiǎn)單了,提高了開發(fā)效率,其次,數(shù)據(jù)庫(kù)的設(shè)計(jì)也方便許多,畢竟各管各的東西。

但是,這也會(huì)帶來(lái)很多麻煩,比如由于各個(gè)系統(tǒng)業(yè)務(wù)存在著關(guān)聯(lián),還要通過(guò) RPC(Remote Procedure Call Protoco,遠(yuǎn)程過(guò)程調(diào)用協(xié)議)處理這些關(guān)聯(lián)信息。

比較流行的 RPC 有 Dubbo、Thrift 和 Hessian 等。其原理是,每一個(gè)服務(wù)都會(huì)暴露一些公共的接口給 RPC 服務(wù),這樣對(duì)于任何一個(gè)服務(wù)器都能夠通過(guò) RPC 服務(wù)獲取其他服務(wù)器對(duì)應(yīng)的接口去調(diào)度各個(gè)服務(wù)器的邏輯來(lái)完成功能,但是接口的相互調(diào)用也會(huì)造成一定的緩慢。

有了水平分法也會(huì)有垂直分法,所謂垂直分法就是將一個(gè)很大的請(qǐng)求量,不按子系統(tǒng)分,而是將它們按照互不相干的幾個(gè)同樣的系統(tǒng)分?jǐn)傁氯ァ?/p>

比如一臺(tái)服務(wù)器的最大負(fù)荷為每秒 1 萬(wàn)個(gè)請(qǐng)求,而測(cè)得系統(tǒng)高峰為每秒 2 萬(wàn)個(gè)請(qǐng)求,如果我們把各個(gè)請(qǐng)求按照一定的算法合理分配到 4 臺(tái)服務(wù)器上,那么 4 臺(tái)服務(wù)器平均 5 千個(gè)請(qǐng)求就屬于正常服務(wù)了,這樣的路由算法被稱為垂直分法,如圖 4 所示。

垂直分法

圖 4 垂直分法

垂直分法不按業(yè)務(wù)分,對(duì)于負(fù)載均衡器的算法往往可以通過(guò)用戶編號(hào)把路由分發(fā)到對(duì)應(yīng)的服務(wù)器上。

每一個(gè)服務(wù)器處理自己獨(dú)立的業(yè)務(wù),互不干擾,但是每一個(gè)服務(wù)器都包含所有的業(yè)務(wù)邏輯功能,會(huì)造成開發(fā)上的業(yè)務(wù)困難,對(duì)于數(shù)據(jù)庫(kù)設(shè)計(jì)而言也是如此。

對(duì)于大型網(wǎng)站還會(huì)有更細(xì)的分法,比如水平和垂直結(jié)合的分法,如圖 5 所示。

水平和垂直結(jié)合分法

圖 5 水平和垂直結(jié)合分法

首先將系統(tǒng)按照業(yè)務(wù)區(qū)分為多個(gè)子系統(tǒng),然后在每一個(gè)子系統(tǒng)下再分多個(gè)服務(wù)器,通過(guò)每一個(gè)子系統(tǒng)的路由器找到對(duì)應(yīng)的子系統(tǒng)服務(wù)器提供服務(wù)。

分法是多樣性的,每一個(gè)企業(yè)都會(huì)根據(jù)自己的需要而進(jìn)行不同的設(shè)計(jì),但是無(wú)論系統(tǒng)如何分,秉承的原則是不變的。

首先,服務(wù)器的負(fù)載均衡,要使得每一個(gè)服務(wù)器都能比較平均地得到請(qǐng)求數(shù)量,從而提高系統(tǒng)的吞吐和性能。其次,業(yè)務(wù)簡(jiǎn)化,按照模塊劃分可以使得系統(tǒng)劃分為各個(gè)子系統(tǒng),這樣開發(fā)者的業(yè)務(wù)單一化,就更容易理解和開發(fā)了。

數(shù)據(jù)庫(kù)設(shè)計(jì)

對(duì)于數(shù)據(jù)庫(kù)的設(shè)計(jì)而言,為了得到高性能,可以使用分表或分庫(kù)技術(shù),從而提高系統(tǒng)的響應(yīng)能力。

分表是指在一個(gè)數(shù)據(jù)庫(kù)內(nèi)本來(lái)一張表可以保存的數(shù)據(jù),設(shè)計(jì)成多張表去保存,比如交易表 t_transaction。

由于存儲(chǔ)數(shù)據(jù)多會(huì)造成查詢和統(tǒng)計(jì)的緩慢,這個(gè)時(shí)候可以使用多個(gè)表存儲(chǔ),比如 2016 年的數(shù)據(jù)用表 t_transaction_2016 存儲(chǔ),2017 年的數(shù)據(jù)使用表 t_transaction_2017 存儲(chǔ),2018 年的數(shù)據(jù)則用表 t_transaction_2018 存儲(chǔ),依此類推,開發(fā)者只要根據(jù)查詢的年份確定需要查找哪張表就可以了,如圖 6 所示。

通過(guò)年份路由分表

圖 6 通過(guò)年份路由分表

分庫(kù)則不一樣,它把表數(shù)據(jù)分配在不同的數(shù)據(jù)庫(kù)中,比如上述的交易表 t_transaction 可以存放在多個(gè)數(shù)據(jù)庫(kù)中,如圖 7 所示。

分庫(kù)設(shè)計(jì)

圖 7 分庫(kù)設(shè)計(jì)

分庫(kù)數(shù)據(jù)庫(kù)首先需要一個(gè)路由算法確定數(shù)據(jù)在哪個(gè)數(shù)據(jù)庫(kù)上,然后才能進(jìn)行查詢,比如我們可以把用戶和對(duì)應(yīng)業(yè)務(wù)的數(shù)據(jù)庫(kù)的信息緩存到 Redis 中,這樣路由算法就可以通過(guò) Redis 讀取的數(shù)據(jù)來(lái)決定使用哪個(gè)數(shù)據(jù)庫(kù)進(jìn)行查詢了。

一些會(huì)員很多的網(wǎng)站還可以區(qū)分活躍會(huì)員和非活躍會(huì)員?;钴S會(huì)員可以通過(guò)數(shù)據(jù)遷徙的手段,也就是先記錄在某個(gè)時(shí)間段(比如一個(gè)月的月底)會(huì)員的活躍度,然后通過(guò)數(shù)據(jù)遷徙,將活躍會(huì)員較平均分?jǐn)偟礁鱾€(gè)數(shù)據(jù)庫(kù)中,以避免某個(gè)庫(kù)過(guò)多的集中活躍會(huì)員,而導(dǎo)致個(gè)別數(shù)據(jù)庫(kù)被訪問(wèn)過(guò)多,從而達(dá)到數(shù)據(jù)庫(kù)的負(fù)載均衡。

做完這些還可以考慮優(yōu)化 SQL,建立索引等優(yōu)化,提高數(shù)據(jù)庫(kù)的性能。性能低下的 SQL 對(duì)于高并發(fā)網(wǎng)站的影響是很大的,這些對(duì)開發(fā)者提出了更高的要求。

在開發(fā)網(wǎng)站中使用更新語(yǔ)句和復(fù)雜查詢語(yǔ)句要時(shí)刻記住更新是表鎖定還是行鎖定,比如 id 是主鍵,而 user_name 是用戶名稱,也是唯一索引,更新用戶的生日,可以使用以下兩條SQL中的任何一條:

update t_user set birthday = #{birthday} where id = #{id}; update t_user set birthday = #{birthday} where user_name = #{userName};

上述邏輯都是正確的,但是優(yōu)選使用主鍵更新,其原因是在 MySQL 的運(yùn)行過(guò)程中,第二句 SQL 會(huì)鎖表,即不僅鎖定更新的數(shù)據(jù),而且鎖定其他表的數(shù)據(jù),從而影響并發(fā),而使用主鍵的更新則是行鎖定。

對(duì)于 SQL 的優(yōu)化還有很多細(xì)節(jié),比如可以使用連接查詢代替子查詢。查詢一個(gè)沒(méi)有分配角色的用戶 id,可能有人使用這樣的一個(gè) SQL:

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="sql" cid="n63" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> select u.id from t_user u
where u.id not in (select ur.user_id from t_user_role ur);</pre>

這是一個(gè) not in 語(yǔ)句,性能低下,對(duì)于這樣的 not in 和 not exists 語(yǔ)句,應(yīng)該全部修改為連接語(yǔ)句去執(zhí)行,從而極大地提高 SQL 的性能,比如這條 not in 語(yǔ)句可以修改為:

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="sql" cid="n65" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> select u.id from t_user u left join t_user_role ur
on u.id = ur.user_id
where ur.user_id is null;</pre>

not in 語(yǔ)句消失了,使用了連接查詢,大大提高了 SQL 的執(zhí)行性能。

此外還可以通過(guò)讀/寫分離等技術(shù),進(jìn)行進(jìn)一步的優(yōu)化,這樣就可以有一臺(tái)主機(jī)主要負(fù)責(zé)寫業(yè)務(wù),一臺(tái)或者多臺(tái)備機(jī)負(fù)責(zé)讀業(yè)務(wù),有助于性能的提高。

對(duì)于分布式數(shù)據(jù)庫(kù)而言,還會(huì)有另外一個(gè)麻煩,就是事務(wù)的一致性,事務(wù)的一致性比較復(fù)雜,目前流行的有兩段提交協(xié)議,即 XA 協(xié)議、Paxos 協(xié)議。

動(dòng)靜分離技術(shù)

動(dòng)靜分離技術(shù)是目前互聯(lián)網(wǎng)的主流技術(shù),對(duì)于互聯(lián)網(wǎng)而言大部分?jǐn)?shù)據(jù)都是靜態(tài)數(shù)據(jù),只有少數(shù)使用動(dòng)態(tài)數(shù)據(jù),動(dòng)態(tài)數(shù)據(jù)的數(shù)據(jù)包很小,不會(huì)造成網(wǎng)絡(luò)瓶頸。

而靜態(tài)的數(shù)據(jù)則不一樣,靜態(tài)數(shù)據(jù)包含圖片、CSS(樣式)、JavaScript(腳本)和視頻等互聯(lián)網(wǎng)的應(yīng)用,尤其是圖片和視頻占據(jù)的流量很大,如果都從動(dòng)態(tài)服務(wù)器(比如 Tomcat、WildFly 和 WebLogic 等)獲取,那么動(dòng)態(tài)服務(wù)器的帶寬壓力會(huì)很大,這個(gè)時(shí)候應(yīng)該考慮使用動(dòng)靜分離技術(shù)。

對(duì)于一些有條件的企業(yè)也可以考慮使用 CDN(Content Delivery Network,即內(nèi)容分發(fā)網(wǎng)絡(luò))技術(shù),它允許企業(yè)將自己的靜態(tài)數(shù)據(jù)緩存到網(wǎng)絡(luò) CDN 的節(jié)點(diǎn)中。比如企業(yè)將數(shù)據(jù)緩存在北京的節(jié)點(diǎn)上,當(dāng)在天津的客戶發(fā)送請(qǐng)求時(shí),通過(guò)一定的算法,會(huì)找到北京 CDN 節(jié)點(diǎn),從而把 CDN 緩存的數(shù)據(jù)發(fā)送給天津的客戶,完成請(qǐng)求。

對(duì)于深圳的客戶,如果企業(yè)將數(shù)據(jù)緩存到廣州 CDN 節(jié)點(diǎn)上,那么它也可以從廣州的 CDN 節(jié)點(diǎn)上取出數(shù)據(jù),由于就近取出緩存節(jié)點(diǎn)的數(shù)據(jù),所以速度會(huì)很快,如圖 8 所示。

圖解CDN

圖 8 圖解CDN

一些企業(yè)也許需要自己的靜態(tài) HTTP 服務(wù)器,將靜態(tài)數(shù)據(jù)分離到靜態(tài) HTTP 服務(wù)器上。其原理大同小異,就是將資源分配到靜態(tài)服務(wù)器上,這樣圖片、HTML、腳本等資源都可以從靜態(tài)服務(wù)器上獲取,盡量使用 Cookie 等技術(shù),讓客戶端緩存能夠緩存數(shù)據(jù),避免多次請(qǐng)求,降低服務(wù)器的壓力。

對(duì)于動(dòng)態(tài)數(shù)據(jù),則需要根據(jù)會(huì)員登錄來(lái)獲取后臺(tái)數(shù)據(jù),這樣的動(dòng)態(tài)數(shù)據(jù)是高并發(fā)網(wǎng)站關(guān)注的重點(diǎn)。

鎖和高并發(fā)

無(wú)論區(qū)分有效請(qǐng)求和無(wú)效請(qǐng)求,水平劃分和垂直劃分,動(dòng)靜分離技術(shù),還是數(shù)據(jù)庫(kù)分表、分庫(kù)等技術(shù)的應(yīng)用,都無(wú)法避免動(dòng)態(tài)數(shù)據(jù),而動(dòng)態(tài)數(shù)據(jù)的請(qǐng)求最終也會(huì)落在一臺(tái) Web 服務(wù)器上。

對(duì)于一臺(tái) Web 服務(wù)器而言,如果是 Java 服務(wù)器,它極有可能采用 SSM 框架結(jié)合數(shù)據(jù)庫(kù)和 Redis 等技術(shù)提供服務(wù),那么它會(huì)面臨何種困難呢?高并發(fā)系統(tǒng)存在的一個(gè)麻煩是并發(fā)數(shù)據(jù)不一致問(wèn)題。

以搶紅包為例,發(fā)放了一個(gè)總額為 20 萬(wàn)元的紅包,它可以拆分為 2 萬(wàn)個(gè)可搶的小紅包。假設(shè)每個(gè)小紅包都是 10 元,供給網(wǎng)站會(huì)員搶奪,網(wǎng)站同時(shí)存在 3 萬(wàn)會(huì)員在線搶奪,這就是一個(gè)典型的高并發(fā)的場(chǎng)景。

以上會(huì)出現(xiàn)多個(gè)線程同時(shí)享有大紅包數(shù)據(jù)的場(chǎng)景,在高并發(fā)的場(chǎng)景中,由于線程每一步完成的順序不一樣,這樣會(huì)導(dǎo)致數(shù)據(jù)的一致性問(wèn)題,比如在最后的一個(gè)紅包,就可能出現(xiàn)如表 1 所示的場(chǎng)景。

注意表 1 中加粗的文字,由此可見,在高并發(fā)的場(chǎng)景下可能出現(xiàn)錯(cuò)扣紅包的情況,這樣就會(huì)導(dǎo)致數(shù)據(jù)錯(cuò)誤。由于在一個(gè)瞬間產(chǎn)生很高的并發(fā),因此除了保證數(shù)據(jù)一致性,我們還要盡可能地保證系統(tǒng)的性能,加鎖會(huì)影響并發(fā),而不加鎖就難以保證數(shù)據(jù)的一致性,這就是高并發(fā)和鎖的矛盾。

時(shí)刻 線程一 線程二 備注
T0 —— —— 存在最后一個(gè)紅包可搶
T1 讀取大紅包信息,存在最后一個(gè)紅包,可搶 —— ——
T2 —— 讀取大紅包信息,存在最后一個(gè)紅包,可搶 ——
T3 扣減最后一個(gè)紅包 —— 此時(shí)已經(jīng)不存在紅包可搶
T4 —— 扣減紅包 錯(cuò)誤發(fā)生了,超扣了
T5 記錄用戶獲取紅包信息 —— ——
T6 —— 記錄用戶獲取紅包信息 因?yàn)殄e(cuò)誤扣減紅包而引發(fā)的錯(cuò)誤

為了解決這對(duì)矛盾,在當(dāng)前互聯(lián)網(wǎng)系統(tǒng)中,大部分企業(yè)提出了悲觀鎖和樂(lè)觀鎖的概念,而對(duì)于數(shù)據(jù)庫(kù)而言,如果在那么短的時(shí)間內(nèi)需要執(zhí)行大量 SQL,對(duì)于服務(wù)器的壓力可想而知,需要優(yōu)化數(shù)據(jù)庫(kù)的表設(shè)計(jì)、索引、SQL 語(yǔ)句等。

有些企業(yè)提出了使用 Redis 事務(wù)和 Lua 語(yǔ)言所提供的原子性來(lái)取代現(xiàn)有的數(shù)據(jù)庫(kù)的技術(shù),從而提高數(shù)據(jù)的存儲(chǔ)響應(yīng),以應(yīng)對(duì)高并發(fā)場(chǎng)景,嚴(yán)格來(lái)說(shuō)它也屬于樂(lè)觀鎖的概念。教程后面會(huì)討論關(guān)于數(shù)據(jù)不一致的方案,悲觀鎖、樂(lè)觀鎖和 Redis 實(shí)現(xiàn)的場(chǎng)景。

Redis悲觀鎖、樂(lè)觀鎖和調(diào)用Lua腳本三種方式的優(yōu)缺點(diǎn)

教程前面主要討論了 Java 互聯(lián)網(wǎng)的高并發(fā)應(yīng)用,先談及了一些常用的系統(tǒng)設(shè)計(jì)理念,用以搭建高可用的互聯(lián)網(wǎng)應(yīng)用系統(tǒng),還討論了數(shù)據(jù)不一致的超發(fā)問(wèn)題。

并且還論述了樂(lè)觀鎖、悲觀鎖和 Redis 如何消除數(shù)據(jù)不一致性的問(wèn)題,也對(duì)它們的性能進(jìn)行了探討。

悲觀鎖使用了數(shù)據(jù)庫(kù)的鎖機(jī)制,可以消除數(shù)據(jù)不一致性,對(duì)于開發(fā)者而言會(huì)十分簡(jiǎn)單,但是,使用悲觀鎖后,數(shù)據(jù)庫(kù)的性能有所下降,因?yàn)榇罅康木€程都會(huì)被阻塞,而且需要有大量的恢復(fù)過(guò)程,需要進(jìn)一步改變算法以提高系統(tǒng)的并發(fā)能力。

通過(guò) CAS 原理和 ABA 問(wèn)題的討論,我們更加明確了樂(lè)觀鎖的原理,使用樂(lè)觀鎖有助于提高并發(fā)性能,但是由于版本號(hào)沖突,樂(lè)觀鎖導(dǎo)致多次請(qǐng)求服務(wù)失敗的概率大大提高,而我們通過(guò)重入(按時(shí)間戳或者按次數(shù)限定)來(lái)提高成功的概率,這樣對(duì)于樂(lè)觀鎖而言實(shí)現(xiàn)的方式就相對(duì)復(fù)雜了,其性能也會(huì)隨著版本號(hào)沖突的概率提升而提升,并不穩(wěn)定。

使用樂(lè)觀鎖的弊端在于,導(dǎo)致大量的 SQL 被執(zhí)行,對(duì)于數(shù)據(jù)庫(kù)的性能要求較高,容易引起數(shù)據(jù)庫(kù)性能的瓶頸,而且對(duì)于開發(fā)還要考慮重入機(jī)制,從而導(dǎo)致開發(fā)難度加大。

使用 Redis 去實(shí)現(xiàn)高并發(fā),通過(guò) Redis 提供的 Lua 腳本的原子性,消除了數(shù)據(jù)不一致性,并且在整個(gè)過(guò)程中只有最后一次涉及數(shù)據(jù)庫(kù),而且是使用了新的線程。

在實(shí)際的操作中筆者更加傾向于使用 JMS 啟動(dòng)另外的服務(wù)器進(jìn)行操作。但是這樣使用的風(fēng)險(xiǎn)在于 Redis 的不穩(wěn)定性,因?yàn)槠涫聞?wù)和存儲(chǔ)都存在不穩(wěn)定的因素,所以更多的時(shí)候,筆者都建議使用獨(dú)立 Redis 服務(wù)器做高并發(fā)業(yè)務(wù),一方面可以提高 Redis 的性能,另一方面即使在高并發(fā)的場(chǎng)合,Redis 服務(wù)器宕機(jī)也不會(huì)影響現(xiàn)有的其他業(yè)務(wù),同時(shí)也可以使用備機(jī)等設(shè)備提高系統(tǒng)的高可用,保證網(wǎng)站的安全穩(wěn)定。

以上討論了 3 種方式實(shí)現(xiàn)高并發(fā)業(yè)務(wù)技術(shù)的利弊,妥善規(guī)避風(fēng)險(xiǎn),同時(shí)保證系統(tǒng)的高可用和高效是值得每一位開發(fā)者思考的問(wèn)題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容