夏雨++朱信忠++徐慧英++顧雅麗++盧山富
摘 要 隨著互聯(lián)網(wǎng)技術(shù)突飛猛進(jìn)的發(fā)展,網(wǎng)站規(guī)模的不斷擴(kuò)大,數(shù)據(jù)量的增加,大數(shù)據(jù)的時(shí)代已經(jīng)真正到來,性能和安全的問題也日益突出,很多技術(shù)被提出,并且用來提升網(wǎng)絡(luò)以及服務(wù)器的性能和安全,各種各樣的架構(gòu)也應(yīng)運(yùn)而生,如何在高并發(fā),高性能之中尋找一個(gè)平衡點(diǎn),已經(jīng)成為亟需解決的問題,很多人可能知道應(yīng)對高并發(fā)要用分布式緩存,要用分布式和負(fù)載均衡。但為什么要這么用,怎么用才能用好,怎樣根據(jù)實(shí)際需求設(shè)計(jì)最適合的架構(gòu)完全不懂。本文基于.net平臺,提出了并設(shè)計(jì)了支持.Net平臺的千萬級規(guī)模分布式高性能高并發(fā)WEB軟件開發(fā)架構(gòu)設(shè)計(jì)方案。
【關(guān)鍵詞】互聯(lián)網(wǎng)技術(shù) 并發(fā)業(yè)務(wù)系統(tǒng) 方案設(shè)計(jì)
1 系統(tǒng)產(chǎn)生并發(fā)的業(yè)務(wù)節(jié)點(diǎn)
并發(fā)產(chǎn)生的原因:
(1)短時(shí)間大量請求競爭低速存儲(chǔ)設(shè)備或者競爭處理器資源。
(2)不合理的程序處理邏輯導(dǎo)致請求無法盡快完成。
(3)其他瓶頸,如帶寬、服務(wù)器資源等。
2 應(yīng)對并發(fā)的解決方案
2.1 針對并發(fā)量不高的情況的方案——分庫分表,實(shí)質(zhì)上是分布式系統(tǒng)的一種模式
具體邏輯如下:
將原本屬于同一臺服務(wù)器的同一個(gè)數(shù)據(jù)庫拆分到多臺服務(wù)器中,甚至可以將原本同屬于一張表的字段拆分到不同的服務(wù)器上。其目的為將一個(gè)大的讀寫請求分解到多臺服務(wù)器中,從而在異步并行執(zhí)行中減少請求執(zhí)行時(shí)間,以達(dá)到并發(fā)周期中可承載更多的請求。
2.2 使用分布式處理,結(jié)合CDN負(fù)載均衡技術(shù)
2.2.1 鏡像模式
將一臺服務(wù)器的鏡像副本復(fù)制到多臺服務(wù)器中,通過windows的NLB或者第三方CDN方案如nginx實(shí)現(xiàn)分布式處理,請求均衡。此種分布式模式在windows下只能使用NBL,因?yàn)镹BL僅支持鏡像式分布式服務(wù)器集群,理論上NLB可以支持32臺服務(wù)器。
NLB模式的分布式系統(tǒng)的優(yōu)勢在于通過負(fù)載均衡能夠充分發(fā)揮分布式系統(tǒng)在讀數(shù)據(jù)比較頻繁的模式下發(fā)揮最大的效益,而且彼此之間互為備份。其缺點(diǎn)也是明顯的,因?yàn)橥粭l數(shù)據(jù)的寫入(更新或者刪除等寫操作)必須通過事務(wù)在集群內(nèi)每臺服務(wù)器上同步。
2.2.2 分段模式
多臺服務(wù)器通過CDN來決定數(shù)據(jù)寫入那一臺或者多臺服務(wù)器中,數(shù)據(jù)在讀取時(shí)需要在多臺服務(wù)器中輪詢,增加了數(shù)據(jù)讀取的復(fù)雜度。
2.3 使用分布式緩存技術(shù)
分布式緩存技術(shù)已經(jīng)得到廣泛應(yīng)用,其實(shí)現(xiàn)算法為平衡樹(紅黑樹),使用緩存的目的是要減少訪問服務(wù)器低速存儲(chǔ)設(shè)備以及I/O帶來的性能損耗,從而提高系統(tǒng)單位時(shí)間內(nèi)的響應(yīng)能力,以達(dá)到提高并發(fā)能力的目的。
分布式緩存有很多典型應(yīng)用,例如聊天室。早期的聊天室沒有使用socket前,基本上使用的都是脈沖模式(服務(wù)器輪詢),不斷向低速存儲(chǔ)設(shè)備輪詢的成本是很可觀的。所以寫入內(nèi)存當(dāng)中能夠更快速的使客戶端訪問到,一旦聊天結(jié)束,服務(wù)器并不持久化聊天數(shù)據(jù)。
投注業(yè)務(wù)具有短時(shí)間集中并發(fā)寫入數(shù)據(jù)的特點(diǎn),將數(shù)據(jù)寫入內(nèi)存,并在內(nèi)存中操作并非復(fù)雜的事情。但如涉及到數(shù)據(jù)持久化,就存在數(shù)據(jù)一致性的問題。分布式緩存解決方案redis提供了數(shù)據(jù)持久化,能夠保證數(shù)據(jù)的一致性。
實(shí)踐證明,10萬級別的并發(fā)場景中,向低速存儲(chǔ)設(shè)備(關(guān)系數(shù)據(jù)庫)寫入數(shù)據(jù)如果以整形為主,使用緩存和不使用緩存的差別并不明顯。
2.4 NOSQL的使用
NOSQL數(shù)據(jù)庫的數(shù)據(jù)吞吐能力可以達(dá)到關(guān)系數(shù)據(jù)庫的百倍以上,天然支持分布式模式。能夠提供高速讀寫的優(yōu)異性能,提供高I/O操作。谷歌、百度、淘寶等都使用了NOSQL。
但NOSQL也并非沒有缺點(diǎn),大多數(shù)應(yīng)用都是關(guān)系型的,也就是要保證數(shù)據(jù)操作的原子性和唯一性,NOSQL無法保證這一點(diǎn)。因此多數(shù)NOSQL應(yīng)用需要數(shù)據(jù)庫中間件來保證關(guān)系數(shù)據(jù)的原子性。常用的NOSQL如MongoDB、Hadoop等。
3 并發(fā)解決方案的實(shí)現(xiàn)
分庫分表的實(shí)現(xiàn):
A:為什么要分庫分表?
在只有一臺服務(wù)器的情況下,大量的select會(huì)被同時(shí)執(zhí)行的update和delete阻塞,導(dǎo)致并發(fā)數(shù)嚴(yán)重受限。當(dāng)然,這種場景非常適合讀遠(yuǎn)大于寫的應(yīng)用,當(dāng)讀寫基本相當(dāng)?shù)那闆r下,單臺服務(wù)器依舊存在讀寫互斥的情況,不適合并發(fā)量較大的應(yīng)用。
B:分庫的作用在于多臺服務(wù)器分擔(dān)客戶端的讀寫請求,大大降低了并發(fā)的可能性。
在web應(yīng)用中,通常web服務(wù)器應(yīng)與數(shù)據(jù)庫服務(wù)分離,這在未來實(shí)現(xiàn)CDN的時(shí)候非常有意義,來自客戶端的請求基本會(huì)在web服務(wù)器之間進(jìn)行分配,由web服務(wù)器根據(jù)CDN決定訪問哪些數(shù)據(jù)庫服務(wù)器。
數(shù)據(jù)庫服務(wù)器分離后,多臺數(shù)據(jù)庫服務(wù)器之間通過異步數(shù)據(jù)事務(wù)保持一致性,當(dāng)然這種數(shù)據(jù)同步會(huì)有一些時(shí)間上的延遲,這種延遲基本上都是毫秒級別的。當(dāng)然,低級別的分庫可以直接由邏輯層通過事務(wù)保證數(shù)據(jù)一致性。
操作系統(tǒng)對磁盤的讀寫有控制機(jī)制,在只有一臺服務(wù)器的情況下,各種請求競爭資源,包括對處理機(jī)及內(nèi)存、磁盤等的控制權(quán)。因此,一臺服務(wù)器的并發(fā)能力是非常有限的,這也是分庫的原因所在。
3.1 關(guān)于讀寫分離
前文已提到讀寫互斥,這將給讀取數(shù)據(jù)帶來很大延遲,尤其是大量讀寫的情況下這種延遲是無法接受的。因此在讀業(yè)務(wù)場景中,應(yīng)保持?jǐn)?shù)據(jù)隨時(shí)處于可讀取狀態(tài)。
通過分庫、分表以達(dá)到讀寫分離的目標(biāo),讓一些(庫)表專門用來寫數(shù)據(jù),而另一些表(庫)用來讀數(shù)據(jù)。而數(shù)據(jù)庫與數(shù)據(jù)庫之間通過數(shù)據(jù)一致性組件保持?jǐn)?shù)據(jù)復(fù)制作業(yè),如syncNavigator或者數(shù)據(jù)庫自帶的訂閱發(fā)布功能。用于寫操作的數(shù)據(jù)庫一旦有新數(shù)據(jù)寫入,則根據(jù)數(shù)據(jù)同步策略來同步數(shù)據(jù)到讀服務(wù)器(數(shù)據(jù)庫、表)中。
3.2 關(guān)于緩存
Memcached 是一個(gè)高性能的分布式內(nèi)存 對象緩存系統(tǒng),用于動(dòng)態(tài)Web應(yīng)用以減輕數(shù)據(jù)庫負(fù)載。它通過在內(nèi)存中緩存數(shù)據(jù)和對象 來減少讀取數(shù)據(jù)庫的次數(shù),從而提供動(dòng)態(tài)、數(shù)據(jù)庫驅(qū)動(dòng)網(wǎng)站的速度。
memcache會(huì)預(yù)先生成很多的內(nèi)存塊,比如有96byte,120byte,150byte,200byte,800byte。
預(yù)先生成一批slab的好處是什么?可以根據(jù)item的大小,放到合適的slab上面去。
原因:找內(nèi)存所消耗的時(shí)間比遠(yuǎn)大于釋放時(shí)間。
但是Memcached對內(nèi)存資源的有效利用:
(1)重復(fù)利用已經(jīng)分配的內(nèi)存,也就是說不會(huì)去刪除已有的數(shù)據(jù)而且釋放,而是數(shù)據(jù)過期后,用戶將數(shù)據(jù)不可見。
(2)Memcached 還是用了一種Lazy Expiration (延遲過期[姑且這樣翻譯]) 技術(shù),就是Memcached不會(huì)去監(jiān)視服務(wù)器上的數(shù)據(jù)是否過期,而是等待get的時(shí)候檢查時(shí)間戳是否過期,減少M(fèi)emcached在監(jiān)控?cái)?shù)據(jù)上所用到的時(shí)間。
(3) Memcached 不會(huì)去釋放已經(jīng)使用的內(nèi)存空間,但是如果分配的內(nèi)存空間已經(jīng)滿了,而Memcached 是如何去保證內(nèi)存空間的重復(fù)使用呢!Memcached 是用了 Least Recently Used(LRU) 機(jī)制來協(xié)調(diào)內(nèi)存空間的使用。LRU 意思就是最少最近使用,當(dāng)此處內(nèi)存空間數(shù)據(jù)最長時(shí)間沒有使用,而且使用次數(shù)很少,在存儲(chǔ)新的數(shù)據(jù)的同時(shí)就會(huì)覆蓋此處空間。
一致性哈希
當(dāng)hash遇上分布式,單臺機(jī)子的hashmap存儲(chǔ)已經(jīng)不能滿足我們的key-value需求,怎么辦,我們需要把存儲(chǔ)內(nèi)容分布到不同的實(shí)體機(jī)上,這時(shí)需要一種把key映射到不同機(jī)器的方法,我們想起了hash,可以把實(shí)體機(jī)當(dāng)做是桶,采用和hashmap實(shí)現(xiàn)一樣的思路,通過和實(shí)體機(jī)的數(shù)量取模,自然映射到不同的機(jī)器。
但是這就會(huì)導(dǎo)致一個(gè)問題:數(shù)據(jù)分布不均勻。大部分?jǐn)?shù)據(jù)都分配到server1了,只有小部分?jǐn)?shù)據(jù)分布在server2。在服務(wù)器數(shù)據(jù)很少的時(shí)候,數(shù)據(jù)不均 勻會(huì)表現(xiàn)的非常明顯。
解決這個(gè)問題的方法是使用虛擬節(jié)點(diǎn),一個(gè)真實(shí)服務(wù)器對應(yīng)多個(gè)虛擬節(jié)點(diǎn),所有虛擬節(jié)點(diǎn)按hash值分布在一致性哈希圓環(huán)上。具體實(shí)現(xiàn)方法可以這樣做,為真實(shí)服務(wù)器設(shè)置副本數(shù)量,然后根據(jù)各真實(shí)服務(wù)器的IP和端口號再加上一個(gè)遞增的索引數(shù)計(jì)算hash值。
如圖2所示。
分布式緩存可以解決以下幾種問題:
比如用來緩存Web 頁面的內(nèi)容片段,包括HTML、CSS 和圖片等,多應(yīng)用于社交網(wǎng)站等;
應(yīng)用對象緩存:緩存系統(tǒng)作為ORM 框架的二級緩存對外提供服務(wù),目的是減輕數(shù)據(jù)庫的負(fù)載壓力,加速應(yīng)用訪問;
狀態(tài)緩存:緩存包括Session 會(huì)話狀態(tài)及應(yīng)用橫向擴(kuò)展時(shí)的狀態(tài)數(shù)據(jù)等,這類數(shù)據(jù)一般是難以恢復(fù)的,對可用性要求較高,多應(yīng)用于高可用集群;
并行處理:通常涉及大量中間計(jì)算結(jié)果需要共享;
事件處理:分布式緩存提供了針對事件流的連續(xù)查詢(continuous query)處理技術(shù),滿足實(shí)時(shí)性需求;
極限事務(wù)處理:分布式緩存為事務(wù)型應(yīng)用提供高吞吐率、低延時(shí)的解決方案,支持高并發(fā)事務(wù)請求處理,多應(yīng)用于鐵路、金融服務(wù)和電信等領(lǐng)域。
3.3 關(guān)于NOSQL
以SQL SERVER2012作為數(shù)據(jù)倉庫存儲(chǔ)邏輯數(shù)據(jù),當(dāng)關(guān)系型數(shù)據(jù)庫無法滿足并發(fā)要求的時(shí)候,后端增加使用非關(guān)系型數(shù)據(jù)庫mongodb作為高吞吐數(shù)據(jù)庫使用,為避免NOSQL數(shù)據(jù)庫的非原子性操作帶來的一些問題,架構(gòu)使用SQL SERVER作為數(shù)據(jù)一致性中間件,以分布式緩存作為業(yè)務(wù)快速存儲(chǔ)載體,提高整個(gè)系統(tǒng)的并發(fā)響應(yīng)能力。
MongoDB一個(gè)文件存儲(chǔ)片段最大2GB,所以隨著數(shù)量的增加,MongoDB會(huì)一個(gè)又一個(gè)增加數(shù)據(jù)存儲(chǔ)文件,正因?yàn)槭嵌鄠€(gè)文件,所以可以并行從多個(gè)文件中讀取和寫入數(shù)據(jù),但是因?yàn)槭嵌鄠€(gè)文件的并行處理,帶來了高IO,也帶來了致命的缺陷,原因非常明顯,那就是關(guān)系數(shù)據(jù)庫為了保證數(shù)據(jù)的一致性,使用了原子性約束,也就是我們說的原子性操作,比如添加數(shù)據(jù)庫記錄時(shí),不允許出現(xiàn)兩條數(shù)據(jù)完全相同,比如主鍵都是2,這樣的話就沒辦法唯一標(biāo)識這條數(shù)據(jù)了,所以關(guān)系數(shù)據(jù)庫普遍采用了原子性(唯一性)約束,非關(guān)系數(shù)據(jù)庫和關(guān)系數(shù)據(jù)庫在這一點(diǎn)上的區(qū)別非常鮮明,那就是無法保證原子性操作,非關(guān)系數(shù)據(jù)庫不保證原子性,也就無法保證數(shù)據(jù)的唯一性,這樣在連表查詢時(shí)就無法獲取正確的數(shù)據(jù),所以一般情況下都是由中間件或者關(guān)系數(shù)據(jù)庫來保證數(shù)據(jù)的唯一性,比如NOSQL數(shù)據(jù)庫專門用來讀操作,你不是I/O能力很強(qiáng)嗎(是關(guān)系數(shù)據(jù)庫的100倍),那你就專門用來讀,數(shù)據(jù)通過各種手段先寫入關(guān)系數(shù)據(jù)庫,然后再同步到非關(guān)系數(shù)據(jù)庫,你從關(guān)系數(shù)據(jù)庫里讀出來的數(shù)據(jù)都具有唯一性,再同步到NOSQL,寫數(shù)據(jù)庫時(shí)底層用的是socket,來監(jiān)聽多個(gè)異步操作是否返回了正確的結(jié)果。如果是則萬事大吉,如果不是,則向緩存拿數(shù)據(jù)再次寫入數(shù)據(jù)庫,直到寫入為止,除非有新的請求告訴你丟棄這個(gè)操作。最后再同步到NOSQL來讀。
參考文獻(xiàn)
[1]包立輝,黃彥飛.高并發(fā)網(wǎng)站的架構(gòu)研究及解決力案[J].計(jì)算機(jī)科學(xué),2012,39(10):184-187.
[2]薛質(zhì).電子商務(wù)平臺的性能優(yōu)化和高可靠性研究與實(shí)現(xiàn)[D].上海:上海交通大學(xué),2007:41-45.
作者單位
浙江師范大學(xué) 浙江省金華市 321004