何 川
隨著互聯(lián)網(wǎng)越來(lái)越普及,我國(guó)的網(wǎng)民也不斷增加。2022年6月,我國(guó)的互聯(lián)網(wǎng)普及率甚至達(dá)到了74.4%的高峰,我國(guó)的網(wǎng)民數(shù)量在10.51億左右。隨著人們的生活質(zhì)量越來(lái)越高,人們的要求也不斷增加,互聯(lián)網(wǎng)高并發(fā)場(chǎng)景也越來(lái)越多,常見(jiàn)的就有618、雙11等各種網(wǎng)絡(luò)購(gòu)物節(jié),當(dāng)然還有各種節(jié)假日的火車(chē)票網(wǎng)上搶票等,這些都讓互聯(lián)網(wǎng)服務(wù)器產(chǎn)生了極大的壓力,每逢這些節(jié)點(diǎn),互聯(lián)網(wǎng)峰值流量就會(huì)達(dá)到甚至超過(guò)TB級(jí)[1]。如果想要讓用戶擁有更好的使用體驗(yàn),就需要讓服務(wù)器基于更好的服務(wù),可以從幾個(gè)方面一一考量,比如負(fù)載均衡、業(yè)務(wù)拆分和讓用戶在這些節(jié)點(diǎn)分流等。不過(guò)以上這些解決方法并不能解決本質(zhì)的問(wèn)題,最重要的是構(gòu)建一個(gè)可以支持大開(kāi)發(fā)業(yè)務(wù)的Web服務(wù)器。
在提升系統(tǒng)相應(yīng)速度的各項(xiàng)技術(shù)中,較為重要與關(guān)鍵的一項(xiàng)是緩存,該項(xiàng)技術(shù)能將未來(lái)可以用到的數(shù)據(jù)暫時(shí)保存下來(lái),從技術(shù)架構(gòu)設(shè)計(jì)方面來(lái)看,緩存這項(xiàng)技術(shù)屬于非功能性約束。計(jì)算機(jī)緩存在運(yùn)用的時(shí)候要求并不高,并非只有系統(tǒng)架構(gòu)的某個(gè)固定位置才能夠使用,而是在多個(gè)位置都能被運(yùn)用。緩存一般分成三大類(lèi):一是瀏覽器緩存;二是服務(wù)端緩存;三是網(wǎng)絡(luò)中的緩存。當(dāng)緩存技術(shù)被運(yùn)用到系統(tǒng)中的多個(gè)部分,系統(tǒng)的整體性能會(huì)有極大的提升。當(dāng)緩存技術(shù)被順利運(yùn)用之后,系統(tǒng)開(kāi)發(fā)的工作量將會(huì)得到降低,也會(huì)讓系統(tǒng)的并發(fā)性與吞吐量得到提升。
早在0.7.48版本里面,Nginx就已經(jīng)存在屬于自己的緩存功能,當(dāng)運(yùn)用該功能時(shí),其對(duì)應(yīng)的緩存值是Value值。在Nginx里面,其反向代理常會(huì)用到proxy_cache相關(guān)指令集,此時(shí)進(jìn)行內(nèi)容緩存時(shí),其設(shè)計(jì)不但具有可擴(kuò)展性,更具有穩(wěn)定性。它將過(guò)去運(yùn)用的多線程服務(wù)器以及多進(jìn)程服務(wù)器開(kāi)發(fā)模式徹底拋棄,而是選擇了新的開(kāi)發(fā)模式,即全異步網(wǎng)絡(luò)I/O處理機(jī)制和事件驅(qū)動(dòng)架構(gòu),這讓整個(gè)技術(shù)具有了更高的性能[2]。所以,很多知名網(wǎng)站在碰上大流量服務(wù)的時(shí)候,會(huì)更愿意運(yùn)用Nginx緩存的方式來(lái)讓用戶的體驗(yàn)得以提高。如果出現(xiàn)了低并發(fā)壓力時(shí),運(yùn)用Nginx緩存能讓使用的所有用戶擁有較快的服務(wù);如果出現(xiàn)了高并發(fā)壓力,要想讓系統(tǒng)的吞吐率得到提升,則可以運(yùn)用降低部分訪問(wèn)速度的方式來(lái)實(shí)現(xiàn)。
Redis緩存是一種分布式緩存系統(tǒng),其往往運(yùn)行的進(jìn)程是單進(jìn)程。當(dāng)運(yùn)行Redis緩存時(shí),所需要的數(shù)據(jù)都將會(huì)加載于內(nèi)存里面,所有緩存數(shù)據(jù)的操作都會(huì)在內(nèi)存里面順利完成。但把數(shù)據(jù)持久化內(nèi)存數(shù)據(jù)導(dǎo)入磁盤(pán)這一行為,系統(tǒng)也是支持的。Redis緩存和MySQL等關(guān)系型數(shù)據(jù)庫(kù)之間有著特別明顯的區(qū)別,這是因?yàn)榍罢叩腎O速度會(huì)更快,而且其內(nèi)存使用的效率也更高,除此之外,前者的吞吐量以及響應(yīng)速度都有較大的提升。在Redis里面,其具有的value擁有結(jié)構(gòu)化的特征,所以其value的數(shù)據(jù)類(lèi)型是能夠被自定義的,靈活性較大,所以說(shuō)其value的特性是極其多樣的。
隨著人們的需求越來(lái)越高,電商網(wǎng)站秒殺活動(dòng)也越來(lái)越多,僅針對(duì)這一場(chǎng)景,該課題選擇從以下幾個(gè)方面來(lái)分析高并發(fā)Web服務(wù)器系統(tǒng)的需求特點(diǎn)。首先分析連接數(shù)量方面,當(dāng)網(wǎng)站服務(wù)器屬于高并發(fā)的情況時(shí),其面對(duì)的用戶數(shù)量是極其龐大的,很多時(shí)候用戶數(shù)量會(huì)達(dá)到萬(wàn)人級(jí)別。特別是電商“秒殺”的瞬間,通過(guò)數(shù)據(jù)統(tǒng)計(jì)能得出,活動(dòng)期間的平均并發(fā)訪問(wèn)量甚至超過(guò)了百萬(wàn)QPS,所以在最初設(shè)計(jì)系統(tǒng)的時(shí)候,就要將高峰期用戶分流、負(fù)載均衡以及業(yè)務(wù)拆分等措施考慮在內(nèi)。其次是分析用戶請(qǐng)求的數(shù)據(jù)類(lèi)型,從用戶的視角看,其請(qǐng)求的商品頁(yè)面的數(shù)據(jù)其實(shí)大部分都是Json數(shù)據(jù),這會(huì)讓客戶端解析更加便利。由于用戶請(qǐng)求的內(nèi)容很多都是重復(fù)的,且請(qǐng)求的數(shù)據(jù)類(lèi)型也并不廣泛,為了讓響應(yīng)速率得到提升,可以把用戶請(qǐng)求的熱點(diǎn)數(shù)據(jù)進(jìn)行緩存。最后分析請(qǐng)求數(shù)據(jù)量的大小,很多情況下都是請(qǐng)求在客戶端將動(dòng)態(tài)Json數(shù)據(jù)進(jìn)行顯示,此時(shí)的數(shù)據(jù)大多數(shù)都是約束到KB級(jí)別以下,就算數(shù)據(jù)達(dá)到了MB級(jí)別,其通常也是運(yùn)用數(shù)據(jù)拆分合并的方式。
1.一級(jí)緩存架構(gòu)
要想在分布式系統(tǒng)里面同時(shí)將高并發(fā)性能進(jìn)行滿足,最常運(yùn)用的方式是將分布式緩存Redis引入進(jìn)去,在緩存數(shù)據(jù)庫(kù)里存儲(chǔ)熱點(diǎn)數(shù)據(jù)[3]。具體的過(guò)程如下圖1所示,主要分為三個(gè)部分:用戶表示、數(shù)據(jù)源服務(wù)以及中間件緩存服務(wù)。
圖1 請(qǐng)求查詢緩存過(guò)程
圖2 二級(jí)緩存請(qǐng)求過(guò)程
當(dāng)用戶發(fā)送了請(qǐng)求以后,服務(wù)器會(huì)率先和集中式緩存通信,此時(shí)會(huì)將用戶請(qǐng)求的相關(guān)數(shù)據(jù)在緩存集群里面進(jìn)行查找。如果在緩存集群里找到了所需要的數(shù)據(jù)信息,那么就直接返回,如果沒(méi)有在緩存集群里找到所需要的數(shù)據(jù)信息,則請(qǐng)求源數(shù)據(jù)庫(kù),且最終獲得的結(jié)果要存儲(chǔ)到緩存數(shù)據(jù)庫(kù)里。盡管運(yùn)用一級(jí)緩存的方式能夠讓數(shù)據(jù)庫(kù)的請(qǐng)求壓力得以舒緩,可如果出現(xiàn)了宕機(jī)等問(wèn)題讓Redis集群崩潰,后端數(shù)據(jù)庫(kù)就會(huì)因?yàn)槭艿酱罅空?qǐng)求沖擊而出現(xiàn)崩潰的情況,這時(shí)候系統(tǒng)災(zāi)難的爆發(fā)就在所難免。
2.二級(jí)緩存架構(gòu)
對(duì)于上面所提及的一級(jí)緩存架構(gòu)可能會(huì)出現(xiàn)的問(wèn)題,為更有效地解決這些問(wèn)題,將Ehcache引入Tomcat服務(wù)器中,以構(gòu)建第二級(jí)緩存。對(duì)于那些熱點(diǎn)商品來(lái)說(shuō),其被用戶訪問(wèn)的數(shù)量是很大的,假如所有的用戶在獲取相應(yīng)信息時(shí)都要用到Tomcat服務(wù)器去連接,大量的訪問(wèn)量一級(jí)并發(fā)量會(huì)讓Redis緩存服務(wù)器上的負(fù)載壓力大幅增加,同時(shí)也會(huì)出現(xiàn)網(wǎng)絡(luò)寬帶受到限制等問(wèn)題,不僅使得系統(tǒng)的吞吐量下降,也加長(zhǎng)了服務(wù)端的響應(yīng)耗時(shí)。對(duì)于以上所提及的問(wèn)題,可以通過(guò)配置一主多從的服務(wù)器集群架構(gòu)來(lái)解決[4],這樣客戶端的請(qǐng)求就能夠均分到不同的服務(wù)器節(jié)點(diǎn)上,不會(huì)造成同一個(gè)服務(wù)器節(jié)點(diǎn)出現(xiàn)極大訪問(wèn)量的情況。除了這種方式外,還有一種更好的方式,即運(yùn)用服務(wù)器本地平臺(tái),把熱點(diǎn)數(shù)據(jù)存儲(chǔ)成緩存的狀態(tài),這樣就能將遠(yuǎn)程緩存的網(wǎng)絡(luò)開(kāi)銷(xiāo)訪問(wèn)大大減少。
將以上所提及的Redis分布式緩存以及Ehcache二級(jí)緩存架構(gòu)進(jìn)行結(jié)合之后,緩存數(shù)據(jù)的傳輸開(kāi)銷(xiāo)以及網(wǎng)絡(luò)連接都會(huì)得以降低。即使是前者在緩存的過(guò)程中有故障發(fā)生,后者也依舊能夠通過(guò)本地緩存的方式繼續(xù)提供緩存服務(wù)。這不但能將系統(tǒng)發(fā)生緩存穿透以及緩存雪崩情況下的負(fù)面影響有所降低,也讓系統(tǒng)的高可用性以及健壯性得到提升。
3.多級(jí)緩存架構(gòu)
當(dāng)運(yùn)用多級(jí)緩存架構(gòu)時(shí),也就是將所有的數(shù)據(jù)緩存在不一樣的系統(tǒng)組件里面,利用組件里緩存的協(xié)同合作能讓系統(tǒng)擁有更好的高并發(fā)性以及高可用性。當(dāng)使用多級(jí)緩存時(shí),在最初的緩存查找中,需要運(yùn)用到Nginx和Lua腳本一起結(jié)合的方式在代理服務(wù)器里面查找。假如能夠直接在緩存中找到數(shù)據(jù),那就直接返回,假如沒(méi)有辦法找到數(shù)據(jù),則只能先去Tomcat服務(wù)器尋找,如果還找不到數(shù)據(jù),則要在Redis分布式集群里面尋找[5]。假如通過(guò)上面這些尋找途徑還是沒(méi)有辦法找到數(shù)據(jù),最終的辦法就是請(qǐng)求關(guān)系型數(shù)據(jù)庫(kù),當(dāng)找到所需要的請(qǐng)求數(shù)據(jù)后,將其緩存在所有的組件里面。具體的請(qǐng)求過(guò)程如下圖3所示。
圖3 多級(jí)緩存請(qǐng)求過(guò)程
4.數(shù)據(jù)一致性
所謂數(shù)據(jù)一致性,其實(shí)就是在系統(tǒng)運(yùn)行期間,處于源數(shù)據(jù)庫(kù)里面的數(shù)據(jù)和緩存里面的數(shù)據(jù)應(yīng)當(dāng)始終保持一致性。因?yàn)橛芯彺孢@一技術(shù),很多的內(nèi)容副本在網(wǎng)絡(luò)的很多地方都有所分散。假如源服務(wù)器里面的內(nèi)容出現(xiàn)了變化,那些存儲(chǔ)在其他網(wǎng)絡(luò)各處的緩存副本將會(huì)自動(dòng)失去效果。確保緩存數(shù)據(jù)和源數(shù)據(jù)庫(kù)的數(shù)據(jù)一致,進(jìn)而確保用戶最終緩存得到的數(shù)據(jù)是有效的數(shù)據(jù)。
詳細(xì)的流程如下所示:
(1)先將緩存下來(lái)的請(qǐng)求數(shù)據(jù)進(jìn)行刪除。
(2)等緩存里面的請(qǐng)求數(shù)據(jù)被徹底刪除后,再將處于源數(shù)據(jù)庫(kù)里面的數(shù)據(jù)進(jìn)行自動(dòng)更新。假如在對(duì)源數(shù)據(jù)庫(kù)里面的數(shù)據(jù)更新失敗了,就需要立即寫(xiě)請(qǐng)求失敗而返回,這個(gè)時(shí)候的數(shù)據(jù)并不會(huì)發(fā)生任何變化[6]。這個(gè)時(shí)候會(huì)發(fā)現(xiàn)緩存里面并不存在與之對(duì)應(yīng)的請(qǐng)求數(shù)據(jù),那么請(qǐng)求數(shù)據(jù)會(huì)直接從源數(shù)據(jù)庫(kù)里面讀取。同一時(shí)間該請(qǐng)求數(shù)據(jù)也會(huì)被放入緩存里面,這時(shí)候兩邊的數(shù)據(jù)是一致的,自然不會(huì)有數(shù)據(jù)一致性的問(wèn)題發(fā)生。
(3)當(dāng)源數(shù)據(jù)庫(kù)里面的請(qǐng)求數(shù)據(jù)得到了更新,就需要對(duì)緩存里的數(shù)據(jù)進(jìn)行更新。如果對(duì)緩存里面的數(shù)據(jù)進(jìn)行更新出現(xiàn)了失敗的情況,則立即返回。這時(shí)候數(shù)據(jù)庫(kù)里面的數(shù)據(jù)已經(jīng)更新過(guò),當(dāng)讀請(qǐng)求傳送過(guò)來(lái)之后,在緩存里面并未找到相應(yīng)的數(shù)據(jù)時(shí),就要從數(shù)據(jù)庫(kù)里面將對(duì)應(yīng)的數(shù)據(jù)讀取下來(lái),當(dāng)然這個(gè)請(qǐng)求數(shù)據(jù)將會(huì)直接存入緩存里面。這時(shí)候兩邊的數(shù)據(jù)是一致的,也不會(huì)有數(shù)據(jù)一致性的問(wèn)題發(fā)生。
(4)等以上的步驟全部成功之后,不管是源數(shù)據(jù)庫(kù)里面的數(shù)據(jù)還是緩存里面的數(shù)據(jù)都是一致的,均不會(huì)產(chǎn)生數(shù)據(jù)一致性的問(wèn)題。
當(dāng)處于高并發(fā)Web場(chǎng)景時(shí),服務(wù)器會(huì)有較長(zhǎng)的讀請(qǐng)求響應(yīng)時(shí)間,從而導(dǎo)致用戶沒(méi)有很好的使用體驗(yàn)。將分布式緩存引入其中,盡管存在不少優(yōu)勢(shì),可當(dāng)運(yùn)用到分布式應(yīng)用里面時(shí),用戶的所有請(qǐng)求都會(huì)讓緩存服務(wù)器和應(yīng)用服務(wù)器之間產(chǎn)生網(wǎng)絡(luò)連接,因?yàn)槎喾N因素的影響,其性能優(yōu)化將會(huì)被網(wǎng)絡(luò)時(shí)間開(kāi)銷(xiāo)所抵消。不僅如此,還會(huì)出現(xiàn)源數(shù)據(jù)庫(kù)和緩存一致性的問(wèn)題。所以該課題主要是針對(duì)單一緩存的局限性來(lái)構(gòu)建多級(jí)緩存策略(基于Nginx本地緩存與應(yīng)用緩存之上的),其目的是讓緩存的性能得到提升,同時(shí)減少讀請(qǐng)求響應(yīng)時(shí)間,最終讓用戶得到更好的使用體驗(yàn),