關(guān)于大型網(wǎng)站技術(shù)演進(jìn)的思考:存儲(chǔ)的瓶頸(1-3)
前不久公司請(qǐng)來(lái)了位互聯(lián)網(wǎng)界的技術(shù)大牛跟我們做了一次大型網(wǎng)站架構(gòu)的培訓(xùn),兩天12個(gè)小時(shí)信息量非常大,知識(shí)的廣度和難度也非常大,培訓(xùn)完后我很難完整理出全部聽(tīng)到的知識(shí),今天我換了個(gè)思路是回味這次培訓(xùn),這個(gè)思路就是通過(guò)本人目前的經(jīng)驗(yàn)和技術(shù)水平來(lái)思考下大型網(wǎng)站技術(shù)演進(jìn)的過(guò)程。
首先我們要思考一個(gè)問(wèn)題,什么樣的網(wǎng)站才是大型網(wǎng)站,從網(wǎng)站的技術(shù)指標(biāo)角度考慮這個(gè)問(wèn)題人們很容易犯一個(gè)毛病就是認(rèn)為網(wǎng)站的訪問(wèn)量是衡量的指標(biāo),懂點(diǎn)行的人也許會(huì)認(rèn)為是網(wǎng)站在單位時(shí)間里的并發(fā)量的大小來(lái)作為指標(biāo),如果按這些標(biāo)準(zhǔn)那么像hao123這樣的網(wǎng)站就是大型網(wǎng)站了,如下圖所示:
其實(shí)這種網(wǎng)站訪問(wèn)量非常大,并發(fā)數(shù)也非常高,但是它卻能用最為簡(jiǎn)單的web技術(shù)來(lái)實(shí)現(xiàn):我們只要保持網(wǎng)站的充分的靜態(tài)化,多部署幾臺(tái)服務(wù)器,那么就算地球上所有人都用它,網(wǎng)站也能正常運(yùn)行。
我覺(jué)得大型網(wǎng)站是技術(shù)和業(yè)務(wù)的結(jié)合,一個(gè)滿足某些用戶需求的網(wǎng)站只要技術(shù)和業(yè)務(wù)二者有一方難度很大,必然會(huì)讓企業(yè)投入更多的、更優(yōu)秀的人力成本實(shí)現(xiàn)它,那么這樣的網(wǎng)站就是所謂的大型網(wǎng)站了。
一個(gè)初建的網(wǎng)站往往用戶群都是很小的,最簡(jiǎn)單的網(wǎng)站架構(gòu)就能解決實(shí)際的用戶需求,當(dāng)然為了保證網(wǎng)站的穩(wěn)定性和安全性,我們會(huì)把網(wǎng)站的應(yīng)用部署到至少兩臺(tái)機(jī)器上,后臺(tái)的存儲(chǔ)使用數(shù)據(jù)庫(kù),如果經(jīng)濟(jì)實(shí)力允許,數(shù)據(jù)庫(kù)使用單臺(tái)服務(wù)器部署,由于數(shù)據(jù)是網(wǎng)站的生命線,因此我們常常會(huì)把部署數(shù)據(jù)庫(kù)的服務(wù)器使用的好點(diǎn),這個(gè)網(wǎng)站結(jié)構(gòu)如下所示:
這個(gè)結(jié)構(gòu)非常簡(jiǎn)單,其實(shí)大部分初建網(wǎng)站開(kāi)發(fā)里往往業(yè)務(wù)邏輯沒(méi)有企業(yè)級(jí)系統(tǒng)那么復(fù)雜,所以只要有個(gè)好的idea,建設(shè)一個(gè)新網(wǎng)站的成本是非常低的,所使用的技術(shù)手段也是非常的基本和簡(jiǎn)單,不過(guò)該圖我們要準(zhǔn)備三臺(tái)服務(wù)器,而且還要租個(gè)機(jī)房放置我們的服務(wù)器,這些成本對(duì)于草根和屌絲還是非常高的,幸運(yùn)的是當(dāng)下很多大公司和機(jī)構(gòu)提供了云平臺(tái),我們可以花費(fèi)很少的錢(qián)將自己的應(yīng)用部署到云平臺(tái)上,這種做法我們甚至不用去考慮把應(yīng)用、數(shù)據(jù)庫(kù)分開(kāi)部署的問(wèn)題,更加進(jìn)一步的降低了網(wǎng)站開(kāi)發(fā)和運(yùn)維的成本,但是這種做法也有一個(gè)問(wèn)題,就是網(wǎng)站的小命被這個(gè)云平臺(tái)捏住了,如果云平臺(tái)掛了,俺們的網(wǎng)站服務(wù)也就跟著掛了。
這里我先講講自己獨(dú)立使用服務(wù)器部署網(wǎng)站的問(wèn)題,如果我們要把網(wǎng)站服務(wù)應(yīng)用使用多臺(tái)服務(wù)器部署,這么做的目的一般有兩個(gè):
- 保證網(wǎng)站的可用性,多臺(tái)服務(wù)器部署應(yīng)用,那么其中一些服務(wù)器掛掉了,只要網(wǎng)站還有服務(wù)器能正常運(yùn)轉(zhuǎn),那么網(wǎng)站對(duì)外任然可以正常提供服務(wù)。
- 提高網(wǎng)站的并發(fā)量,服務(wù)器越多那么網(wǎng)站能夠服務(wù)的用戶,單位時(shí)間內(nèi)能承載的請(qǐng)求數(shù)也就越大。
不過(guò)要做到以上兩點(diǎn),并不是我們簡(jiǎn)單將網(wǎng)站分開(kāi)部署就可以滿足的,因?yàn)榇蠖鄶?shù)網(wǎng)站在用戶使用時(shí)候都是要保持用戶的狀態(tài),具體點(diǎn)就是網(wǎng)站要記住請(qǐng)求是歸屬到那一個(gè)客戶端,而這個(gè)狀態(tài)在網(wǎng)站開(kāi)發(fā)里就是通過(guò)會(huì)話session來(lái)體現(xiàn)的。分開(kāi)部署的web應(yīng)用服務(wù)要解決的一個(gè)首要問(wèn)題就是要保持不同物理部署服務(wù)器之間的session同步問(wèn)題,從而達(dá)到當(dāng)用戶第一次請(qǐng)求訪問(wèn)到服務(wù)器A,第二個(gè)請(qǐng)求訪問(wèn)到服務(wù)器B,網(wǎng)站任然知道這兩個(gè)請(qǐng)求是同一個(gè)人,解決方案很直接:服務(wù)器A和服務(wù)器B上的session信息要時(shí)刻保持同步,那么如何保證兩臺(tái)服務(wù)器之間session信息的同步呢?
為了回答上面的問(wèn)題,我們首先要理解下session的機(jī)制,session信息在web容器里都是存儲(chǔ)在內(nèi)存里的,web容器會(huì)給每個(gè)連接它的客戶端生成一個(gè)sessionid值,這個(gè)sessionid值會(huì)被web容器置于http協(xié)議里的cookie域下,當(dāng)響應(yīng)被客戶端處理后,客戶端本地會(huì)存儲(chǔ)這個(gè)sessionid值,用戶以后的每個(gè)請(qǐng)求都會(huì)讓這個(gè)sessionid值隨cookie一起傳遞到服務(wù)器,服務(wù)器通過(guò)sessionid找到內(nèi)存中存儲(chǔ)的該用戶的session內(nèi)容,session在內(nèi)存的數(shù)據(jù)結(jié)構(gòu)是一個(gè)map的格式。那么為了保證不同服務(wù)器之間的session共享,那么最直接的方案就是讓服務(wù)器之間session不斷的傳遞和復(fù)制,例如java開(kāi)發(fā)里常用的tomcat容器就采用這種方案,以前我測(cè)試過(guò)tomcat這種session同步的性能,我發(fā)現(xiàn)當(dāng)需要同步的web容器越多,web應(yīng)用所能承載的并發(fā)數(shù)并沒(méi)有因?yàn)榉?wù)器的增加而線性提升,當(dāng)服務(wù)器數(shù)量達(dá)到一個(gè)臨界值后,整個(gè)web應(yīng)用的并發(fā)數(shù)甚至還會(huì)下降,為什么會(huì)這樣了?
原因很簡(jiǎn)單,不同服務(wù)器之間session的傳遞和復(fù)制會(huì)消耗服務(wù)器本身的系統(tǒng)資源,當(dāng)服務(wù)器數(shù)量越大,消耗的資源越多,當(dāng)用戶請(qǐng)求越頻繁,系統(tǒng)消耗資源也會(huì)越來(lái)越大。如果我們多部署服務(wù)器的目的只是想保證系統(tǒng)的穩(wěn)定性,采用這種方案還是不錯(cuò)的,不過(guò)web應(yīng)用最好部署少點(diǎn),這樣才不會(huì)影響到web應(yīng)用的性能問(wèn)題,如果我們還想提升網(wǎng)站的并發(fā)量那么就得采取其他的方案了。
時(shí)下使用的比較多的方案就是使用獨(dú)立的緩存服務(wù)器,也就是將session的數(shù)據(jù)存儲(chǔ)在一臺(tái)獨(dú)立的服務(wù)器上,如果覺(jué)得存在一臺(tái)服務(wù)器不安全,那么可以使用memcached這樣的分布式緩存服務(wù)器進(jìn)行存儲(chǔ),這樣既可以滿足了網(wǎng)站穩(wěn)定性問(wèn)題也提升了網(wǎng)站的并發(fā)能力。
不過(guò)早期的淘寶在這個(gè)問(wèn)題解決更加巧妙,他們將session的信息直接存儲(chǔ)到瀏覽器的cookie里,每次請(qǐng)求cookie信息都會(huì)隨著http一起傳遞到web服務(wù)器,這樣就避免了web服務(wù)器之間session信息同步的問(wèn)題,這種方案會(huì)讓很多人詬病,詬病的原因是cookie的不安全性是總所周知的,如果有人惡意截取cookie信息那么網(wǎng)站不就不安全了嗎?這個(gè)答案還真不好說(shuō),但是我覺(jué)得我們僅僅是跟蹤用戶的狀態(tài),把session存在cookie里其實(shí)也沒(méi)什么大不了的。
其實(shí)如此專業(yè)的淘寶這么做其實(shí)還是很有深意的,還記得本文開(kāi)篇提到的hao123網(wǎng)站,它是可以承載高并發(fā)的網(wǎng)站,它之所以可以做到這一點(diǎn),原因很簡(jiǎn)單它是個(gè)靜態(tài)網(wǎng)站,靜態(tài)網(wǎng)站的特點(diǎn)就是不需要記錄用戶的狀態(tài),靜態(tài)網(wǎng)站的服務(wù)器不需要使用寶貴的系統(tǒng)資源來(lái)存儲(chǔ)大量的session會(huì)話信息,這樣它就有更多系統(tǒng)資源來(lái)處理請(qǐng)求,而早期淘寶將cookie存在客戶端也是為了達(dá)到這樣的目的,所以這個(gè)方案在淘寶網(wǎng)站架構(gòu)里還是使用了很長(zhǎng)時(shí)間的。
在我的公司里客戶端的請(qǐng)求到達(dá)web服務(wù)器之前,會(huì)先到F5,F(xiàn)5是一個(gè)用來(lái)做負(fù)載均衡的硬件設(shè)備,它的作用是將用戶請(qǐng)求均勻的分發(fā)到后臺(tái)的服務(wù)器集群,F(xiàn)5是硬件的負(fù)載均衡解決方案,如果我們沒(méi)那么多錢(qián)買(mǎi)這樣的設(shè)備,也有軟件的負(fù)載均衡解決方案,這個(gè)方案就是大名鼎鼎的LVS了,這些負(fù)載均衡設(shè)備除了可以分發(fā)請(qǐng)求外它們還有個(gè)能力,這個(gè)能力是根據(jù)http協(xié)議的特點(diǎn)設(shè)計(jì)的,一個(gè)http請(qǐng)求從客戶端到達(dá)最終的存儲(chǔ)服務(wù)器之前可能會(huì)經(jīng)過(guò)很多不同的設(shè)備,如果我們把一個(gè)請(qǐng)求比作高速公路上的一輛汽車(chē),這些設(shè)備也可以叫做這些節(jié)點(diǎn)就是高速路上的收費(fèi)站,這些收費(fèi)站都能根據(jù)自己的需求改變http報(bào)文的內(nèi)容,所以負(fù)載均衡設(shè)備可以記住每個(gè)sessionid值對(duì)應(yīng)的后臺(tái)服務(wù)器,當(dāng)一個(gè)帶有sessionid值的請(qǐng)求通過(guò)負(fù)載均衡設(shè)備時(shí)候,負(fù)載均衡設(shè)備會(huì)根據(jù)該sessionid值直接找到指定的web服務(wù)器,這種做法有個(gè)專有名詞就是session粘滯,這種做法也比那種session信息在不同服務(wù)器之間拷貝復(fù)制要高效,不過(guò)該做法還是比存cookie的效率低下,而且對(duì)于網(wǎng)站的穩(wěn)定性也有一定影響即如果某臺(tái)服務(wù)器掛掉了,那么連接到該服務(wù)器的用戶的會(huì)話都會(huì)失效。
解決session的問(wèn)題的本質(zhì)也就是解決session的存儲(chǔ)問(wèn)題,其本質(zhì)也就是解決網(wǎng)站的存儲(chǔ)問(wèn)題,一個(gè)初建的網(wǎng)站在早期的運(yùn)營(yíng)期需要解決的問(wèn)題基本都是由存儲(chǔ)導(dǎo)致的。上文里我提到時(shí)下很多新建的web應(yīng)用會(huì)將服務(wù)器部署后云平臺(tái)里,好的云平臺(tái)里或許會(huì)幫助我們解決負(fù)載均衡和session同步的問(wèn)題,但是云平臺(tái)里有個(gè)問(wèn)題很難解決那就是數(shù)據(jù)庫(kù)的存儲(chǔ)問(wèn)題,如果我們使用的云平臺(tái)發(fā)生了重大事故,導(dǎo)致云平臺(tái)存儲(chǔ)的數(shù)據(jù)丟失,這種會(huì)不會(huì)導(dǎo)致我們?cè)谠破脚_(tái)里數(shù)據(jù)庫(kù)的信息也會(huì)丟失了,雖然這個(gè)事情的概率不高,但是發(fā)生這種事情的幾率還是有的,雖然很多云平臺(tái)都聲稱自己多么可靠,但是真實(shí)可靠性有多高不是局中人還真不清楚哦,因此使用云平臺(tái)我們首要考慮的就是要做好數(shù)據(jù)備份,假如真發(fā)生了數(shù)據(jù)丟失,對(duì)于一個(gè)快速成長(zhǎng)的網(wǎng)站而言可能非常致命。
寫(xiě)到這里一個(gè)嬰兒般的網(wǎng)站就這樣被我們創(chuàng)造出來(lái)了,我們希望網(wǎng)站能健康快速的成長(zhǎng),如果網(wǎng)站真的按我們預(yù)期成長(zhǎng)了,那么一定會(huì)有一天我們制造的寶寶屋已經(jīng)滿足不了現(xiàn)實(shí)的需求,這個(gè)時(shí)候我們應(yīng)該如何抉擇了?換掉,全部換掉,使用新的架構(gòu)例如我們以前長(zhǎng)提的SOA架構(gòu),分布式技術(shù),這個(gè)方法不錯(cuò),但是SOA和分布式技術(shù)是很難的,成本是很高的,如果這時(shí)候我們通過(guò)添加幾臺(tái)服務(wù)器就能解決問(wèn)題的話,我們絕對(duì)不要去選擇什么分布式技術(shù),因?yàn)檫@個(gè)成本太高了。上面我講到幾種session共享的方案,這個(gè)方案解決了應(yīng)用的水平擴(kuò)展問(wèn)題,那么當(dāng)我們網(wǎng)站出現(xiàn)瓶頸時(shí)候就多加幾臺(tái)服務(wù)器不就行了嗎?那么這里就有個(gè)問(wèn)題了,當(dāng)網(wǎng)站成長(zhǎng)很快,網(wǎng)站首先碰到的瓶頸到底是哪個(gè)方面的問(wèn)題?
本人是做金融網(wǎng)站的,我們所做的網(wǎng)站有個(gè)特點(diǎn)就是當(dāng)用戶訪問(wèn)到我們所做的網(wǎng)站時(shí)候,目的都很明確就是為了付錢(qián),用戶到了我們所做的網(wǎng)站時(shí)候都希望能快點(diǎn),再快點(diǎn)完成本網(wǎng)站的操作,很多用戶在使用我們做的網(wǎng)站時(shí)候不太去關(guān)心網(wǎng)站的其他內(nèi)容,因此我們所做的網(wǎng)站相對(duì)于數(shù)據(jù)庫(kù)而言就是讀寫(xiě)比例其實(shí)非常的均勻,甚至很多場(chǎng)景寫(xiě)比讀要高,這個(gè)特點(diǎn)是很多專業(yè)服務(wù)網(wǎng)站的特點(diǎn),其實(shí)這樣的網(wǎng)站和企業(yè)開(kāi)發(fā)的特點(diǎn)很類似:業(yè)務(wù)操作的重要度超過(guò)了業(yè)務(wù)展示的重要度,因此專業(yè)性網(wǎng)站吸納企業(yè)系統(tǒng)開(kāi)發(fā)的特點(diǎn)比較多。但是大部分我們?nèi)粘3S玫木W(wǎng)站,我們逗留時(shí)間很長(zhǎng)的網(wǎng)站按數(shù)據(jù)庫(kù)角度而言往往是讀遠(yuǎn)遠(yuǎn)大于寫(xiě),例如大眾點(diǎn)評(píng)網(wǎng)站它的讀寫(xiě)比率往往是9比1。
12306或許是中國(guó)最著名的網(wǎng)站之一,我記得12306早期經(jīng)常出現(xiàn)一個(gè)問(wèn)題就是用戶登錄老是登不上,甚至在高峰期整個(gè)網(wǎng)站掛掉,頁(yè)面顯示503網(wǎng)站拒絕訪問(wèn)的問(wèn)題,這個(gè)現(xiàn)象很好理解就是網(wǎng)站并發(fā)高了,大量人去登錄網(wǎng)站,購(gòu)票,系統(tǒng)掛掉了,最后所有的人都不能使用網(wǎng)站了。當(dāng)網(wǎng)站出現(xiàn)503拒絕訪問(wèn)時(shí)候,那么這個(gè)網(wǎng)站就出現(xiàn)了最致命的問(wèn)題,解決大用戶訪問(wèn)的確是個(gè)超級(jí)難題,但是當(dāng)高并發(fā)無(wú)法避免時(shí)候,整個(gè)網(wǎng)站都不能使用這個(gè)只能說(shuō)網(wǎng)站設(shè)計(jì)上發(fā)生了致命錯(cuò)誤,一個(gè)好的網(wǎng)站設(shè)計(jì)在應(yīng)對(duì)超出自己能力的并發(fā)時(shí)候我們首先應(yīng)該是不讓他掛掉,因?yàn)檫@種結(jié)果是誰(shuí)都不能使用,我們希望那些在可接受的請(qǐng)求下,讓在可接受請(qǐng)求范圍內(nèi)的請(qǐng)求還是可以正常使用,超出的請(qǐng)求可以被拒絕,但是它們絕對(duì)不能影響到全網(wǎng)站的穩(wěn)定性,現(xiàn)在我們看到了12306網(wǎng)站的峰值從未減少過(guò),而且是越變?cè)蕉?,但?2306出現(xiàn)全站掛掉的問(wèn)題是越來(lái)越少了。通過(guò)12036網(wǎng)站改變我們更進(jìn)一步思考下網(wǎng)站的瓶頸問(wèn)題。
排除一些不可控的因素,網(wǎng)站在高并發(fā)下掛掉的原因90%都是因?yàn)閿?shù)據(jù)庫(kù)不堪重負(fù)所致,而應(yīng)用的瓶頸往往只有在解決了存儲(chǔ)瓶頸后才會(huì)暴露,那么我們要升級(jí)網(wǎng)站能力的第一步工作就是提升數(shù)據(jù)庫(kù)的承載能力,對(duì)于讀遠(yuǎn)大于寫(xiě)的網(wǎng)站我們采取的方式就是將數(shù)據(jù)庫(kù)從讀寫(xiě)這個(gè)角度拆分,具體操作就是將數(shù)據(jù)庫(kù)讀寫(xiě)分離,如下圖所示:
我們這時(shí)要設(shè)計(jì)兩個(gè)數(shù)據(jù)庫(kù),一個(gè)數(shù)據(jù)庫(kù)主要負(fù)責(zé)寫(xiě)操作我們稱之為主庫(kù),一個(gè)數(shù)據(jù)庫(kù)專門(mén)負(fù)責(zé)讀操作我們稱之為副庫(kù),副庫(kù)的數(shù)據(jù)都是從主庫(kù)導(dǎo)入的,數(shù)據(jù)庫(kù)的讀寫(xiě)分離可以有效的保證關(guān)鍵數(shù)據(jù)的安全性,但是有個(gè)缺點(diǎn)就是當(dāng)用戶瀏覽數(shù)據(jù)時(shí)候,讀的數(shù)據(jù)都會(huì)有點(diǎn)延時(shí),這種延時(shí)比起全站不可用那肯定是可以接受的。不過(guò)針對(duì)12306的場(chǎng)景,僅僅讀寫(xiě)分離還是遠(yuǎn)遠(yuǎn)不夠的,特別是負(fù)責(zé)讀操作的副庫(kù),在高訪問(wèn)下也是很容易達(dá)到性能的瓶頸的,那么我們就得使用新的解決方案:使用分布式緩存,不過(guò)緩存的缺點(diǎn)就是不能有效的實(shí)時(shí)更新,因此我們使用緩存前首先要對(duì)讀操作的數(shù)據(jù)進(jìn)行分類,對(duì)于那些經(jīng)常不發(fā)生變化的數(shù)據(jù)可以事先存放到緩存里,緩存的訪問(wèn)效率很高,這樣會(huì)讓讀更加高效,同時(shí)也減輕了數(shù)據(jù)庫(kù)的訪問(wèn)壓力。至于用于寫(xiě)操作的主庫(kù),因?yàn)榇蟛糠志W(wǎng)站讀寫(xiě)的比例是嚴(yán)重失衡,所以讓主庫(kù)達(dá)到瓶頸還是比較難的,不過(guò)主庫(kù)也有一個(gè)讀的壓力就是主庫(kù)和副庫(kù)的數(shù)據(jù)同步問(wèn)題,不過(guò)同步時(shí)候數(shù)據(jù)都是批量操作,而不是像請(qǐng)求那樣進(jìn)行少量數(shù)據(jù)讀取操作,讀取操作特別多,因此想達(dá)到瓶頸還是有一定的難度的。聽(tīng)人說(shuō),美國(guó)牛逼的facebook對(duì)數(shù)據(jù)的任何操作都是事先合并為批量操作,從而達(dá)到減輕數(shù)據(jù)庫(kù)壓力的目的。
上面的方案我們可以保證在高并發(fā)下網(wǎng)站的穩(wěn)定性,但是針對(duì)于讀,如果數(shù)據(jù)量太大了,就算網(wǎng)站不掛掉了,用戶能很快的在海量數(shù)據(jù)里檢索到所需要的信息又成為了網(wǎng)站的一個(gè)瓶頸,如果用戶需要很長(zhǎng)時(shí)間才能獲得自己想要的數(shù)據(jù),很多用戶會(huì)失去耐心從而放棄對(duì)網(wǎng)站的使用,那么這個(gè)問(wèn)題又該如何解決了?
解決方案就是我們經(jīng)常使用的百度,谷歌哪里得來(lái),對(duì)于海量數(shù)據(jù)的讀我們可以采用搜索技術(shù),我們可以將數(shù)據(jù)庫(kù)的數(shù)據(jù)導(dǎo)出到文件里,對(duì)文件建立索引,使用倒排索引技術(shù)來(lái)檢索信息,我們看到了百度,谷歌有整個(gè)互聯(lián)網(wǎng)的信息我們?nèi)稳荒芎芸斓臋z索到數(shù)據(jù),搜索技術(shù)是解決快速讀取數(shù)據(jù)的一個(gè)有效方案,不過(guò)這個(gè)讀取還是和數(shù)據(jù)庫(kù)的讀取有所區(qū)別的,如果用戶查詢的數(shù)據(jù)是通過(guò)數(shù)據(jù)庫(kù)的主鍵字段,或者是通過(guò)很明確的建立了索引的字段來(lái)檢索,那么數(shù)據(jù)庫(kù)的查詢效率是很高的,但是使用網(wǎng)站的人跟喜歡使用一些模糊查詢來(lái)查找自己的信息,那么這個(gè)操作在數(shù)據(jù)庫(kù)里就是個(gè)like操作,like操作在數(shù)據(jù)庫(kù)里效率是很低的,這個(gè)時(shí)候使用搜索技術(shù)的優(yōu)勢(shì)就非常明顯了,搜索技術(shù)非常適合于模糊查詢操作。
OK,很晚了,關(guān)于存儲(chǔ)的問(wèn)題今天就寫(xiě)在這里,下一篇我將接著這個(gè)主題講解,解決存儲(chǔ)問(wèn)題是很復(fù)雜的,下篇我盡量講仔細(xì)點(diǎn)。
#p#
上篇里我講到某些網(wǎng)站在高并發(fā)下會(huì)報(bào)出503錯(cuò)誤,503錯(cuò)誤的含義是指網(wǎng)站服務(wù)端暫時(shí)無(wú)法提供服務(wù)的含義,503還表達(dá)了網(wǎng)站服務(wù)端現(xiàn)在有問(wèn)題但是以后可能會(huì)提供正常的服務(wù),對(duì)http協(xié)議熟悉的人都知道,5開(kāi)頭的響應(yīng)碼表達(dá)了服務(wù)端出現(xiàn)了問(wèn)題,在我們開(kāi)發(fā)測(cè)試時(shí)候最為常見(jiàn)的是500錯(cuò)誤,500代表的含義是服務(wù)端程序出現(xiàn)了錯(cuò)誤導(dǎo)致網(wǎng)站無(wú)法正常提供服務(wù),500通常是服務(wù)端異常和錯(cuò)誤所致,如果生產(chǎn)系統(tǒng)里發(fā)現(xiàn)了500錯(cuò)誤,那么只能說(shuō)明網(wǎng)站存在邏輯性的錯(cuò)誤,這往往是系統(tǒng)上線前的測(cè)試做的不到位所致?;氐?03錯(cuò)誤,我上文解釋為拒絕訪問(wèn),其實(shí)更加準(zhǔn)確的回答應(yīng)該是服務(wù)不可用,那么為什么我會(huì)說(shuō)503錯(cuò)誤在高并發(fā)的情況下90%的原因是數(shù)據(jù)庫(kù)所致呢?上文我做出了詳細(xì)的解釋,但是今天我回味了一下,發(fā)現(xiàn)那個(gè)解釋還不是太突出重點(diǎn),問(wèn)題的重點(diǎn)是在高并發(fā)的情況整個(gè)網(wǎng)站系統(tǒng)首先暴露出問(wèn)題的是數(shù)據(jù)庫(kù),如果我們把整個(gè)網(wǎng)站系統(tǒng)比作一個(gè)盛水的木桶,那么木桶最短的那個(gè)板就是數(shù)據(jù)庫(kù)了,一般而言網(wǎng)站的服務(wù)應(yīng)用出問(wèn)題都會(huì)是解決存儲(chǔ)問(wèn)題之后才會(huì)出現(xiàn)。
數(shù)據(jù)庫(kù)出現(xiàn)了瓶頸并不是程序存在邏輯性錯(cuò)誤,數(shù)據(jù)庫(kù)瓶頸的表現(xiàn)就是數(shù)據(jù)庫(kù)因?yàn)槌惺芰颂嗟脑L問(wèn)后,數(shù)據(jù)庫(kù)無(wú)法迅速的做出響應(yīng),嚴(yán)重時(shí)候數(shù)據(jù)庫(kù)會(huì)拒絕進(jìn)一步操作死鎖在哪里不能做出任何反應(yīng)。數(shù)據(jù)庫(kù)猶如一把巨型的大鎖,很多人爭(zhēng)搶這個(gè)鎖時(shí)候會(huì)導(dǎo)致這個(gè)大鎖完全被鎖死,最終請(qǐng)求的處理就停留在這個(gè)大鎖上最終導(dǎo)致網(wǎng)站提示出503錯(cuò)誤,503錯(cuò)誤最終會(huì)傳遞到所有的客戶端上,最終的現(xiàn)象就是全站不可用了。
上文里我講到session共享的一個(gè)方案是將session數(shù)據(jù)存儲(chǔ)在外部一個(gè)獨(dú)立的緩存服務(wù)器里,我開(kāi)始說(shuō)用一臺(tái)服務(wù)器做緩存服務(wù)器,后面提到如果覺(jué)得一臺(tái)服務(wù)器做緩存不安全,那么采用分布式緩存服務(wù)器例如memcached,那么這里就有一個(gè)問(wèn)題了,為了保證web服務(wù)的可用性,我們會(huì)把web服務(wù)分開(kāi)部署到不同的服務(wù)器上,這些服務(wù)器都是對(duì)等關(guān)系,其中一臺(tái)服務(wù)器不能正常提供服務(wù)不會(huì)影響到整個(gè)網(wǎng)站的穩(wěn)定性,那么我們采取memcached集群是不是可以達(dá)到同樣的效果了?即緩存服務(wù)器集群中一臺(tái)服務(wù)器掛掉,不會(huì)影響到用戶對(duì)網(wǎng)站的使用了?問(wèn)題的答案是令人失望了,假如我們使用兩臺(tái)服務(wù)器做緩存服務(wù)器來(lái)存儲(chǔ)session信息,那么如果其中一臺(tái)服務(wù)器掛掉了,那么網(wǎng)站將會(huì)有一半的用戶將不能正常使用網(wǎng)站,原因是他們的session信息丟失了,網(wǎng)站無(wú)法正常的跟蹤用戶的會(huì)話狀態(tài)。我之所以提到這個(gè)問(wèn)題是想告訴大家以memcached為代表的分布式緩存和我們傳統(tǒng)理解的分布式系統(tǒng)是有區(qū)別的,傳統(tǒng)的分布式系統(tǒng)都會(huì)包含一個(gè)容災(zāi)維護(hù)系統(tǒng)穩(wěn)定性的功能,但實(shí)際的分布式技術(shù)是多種多樣的,例如memcached的分布式技術(shù)并不是為了解決容災(zāi)維護(hù)系統(tǒng)穩(wěn)定性的模式設(shè)計(jì),換個(gè)說(shuō)法就是memcached集群的設(shè)計(jì)是沒(méi)有過(guò)分考慮冗余的問(wèn)題,而只有適當(dāng)?shù)娜哂嗖拍鼙WC系統(tǒng)的健壯性問(wèn)題。分布式技術(shù)的實(shí)現(xiàn)是千差萬(wàn)別的,每個(gè)優(yōu)秀的分布式系統(tǒng)都有自身獨(dú)有的特點(diǎn)。
全面的講述memcached技術(shù)并非本文的主題,而且這個(gè)主題也不是一兩句話能說(shuō)清楚的,這里我簡(jiǎn)單的介紹下memcached實(shí)現(xiàn)的原理,當(dāng)網(wǎng)站使用緩存集群時(shí)候,緩存數(shù)據(jù)是通過(guò)一定的算法將緩存數(shù)據(jù)盡量均勻分不到不同服務(wù)器上,如果用戶A的緩存在服務(wù)器A上,那么服務(wù)器B上是沒(méi)有該用戶的緩存數(shù)據(jù),早期的memcache數(shù)據(jù)分布式的算法是根據(jù)緩存數(shù)據(jù)的key即鍵值計(jì)算出一個(gè)hash值,這個(gè)hash值再除以緩存服務(wù)器的個(gè)數(shù),得到的余數(shù)會(huì)對(duì)應(yīng)某一臺(tái)服務(wù)器,例如1對(duì)應(yīng)服務(wù)器A,2對(duì)應(yīng)服務(wù)器B,那么余數(shù)是1的key值緩存就會(huì)存儲(chǔ)在服務(wù)器A上,這樣的算法會(huì)導(dǎo)致某一臺(tái)服務(wù)器掛掉,那么網(wǎng)站損失的緩存數(shù)據(jù)的占比就會(huì)比較高,為了解決這個(gè)問(wèn)題,memcached引入了一致性hash算法。關(guān)于一致性hash網(wǎng)上有很多資料,這里我就貼出一個(gè)鏈接,本文就不做過(guò)多論述了。
一致性hash可以服務(wù)器宕機(jī)時(shí)候這臺(tái)服務(wù)器對(duì)整個(gè)緩存數(shù)據(jù)的影響最小。
上文里我講到了讀寫(xiě)分離的設(shè)計(jì)方案,而讀寫(xiě)分離方案主要是應(yīng)用于網(wǎng)站讀寫(xiě)比例嚴(yán)重失衡的網(wǎng)站,而互聯(lián)網(wǎng)上絕大部分網(wǎng)站都是讀操作的比例遠(yuǎn)遠(yuǎn)大于寫(xiě)操作,這是網(wǎng)站的主流,如果一個(gè)網(wǎng)站讀寫(xiě)比例比較均衡,那么這個(gè)網(wǎng)站一般都是提供專業(yè)服務(wù)的網(wǎng)站,這種網(wǎng)站對(duì)于個(gè)人而言是一個(gè)提供生活便利的工具,它們和企業(yè)軟件類似。大部分關(guān)注大型網(wǎng)站架構(gòu)技術(shù)關(guān)心的重點(diǎn)應(yīng)該是那種對(duì)于讀寫(xiě)比例失衡的網(wǎng)站,因?yàn)樗鼈冏銎饋?lái)更加有挑戰(zhàn)性。
將數(shù)據(jù)庫(kù)進(jìn)行讀寫(xiě)分離是網(wǎng)站解決存儲(chǔ)瓶頸的第一步,為什么說(shuō)是第一步呢?因?yàn)樽x寫(xiě)分離從業(yè)務(wù)角度而言它是一種粗粒度的數(shù)據(jù)拆分,因此它所包含的業(yè)務(wù)復(fù)雜度比較低,容易操作和被掌控,從技術(shù)而言,實(shí)現(xiàn)手段也相對(duì)簡(jiǎn)單,因此讀寫(xiě)分離是一種低成本解決存儲(chǔ)瓶頸的一種手段,這種方案是一種改良方案而不是革命性的的方案,不管是從難度,還是影響范圍或者是經(jīng)濟(jì)成本角度考慮都是很容易讓相關(guān)方接受的。
那么我們僅僅將數(shù)據(jù)庫(kù)做讀寫(xiě)分離為何能產(chǎn)生好的效率了?回答這個(gè)問(wèn)題我們首先要了解下硬盤(pán)的機(jī)制,硬盤(pán)的物理機(jī)制就有一個(gè)大圓盤(pán)飛速旋轉(zhuǎn),然后有個(gè)磁頭不斷掃描這個(gè)大圓盤(pán),這樣的物理機(jī)制就會(huì)導(dǎo)致硬盤(pán)數(shù)據(jù)的順序操作比隨機(jī)操作效率更高,這點(diǎn)對(duì)于硬盤(pán)的讀和寫(xiě)還算公平,但是寫(xiě)操作在高并發(fā)情況下會(huì)有點(diǎn)復(fù)雜,寫(xiě)操作有個(gè)特性就是我們要保證寫(xiě)操作的準(zhǔn)確性,但是高并發(fā)下可能會(huì)出現(xiàn)多個(gè)用戶同時(shí)修改某一條數(shù)據(jù),為了保證數(shù)據(jù)能被準(zhǔn)確的修改,那么我們通常要把并行的操作轉(zhuǎn)變?yōu)榇胁僮鳎?/strong>這個(gè)時(shí)候就會(huì)出現(xiàn)一個(gè)鎖機(jī)制,鎖機(jī)制的實(shí)現(xiàn)是很復(fù)雜的,它會(huì)消耗很多系統(tǒng)性能,如果寫(xiě)操作摻雜了讀操作情況就更復(fù)雜,效率會(huì)更加低效,相對(duì)于寫(xiě)操作讀操作就單純多了,如果我們的數(shù)據(jù)只有讀操作,那么讀的性能也就是硬盤(pán)順序讀能力和隨機(jī)讀能力的體現(xiàn),即使摻雜了并發(fā)也不會(huì)對(duì)其有很大的影響,因此如果把讀操作和寫(xiě)操作分離,效率自然會(huì)得到很大提升。
既然讀寫(xiě)分離可以提升存儲(chǔ)系統(tǒng)的效率,那么為什么我們又要引入緩存系統(tǒng)和搜索技術(shù)了?緩存將數(shù)據(jù)存在內(nèi)存中,內(nèi)存效率是硬盤(pán)的幾萬(wàn)倍,這樣的好處不言而喻,而選擇搜索技術(shù)的背后的原理就不同了,數(shù)據(jù)庫(kù)存儲(chǔ)的數(shù)據(jù)稱之為結(jié)構(gòu)化數(shù)據(jù),結(jié)構(gòu)化數(shù)據(jù)的限制很多,當(dāng)結(jié)構(gòu)化數(shù)據(jù)遇到了千變?nèi)f化的隨機(jī)訪問(wèn)時(shí)候,其效率會(huì)變得異常低效,但是如果一個(gè)網(wǎng)站不能提供靈活、高效的隨機(jī)訪問(wèn)能力,那么這個(gè)網(wǎng)站就會(huì)變得單板沒(méi)有活力,例如我們?cè)谔詫毨锊檎椅覀兿胍纳唐?,但是時(shí)常我們并不清楚自己到底想買(mǎi)啥,如果是在實(shí)體店里店員會(huì)引導(dǎo)我們的消費(fèi),但是網(wǎng)站又如何引導(dǎo)我們的消費(fèi),那么我們必須要賦予網(wǎng)站通過(guò)人們簡(jiǎn)單意向隨機(jī)找到各種不同的商品,這個(gè)對(duì)于數(shù)據(jù)庫(kù)就是一個(gè)like操作的,但是數(shù)據(jù)里數(shù)據(jù)量達(dá)到了一定規(guī)模以后like的低效是無(wú)法讓人忍受的,這時(shí)候搜索技術(shù)在隨機(jī)訪問(wèn)的能力正好可以彌補(bǔ)數(shù)據(jù)庫(kù)這塊的不足。
業(yè)務(wù)再接著的增長(zhǎng)下去,數(shù)據(jù)量也會(huì)隨之越來(lái)越大了,這樣發(fā)展下去總有一天主庫(kù)也會(huì)產(chǎn)生瓶頸了,那么接下來(lái)我們又該如何解決主庫(kù)的瓶頸了?方法很簡(jiǎn)單就是我們要拆分主庫(kù)的數(shù)據(jù)了,那么我該以什么維度拆分?jǐn)?shù)據(jù)了?一個(gè)數(shù)據(jù)庫(kù)里有很多張表,不同的表都針對(duì)不同的業(yè)務(wù),網(wǎng)站的不同業(yè)務(wù)所帶來(lái)的數(shù)據(jù)量也不是不同的,這個(gè)時(shí)候系統(tǒng)的短板就是那些數(shù)據(jù)量最大的表,所以我們要把那些會(huì)讓數(shù)據(jù)庫(kù)產(chǎn)生瓶頸的表拆出來(lái),例如電商系統(tǒng)里商品表和交易表往往數(shù)據(jù)量非常大,那么我們可以把這兩種表建立在單獨(dú)的兩個(gè)數(shù)據(jù)庫(kù)里,這樣就拆分了數(shù)據(jù)庫(kù)的壓力,這種做法叫做數(shù)據(jù)垂直拆分,不過(guò)垂直拆分會(huì)給原有的數(shù)據(jù)庫(kù)查詢,特別是有事務(wù)的相關(guān)操作產(chǎn)生影響,這些問(wèn)題我們必須要進(jìn)行改造,關(guān)于這個(gè)問(wèn)題,我將在下篇里進(jìn)行討論。
當(dāng)我們的系統(tǒng)做完了讀寫(xiě)分離,數(shù)據(jù)垂直拆分后,我們的網(wǎng)站還在迅猛發(fā)展,最終一定又會(huì)達(dá)到新的數(shù)據(jù)庫(kù)瓶頸,當(dāng)然這些瓶頸首先還是出現(xiàn)在那些數(shù)據(jù)量大的表里,這些表數(shù)據(jù)的處理已經(jīng)超出了單臺(tái)服務(wù)器的能力,這個(gè)時(shí)候我們就得對(duì)這個(gè)單庫(kù)單表的數(shù)據(jù)進(jìn)行更進(jìn)一步的拆分,也就是將一張表分布到兩臺(tái)不同的數(shù)據(jù)庫(kù)里,這個(gè)做法就是叫做數(shù)據(jù)的水平拆分了。
Ok,今天內(nèi)容就講到這里了,有這兩篇文章我們可以理出一個(gè)解決大型網(wǎng)站數(shù)據(jù)瓶頸的一個(gè)脈絡(luò)了,具體如下:
單庫(kù)數(shù)據(jù)庫(kù)-->數(shù)據(jù)庫(kù)讀寫(xiě)分離-->緩存技術(shù)-->搜索技術(shù)-->數(shù)據(jù)的垂直拆分-->數(shù)據(jù)的水平拆分
以上的每個(gè)技術(shù)細(xì)節(jié)在具體實(shí)現(xiàn)中可能存在很大的不同,但是問(wèn)題的緣由大致是一致的,我們理清這個(gè)脈絡(luò)就是想告訴大家我們?nèi)绻龅竭@樣的問(wèn)題應(yīng)該按何種思路進(jìn)行思考和設(shè)計(jì)解決方案,好了,今天就寫(xiě)到這里了,晚安。
#p#
存儲(chǔ)的瓶頸寫(xiě)到現(xiàn)在就要進(jìn)入到深水區(qū)了,如果我們所做的網(wǎng)站已經(jīng)到了做數(shù)據(jù)庫(kù)垂直拆分和水平拆分的階段,那么此時(shí)我們所面臨的技術(shù)難度的挑戰(zhàn)也會(huì)大大增強(qiáng)。
這里我們先回顧下數(shù)據(jù)庫(kù)的垂直拆分和水平拆分的定義:
垂直拆分:把一個(gè)數(shù)據(jù)庫(kù)中不同業(yè)務(wù)單元的數(shù)據(jù)分到不同的數(shù)據(jù)庫(kù)里。
水平拆分:是根據(jù)一定的規(guī)則把同一業(yè)務(wù)單元的數(shù)據(jù)拆分到多個(gè)數(shù)據(jù)庫(kù)里。
垂直拆分是一個(gè)粗粒度的拆分?jǐn)?shù)據(jù),它主要是將原來(lái)在一個(gè)數(shù)據(jù)庫(kù)下的表拆分到不同的數(shù)據(jù)庫(kù)里,水平拆分粒度比垂直拆分要更細(xì)點(diǎn),它是將一張表拆到不同數(shù)據(jù)庫(kù)里,粒度的粗細(xì)也會(huì)導(dǎo)致實(shí)現(xiàn)技術(shù)的難度的也不一樣,很明顯水平拆分的技術(shù)難度要遠(yuǎn)大于垂直拆分的技術(shù)難度。難度意味著投入的成本的增加以及我們需要承擔(dān)的風(fēng)險(xiǎn)的加大,我們做系統(tǒng)開(kāi)發(fā)一定要有個(gè)清晰的認(rèn)識(shí):能用簡(jiǎn)單的方案解決問(wèn)題,就一定要毫不猶豫的舍棄復(fù)雜的方案,當(dāng)系統(tǒng)需要使用高難度技術(shù)的時(shí)候,我們一定要讓自己感受到這是迫不得已的。
我是以java工程師應(yīng)聘進(jìn)了我現(xiàn)在的公司,所以在我轉(zhuǎn)到專職前端前,我也做過(guò)不少java的應(yīng)用開(kāi)發(fā),當(dāng)時(shí)我在公司的前輩告訴我,我們公司的數(shù)據(jù)庫(kù)建模很簡(jiǎn)單,怎么個(gè)簡(jiǎn)單法了,數(shù)據(jù)庫(kù)的表之間都沒(méi)有外鍵,數(shù)據(jù)庫(kù)不準(zhǔn)寫(xiě)觸發(fā)器,可以寫(xiě)寫(xiě)存儲(chǔ)過(guò)程,但是存儲(chǔ)過(guò)程決不能用于處理生產(chǎn)業(yè)務(wù)邏輯,而只能是一些輔助工作,例如導(dǎo)入導(dǎo)出寫(xiě)數(shù)據(jù)啊,后面聽(tīng)說(shuō)就算是數(shù)據(jù)庫(kù)做到了讀寫(xiě)分離,數(shù)據(jù)之間同步也最好是用java程序做,也不要使用存儲(chǔ)過(guò)程,除非迫不得已。開(kāi)始我還不太理解這些做法,這種不理解不是指我質(zhì)疑了公司的做法,而是我在想如果一個(gè)數(shù)據(jù)庫(kù)我們就用了這么一點(diǎn)功能,那還不如讓數(shù)據(jù)庫(kù)公司為咋們定制個(gè)閹割版算了,不過(guò)在我學(xué)習(xí)了hadoop之后我有點(diǎn)理解這個(gè)背后的深意了,其實(shí)作為存儲(chǔ)數(shù)據(jù)的數(shù)據(jù)庫(kù),它和我們開(kāi)發(fā)出的程序的本質(zhì)是一樣的那就是:存儲(chǔ)和計(jì)算,那么當(dāng)數(shù)據(jù)庫(kù)作為一個(gè)業(yè)務(wù)系統(tǒng)的存儲(chǔ)介質(zhì)時(shí)候,那么它的存儲(chǔ)對(duì)業(yè)務(wù)系統(tǒng)的重要性要遠(yuǎn)遠(yuǎn)大于它所能承擔(dān)的計(jì)算功能,當(dāng)數(shù)據(jù)庫(kù)作為互聯(lián)網(wǎng)系統(tǒng)的存儲(chǔ)介質(zhì)時(shí)候,如果這個(gè)互聯(lián)網(wǎng)系統(tǒng)成長(zhǎng)迅速,那么這個(gè)時(shí)候我們對(duì)數(shù)據(jù)庫(kù)存儲(chǔ)的要求就會(huì)越來(lái)越高,最后估計(jì)我們都想把數(shù)據(jù)庫(kù)的計(jì)算特性給閹割掉,當(dāng)然數(shù)據(jù)庫(kù)基本的增刪改查我們是不能舍棄的,因?yàn)樗鼈兪菙?shù)據(jù)庫(kù)和外界溝通的入口,我們?nèi)绻佑|過(guò)具有海量數(shù)據(jù)的數(shù)據(jù)庫(kù),我們會(huì)發(fā)現(xiàn)讓數(shù)據(jù)庫(kù)運(yùn)行的單個(gè)sql語(yǔ)句都會(huì)變得異常簡(jiǎn)潔和簡(jiǎn)單,因?yàn)檫@個(gè)時(shí)候我們知道數(shù)據(jù)庫(kù)已經(jīng)在存儲(chǔ)這塊承擔(dān)了太多的負(fù)擔(dān),那么我們能幫助數(shù)據(jù)庫(kù)的手段只能是盡量降低它運(yùn)算的壓力。
回到關(guān)于數(shù)據(jù)庫(kù)垂直拆分和水平拆分的問(wèn)題,假如我們的數(shù)據(jù)庫(kù)設(shè)計(jì)按照我們公司業(yè)務(wù)數(shù)據(jù)庫(kù)為藍(lán)本的話,那么數(shù)據(jù)庫(kù)進(jìn)行了水平拆分我們會(huì)碰到什么樣的問(wèn)題了?為了回答這個(gè)問(wèn)題我就要比較下拆分前和拆分后會(huì)給調(diào)用數(shù)據(jù)庫(kù)的程序帶來(lái)怎樣的不同,不同主要是兩點(diǎn):
第一點(diǎn):被拆出的表和原庫(kù)的其他表有關(guān)聯(lián)查詢即使用join查詢的操作需要進(jìn)行改變;
第二點(diǎn):某些增刪改(注意:一般業(yè)務(wù)庫(kù)設(shè)計(jì)很少使用物理刪除,因?yàn)檫@個(gè)操作十分危險(xiǎn),這里的刪往往是邏輯刪除,一般做法就是更新下記錄的狀態(tài),本質(zhì)是一個(gè)更新操作)牽涉到拆分的表和原庫(kù)其他表共同完成,那么該操作的事務(wù)性就會(huì)被打破,如果處理不好,假如碰到操作失敗,業(yè)務(wù)無(wú)法做到回滾,這會(huì)對(duì)業(yè)務(wù)操作的安全性帶來(lái)極大的風(fēng)險(xiǎn)。
關(guān)于解決第一點(diǎn)的問(wèn)題還是相對(duì)比較簡(jiǎn)單的,方式方法也很多,下面我來(lái)講講我所知道的一些方法,具體如下:
方法一:在垂直拆表時(shí)候,我們先梳理下使用到j(luò)oin操作sql查詢,梳理的維度是以被拆分出的表為原點(diǎn),如果是弱依賴的join表我們改寫(xiě)下sql查詢語(yǔ)句,如果是強(qiáng)依賴的join表則隨拆分表一起拆分,這個(gè)方法很簡(jiǎn)單也很可控,但是這個(gè)技術(shù)方案存在一個(gè)問(wèn)題,就是讓拆分粒度變大,拆分的業(yè)務(wù)規(guī)則被干擾,這么拆分很容易犯一個(gè)問(wèn)題就是一個(gè)數(shù)據(jù)庫(kù)里總會(huì)存在這樣一些表,就是很多數(shù)據(jù)庫(kù)都會(huì)和它關(guān)聯(lián),我們很難拆解這些關(guān)聯(lián)關(guān)系,當(dāng)我們無(wú)法理清時(shí)候就會(huì)把該表做冗余,即不同數(shù)據(jù)庫(kù)存在雷同表,隨著業(yè)務(wù)增長(zhǎng),這種表的數(shù)據(jù)同步就成為了數(shù)據(jù)庫(kù)的一個(gè)軟肋,最終它會(huì)演變?yōu)檎麄€(gè)數(shù)據(jù)庫(kù)系統(tǒng)的短板甚至是全系統(tǒng)的短板。
方法二:我們拆表的準(zhǔn)則還是按業(yè)務(wù)按需求在數(shù)據(jù)庫(kù)層面進(jìn)行,等數(shù)據(jù)庫(kù)拆好后,再改寫(xiě)原來(lái)受到影響的join查詢語(yǔ)句,這里我要說(shuō)明的是查詢語(yǔ)句修改的成本很低,因?yàn)椴樵儾僮魇莻€(gè)只讀操作,它不會(huì)改變?nèi)魏蔚讓拥臇|西,如果數(shù)據(jù)表跨庫(kù),我們可以把join查詢拆分為多次查詢,最后將查詢結(jié)果在內(nèi)存中歸納和合并,其實(shí)我們?nèi)绻鲃?dòng)拆庫(kù),絕不會(huì)把換個(gè)不同的數(shù)據(jù)庫(kù)產(chǎn)品建立新庫(kù),肯定是使用相同數(shù)據(jù)庫(kù),同類型的數(shù)據(jù)庫(kù)基本都支持跨庫(kù)查詢,不過(guò)跨庫(kù)查詢聽(tīng)說(shuō)效率不咋地,我們可以有選擇的使用。這種方案也有個(gè)致命的缺點(diǎn),我們做數(shù)據(jù)庫(kù)垂直拆分絕不可能一次到位,一般都是多次迭代,而該方案的影響面很大,關(guān)聯(lián)方過(guò)多,每次拆表幾乎要檢查所有相關(guān)的sql語(yǔ)句,這會(huì)導(dǎo)致系統(tǒng)不斷累積不可預(yù)知的風(fēng)險(xiǎn)。
以下三段內(nèi)容是方法三:
不管是方法一還是方法二,都有一個(gè)很根本的缺陷就是數(shù)據(jù)庫(kù)和上層業(yè)務(wù)操作耦合度很高,每次數(shù)據(jù)庫(kù)的變遷都導(dǎo)致業(yè)務(wù)開(kāi)發(fā)跟隨做大量的同步工作,這樣的后果就是資源浪費(fèi),做服務(wù)的人不能天天被數(shù)據(jù)庫(kù)牽著鼻子走,這樣業(yè)務(wù)系統(tǒng)的日常維護(hù)和業(yè)務(wù)擴(kuò)展會(huì)很存問(wèn)題,那么我們一定要有一個(gè)服務(wù)和數(shù)據(jù)庫(kù)解耦方案,那么這里我們就得借鑒ORM技術(shù)了。(這里我要說(shuō)明下,方法一和方法二我都是以修改sql闡述的,在現(xiàn)實(shí)開(kāi)發(fā)里很多系統(tǒng)會(huì)使用ORM技術(shù),互聯(lián)網(wǎng)一般用ibatis和mybatis這種半ORM的產(chǎn)品,因?yàn)樗鼈兛梢灾苯訉?xiě)sql和數(shù)據(jù)庫(kù)最為親近,如果使用hibernate則就不同了,但是hibernate雖然大部分不是直接寫(xiě)sql,但是它只不過(guò)是對(duì)數(shù)據(jù)庫(kù)操作做了一層映射,本質(zhì)手段是一致,所以上文的sql可以算是一種指代,它也包括ORM里的映射技術(shù))
傳統(tǒng)的ORM技術(shù)例如hibernate還有mybatis都是針對(duì)單庫(kù)進(jìn)行的,并不能幫我們解決垂直拆分的問(wèn)題,因此我們必須自己開(kāi)發(fā)一套解決跨庫(kù)操作的ORM系統(tǒng),這里我只針對(duì)查詢的ORM談?wù)勛约旱目捶ǎㄖv到這里是不是有些人會(huì)有種似成相識(shí)的感覺(jué),這個(gè)不是和分布式系統(tǒng)很像嗎)。
其實(shí)具體怎么重構(gòu)有問(wèn)題的sql不是我想討論的問(wèn)題,因?yàn)檫@是個(gè)技術(shù)手段或者說(shuō)是一個(gè)技術(shù)上的技巧問(wèn)題,我這里重點(diǎn)講講這個(gè)ORM與服務(wù)層接口的交互,對(duì)于服務(wù)層而言,服務(wù)層最怕的就是被數(shù)據(jù)庫(kù)牽著鼻子走,因?yàn)楫?dāng)數(shù)據(jù)庫(kù)要進(jìn)行重大改變時(shí)候,服務(wù)層總是想方設(shè)法讓自己不要發(fā)生變化,對(duì)于數(shù)據(jù)庫(kù)層而言服務(wù)層的建議都應(yīng)該是合理,數(shù)據(jù)庫(kù)層要把服務(wù)層當(dāng)做自己的需求方,這樣雙方才能齊心協(xié)力完成這件重要的工作,那么服務(wù)層一般是怎樣和數(shù)據(jù)庫(kù)層交互的呢?
從傳統(tǒng)的ORM技術(shù)我們可以找到答案,具體的方式有兩種:
第一種:以hibernate為代表的,hibernate框架有一套自己的查詢語(yǔ)言就是hql,它類似于sql,自定義一套查詢語(yǔ)言看起來(lái)很酷,也非常靈活,但是實(shí)現(xiàn)難度非常之高,因?yàn)檫@種做法相當(dāng)于我們要自己編寫(xiě)一套新的編程語(yǔ)言,如果這個(gè)語(yǔ)言設(shè)計(jì)不好,使用者又理解不深入,最后往往會(huì)事與愿違,就像hibernate的hql,我們經(jīng)常令可直接使用sql也不愿意使用hql,這其中的緣由用過(guò)的人一定很好理解的。
第二種:就是數(shù)據(jù)層給服務(wù)層提供調(diào)用方法,每個(gè)方法對(duì)應(yīng)一個(gè)具體的數(shù)據(jù)庫(kù)操作,就算底層數(shù)據(jù)庫(kù)發(fā)生重大變遷,只要提供給服務(wù)端的方法定義不變,那么數(shù)據(jù)庫(kù)的變遷對(duì)服務(wù)層影響度也會(huì)最低。
前面我提到技術(shù)難度是我們選擇技術(shù)的一個(gè)重要指標(biāo),相比之下第二種方案將會(huì)是我們的首選。
垂直拆分?jǐn)?shù)據(jù)庫(kù)還會(huì)帶來(lái)另一個(gè)問(wèn)題就是對(duì)事務(wù)的影響,垂直拆分?jǐn)?shù)據(jù)庫(kù)會(huì)導(dǎo)致原來(lái)的事務(wù)機(jī)制變成了分布式事務(wù),解決分布式事務(wù)問(wèn)題是非常難的,特別是如果我們想使用業(yè)界推出的解決分布式事務(wù)方案,那么要自己實(shí)現(xiàn)個(gè)分布式事務(wù)就更難了,不過(guò)這里我要說(shuō)明一下,我這里說(shuō)的更難是和我寫(xiě)本文有關(guān),我本篇文章之所以現(xiàn)在才寫(xiě)是因?yàn)槲蚁胂妊芯肯聵I(yè)界推出的分布式解決方案,但是這些方案的原理看得我很沮喪,我就想如果我們直接用方案的接口實(shí)現(xiàn)了它,因?yàn)檫€是不懂他的很多原理,那么這些方案其實(shí)就是不可控方案,說(shuō)不定使用過(guò)多就會(huì)給系統(tǒng)埋下定時(shí)炸彈,因此這里我就只提提這些方案,有興趣的童鞋可以去研究下:
一、X/OPEN組織推出的分布式事務(wù)規(guī)范XA,其中還包括該組織定義的分布式事務(wù)處理模型X/OPEN;
二、大型網(wǎng)站一致性理論CAP/BASE
三、 PAXOS協(xié)議。
這里特別要提的是PAXOS協(xié)議,我以前寫(xiě)過(guò)好幾篇關(guān)于zookeeper的文章,zookeeper框架有一個(gè)特性就是它本身是一個(gè)分布式文件系統(tǒng),當(dāng)我們往zookeeper寫(xiě)數(shù)據(jù)時(shí)候,zookeeper集群能保證我們的寫(xiě)操作的可靠性,這個(gè)可靠性和我們使用線程安全來(lái)控制寫(xiě)數(shù)據(jù)一樣,絕對(duì)不會(huì)讓寫(xiě)操作出錯(cuò),之所以zookeeper能做到這點(diǎn),是因?yàn)閦ookeeper內(nèi)部有一個(gè)類似PAXOS協(xié)議的協(xié)議,這個(gè)協(xié)議類似一個(gè)選舉方案,它能保證寫(xiě)入操作的原子性。
其實(shí)事務(wù)也是和線程安全技術(shù)類似,只不過(guò)事務(wù)是要保證一個(gè)業(yè)務(wù)操作的原子性問(wèn)題,當(dāng)然事務(wù)還要有個(gè)特點(diǎn)就是回滾機(jī)制即業(yè)務(wù)操作失敗,事務(wù)可以保證系統(tǒng)恢復(fù)到業(yè)務(wù)操作前的狀態(tài),回滾機(jī)制的本質(zhì)其實(shí)是維護(hù)業(yè)務(wù)操作的狀態(tài)性,具體點(diǎn)我這里列舉個(gè)例子:當(dāng)系統(tǒng)將要執(zhí)行一個(gè)業(yè)務(wù)操作時(shí)候,我們首先為業(yè)務(wù)系統(tǒng)定義一個(gè)初始狀態(tài),業(yè)務(wù)執(zhí)行操作時(shí)候我們可以定義一個(gè)執(zhí)行狀態(tài),操作成功就是一個(gè)成功狀態(tài),操作失敗就是一個(gè)操作失敗狀態(tài),如果業(yè)務(wù)操作是失敗狀態(tài),我們可以讓業(yè)務(wù)回滾到初始狀態(tài),更進(jìn)一步如果執(zhí)行狀態(tài)超時(shí)也可以將整個(gè)業(yè)務(wù)狀態(tài)回退到初始狀態(tài),其實(shí)所有事務(wù)回滾機(jī)制的本質(zhì)基本都是如此。記得不久前,在群里有個(gè)群友就問(wèn)大家如何實(shí)現(xiàn)分布式事務(wù),他想要知道的分布式事務(wù)是有沒(méi)有一種技術(shù)能像我們操作數(shù)據(jù)庫(kù)或者是jdbc那樣一個(gè)commit,一個(gè)rollback就搞定,但是現(xiàn)實(shí)中的分布式事務(wù)比commit和rollback復(fù)雜的多,不可能簡(jiǎn)單的讓我們寫(xiě)幾個(gè)標(biāo)記就能實(shí)現(xiàn)分布式事務(wù),當(dāng)然業(yè)界是有方案的,就是我上面提到的,如果有人真想知道可以自己研究下,不過(guò)我本人現(xiàn)在還是不太懂上面這些技術(shù)的原理和思想。
其實(shí)當(dāng)時(shí)我馬上給那位群友一個(gè)解答,我說(shuō)我們開(kāi)發(fā)時(shí)候是經(jīng)常碰到分布式事務(wù),但是我們解決分布式事務(wù)大多數(shù)從業(yè)務(wù)角度來(lái)解決的,而沒(méi)去選擇純技術(shù)手段,因?yàn)榧夹g(shù)手段太復(fù)雜難以控制。這個(gè)答案可能不會(huì)令提問(wèn)者滿意,但是我現(xiàn)在還是堅(jiān)持這個(gè)觀點(diǎn),這個(gè)觀點(diǎn)符合我提到的原則,當(dāng)技術(shù)方案難度過(guò)高,我們就不要輕易選擇使用它,因?yàn)檫@么做是很危險(xiǎn)的,今天我就舉個(gè)例子吧,這樣可能更有說(shuō)服力。我現(xiàn)在做的系統(tǒng)很多業(yè)務(wù)操作經(jīng)常要和其他系統(tǒng)共同完成,其他系統(tǒng)有我們公司自己的系統(tǒng),也有其他企業(yè)的系統(tǒng),這里我還是把業(yè)務(wù)操作比作一輛在高速公路的汽車(chē),那么每個(gè)系統(tǒng)就是高速公路上的一個(gè)收費(fèi)站,業(yè)務(wù)每到一個(gè)收費(fèi)站,該系統(tǒng)的數(shù)據(jù)庫(kù)就會(huì)在對(duì)應(yīng)的數(shù)據(jù)庫(kù)的某張表里某條記錄上記錄一個(gè)狀態(tài),當(dāng)汽車(chē)跑完全程,各個(gè)收費(fèi)站就會(huì)相互通知,告訴大家任務(wù)完成,最終將所有的狀態(tài)置為已完成,如果失敗,就廢掉這輛汽車(chē),收費(fèi)站之間也會(huì)相互通知,讓所有的記錄狀態(tài)回歸到初始狀態(tài),就當(dāng)從來(lái)沒(méi)有這輛汽車(chē)來(lái)過(guò)。這個(gè)做法的原理就是使用了事務(wù)回滾的本質(zhì),狀態(tài)的變遷和回退,這個(gè)做法在業(yè)務(wù)系統(tǒng)開(kāi)發(fā)里也有個(gè)專有術(shù)語(yǔ)就是工作流。其實(shí)大多數(shù)問(wèn)如何實(shí)現(xiàn)分布式事務(wù)如何實(shí)現(xiàn)的問(wèn)題的本質(zhì)就是想解決事務(wù)的回滾問(wèn)題,我們其實(shí)不要被這個(gè)分布式事務(wù)的名字給嚇住了,其實(shí)有很多不起眼的技術(shù)手段和業(yè)務(wù)手段都能達(dá)到相同的目的。
晚上11點(diǎn)了,看來(lái)本文今天寫(xiě)不完了,今天就到此為止,最后我要總結(jié)下本文的內(nèi)容,具體如下:
1. 大型網(wǎng)站解決存儲(chǔ)瓶頸的問(wèn)題,我們要找準(zhǔn)存儲(chǔ)這個(gè)關(guān)鍵點(diǎn),因?yàn)閿?shù)據(jù)庫(kù)其實(shí)是存儲(chǔ)和運(yùn)算的組合體,但是在我們這個(gè)場(chǎng)景下,存儲(chǔ)是第一位的,當(dāng)存儲(chǔ)是瓶頸時(shí)候我們要狠下心來(lái)盡量多的拋棄數(shù)據(jù)的計(jì)算特點(diǎn),所以上文中我提出我們數(shù)據(jù)庫(kù)就不要濫用計(jì)算功能了例如觸發(fā)器、存儲(chǔ)過(guò)程等等。
2. 數(shù)據(jù)庫(kù)剝離計(jì)算功能不代表不要數(shù)據(jù)的計(jì)算功能,因?yàn)闆](méi)有數(shù)據(jù)的計(jì)算功能數(shù)據(jù)庫(kù)也就沒(méi)價(jià)值了,那么我們要將數(shù)據(jù)庫(kù)的計(jì)算功能進(jìn)行遷移,遷移到程序里面,一般大型系統(tǒng)程序和數(shù)據(jù)庫(kù)都是分開(kāi)部署到不同服務(wù)器上,因此程序里處理數(shù)據(jù)計(jì)算就不會(huì)影響到數(shù)據(jù)庫(kù)所在服務(wù)器的性能,就可以讓安裝數(shù)據(jù)庫(kù)的服務(wù)器專心服務(wù)于存儲(chǔ)。
3. 我們要盡一切可能的把數(shù)據(jù)庫(kù)的變化對(duì)服務(wù)層的影響降到最低,最好是數(shù)據(jù)庫(kù)做拆分后,現(xiàn)有業(yè)務(wù)不要任何的更改,那么我們就得設(shè)計(jì)一個(gè)全新的數(shù)據(jù)訪問(wèn)層,這個(gè)數(shù)據(jù)訪問(wèn)層將數(shù)據(jù)庫(kù)和服務(wù)層進(jìn)行解耦,任何數(shù)據(jù)庫(kù)的變化都由數(shù)據(jù)訪問(wèn)層消化,數(shù)據(jù)訪問(wèn)層對(duì)外接口要高度統(tǒng)一,不要輕易改變。
4. 如果我們?cè)O(shè)計(jì)了數(shù)據(jù)訪問(wèn)層來(lái)解決數(shù)據(jù)庫(kù)拆分的問(wèn)題,數(shù)據(jù)訪問(wèn)層加上數(shù)據(jù)庫(kù)其實(shí)就組合出了一個(gè)分布式數(shù)據(jù)庫(kù)的解決方案,由此可見(jiàn)拆分?jǐn)?shù)據(jù)庫(kù)的難度是很高的,因?yàn)閿?shù)據(jù)庫(kù)將擁有分布式的特性,而分布式開(kāi)發(fā)就意味開(kāi)發(fā)難度的增加。
5. 對(duì)于分布式事務(wù)的處理,我們盡量要從具體問(wèn)題具體分析,不要一感覺(jué)這個(gè)事務(wù)操作本質(zhì)是分布式事務(wù)就去尋找通用的分布式事務(wù)技術(shù)手段,這樣的想法其實(shí)是回避困難的思想,結(jié)果可能會(huì)是把問(wèn)題搞得更加復(fù)雜。
好了,今天就寫(xiě)到這里吧,祝大家晚安,生活愉快!
本文出自:http://www.cnblogs.com/sharpxiajun/