轉載請注明來源:Key/Value之王初探:三、解決的分布式存儲場景的應用
一、高可用的服務器場景簡介1.1 應用服務器的無狀態(tài)特性
應用層服務器(這里一般指Web服務器)處理網(wǎng)站應用的業(yè)務邏輯,應用的一個最顯著的特點是:應用的無狀態(tài)性。
PS:提到無狀態(tài)特性,不得不說下Http協(xié)議。我們常常聽到說,Http是一個無狀態(tài)協(xié)議,同一個會話的連續(xù)兩個請求互相不了解,他們由最新實例化的環(huán)境進行解析,除了應用本身可能已經(jīng)存儲在全局對象中的所有信息外,該環(huán)境不保存與會話有關的任何信息。之所以我們在使用ASP.NET 開發(fā)中會感覺不到Http的無狀態(tài)特性,完全是因為幫我們實現(xiàn)了,它是ASP.NET 中保存頁面信息的基本單位,本質是一個HTML中的隱藏域,回調時會將這個隱藏域中的數(shù)據(jù)提交到服務器端。
在很多場景中,用戶都需要和我們的網(wǎng)站系統(tǒng)進行多次的信息交互,這時就需要一種解決方案來克服無狀態(tài)特性所帶來的困境。還好,在巨人的肩膀上,我們已經(jīng)有了很好的解決方案,那就是瀏覽器端的和服務器端的。在一般的單機開發(fā)中(這里一般是指只有一臺Web服務器的情況),服務器端我們通常使用來存儲用戶登錄狀態(tài)(一般是一個自定義對象實例),在多數(shù)的管理信息系統(tǒng)開發(fā)中(畢竟內部系統(tǒng)用戶量不多,一臺Web服務器既提供Web服務又存儲對象內存還算是夠用的)這是很常見的。
但是,在大用戶量下,單機版的就會顯得效率低下,甚至會拖累Web服務器的性能。這是因為:每個用戶的Http請求發(fā)到服務器端后,每臺Web服務器的服務器軟件(例如:IIS、等)都會為該請求創(chuàng)建一個線程來進行處理和響應,但是一臺服務器同一時間可以接收的請求數(shù)畢竟是有限的(這個根據(jù)服務器的配置而定,例如CPU中i3、i5和i7類型分別可以創(chuàng)建的線程數(shù)都各不相同),當某個時間段出現(xiàn)高并發(fā)請求數(shù)的時候(比如:網(wǎng)購秒殺系統(tǒng)中經(jīng)常同一時間會出現(xiàn)海量的并發(fā)數(shù)),那這臺應用服務器將會接收前所未有的請求負載,最終可能會因為承受不了高負載而導致宕機,網(wǎng)站不得不停止服務。
這時旋風空間緩存完成后下載無速度,又想起了那句話:當一頭牛拉不動車的時候,不要去尋找一頭更強壯的牛,而是用兩頭牛來拉車。于是,我們可以采用服務器集群的技術來對Web服務器進行改進,增加N臺Web服務器部署相同的Web應用構成Web服務器集群來對外提供服務,通過負載均衡設備或軟件將海量的并發(fā)請求數(shù)平均地分攤到每臺Web服務器,例如:假設某系統(tǒng)在促銷活動期間同一時刻涌入了10萬個請求,而服務器集群中有5臺Web服務器同時提供服務,這時負載均衡設備就將這個10萬請求通過某種算法較為均衡地分配給其中的Web服務器,平均下來每臺服務器最多就只承擔2萬個請求。
通過服務器集群,已經(jīng)較好地解決了請求負載問題,這時新的問題又來了:由于默認是屬于進程內()的,也就是說它是存儲在Web服務器的內存里邊的。當構建好集群之后,用戶的會建立在負載均衡設備所分配的其中一臺Web服務器里邊。但是當用戶下一次訪問或者訪問系統(tǒng)中的其他子系統(tǒng)(比如:我首先在百度百科進行登錄了,然后訪問百度貼吧),由于會話還存儲在上一次提供服務的Web服務器里邊,系統(tǒng)校驗規(guī)則(現(xiàn)在這臺Web服務器里邊檢測到?jīng)]有該用戶的)會造成用戶的重復登錄(比如:都是在百度的網(wǎng)頁,它卻讓你登錄好幾次,你爽嗎?很明顯,不爽吧)。這時,就需要我們解決Web服務器集群的管理,下面我們就來看看如何進行Web服務器集群的管理。
1.2 應用服務器集群的管理
我們現(xiàn)在來看看在集群環(huán)境中,管理的幾種常見手段:
①復制:該方案簡單易行,集群中的幾臺服務器之間同步對象,任何一臺服務器宕機都不會導致對象的丟失,服務器也只需要從本機獲取即可。但是,該方案只適合集群規(guī)模較小的情況下。當規(guī)模較大時,大量的復制操作會占用服務器和網(wǎng)絡的大量資源,系統(tǒng)不堪重負。
②綁定:利用負載均衡的源地址Hash算法,總是將源于同一IP地址的請求分發(fā)到同一臺服務器上。這樣的話,在整個會話期間,用戶所有的請求都在同一臺服務器上進行處理,即綁定在某臺特定服務器上,保證總能在這臺服務器上獲取。(這種方案又叫做會話粘滯)。
但是,這種方案不符合高可用的需求。因為一旦某臺服務器宕機,那么該機器上得也就不復存在了,用戶請求切換到其他機器后因為沒有而無法完成業(yè)務處理。因此,很少有網(wǎng)站采用此方案進行管理。
③記錄:利用瀏覽器支持的記錄簡單易行,可用性高,并且支持服務器的線性伸縮,因此,許多網(wǎng)站都或多或少地使用了來記錄。但是記錄有缺點:比如受大小限制、每次請求響應都要傳輸影響性能、用戶關閉了會造成訪問不正常等。
④服務器:利用獨立部署的服務器(集群)統(tǒng)一管理,應用服務器每次讀寫時,都訪問服務器。這種方案實際上是將應用服務器的狀態(tài)分離,分為無狀態(tài)的應用服務器和有狀態(tài)的服務器。
從上面的幾種方式來看,各有利弊,但服務器是最符合高可用需求的方案,也是企業(yè)中經(jīng)常用到的方案。那么,對于有狀態(tài)的服務器,一種較簡單的方法是利用分布式緩存(如、Redis等旋風空間緩存完成后下載無速度,有關Redis的簡單介紹可以閱讀我的博文:NoSQL初探之人人都愛Redis)、數(shù)據(jù)庫等,在這些產(chǎn)品的基礎上進行封裝,使其符合的存儲和訪問要求。綜合上述介紹,我們今天就采用來構建我們的服務器,解決Web服務器集群的的共享訪問。
PS:為什么要采用分布式緩存方案而不采用數(shù)據(jù)庫來存儲?這個就得要分析一下數(shù)據(jù)訪問的性能瓶頸了,一般來說,磁盤IO讀寫的速度是最慢的,因為數(shù)據(jù)庫數(shù)據(jù)其實是存儲在文件中的,雖然目前大多數(shù)的數(shù)據(jù)庫都采用了B+樹結構,讀取一條數(shù)據(jù)最多都還是需要4次的數(shù)據(jù)讀寫(三次磁盤訪問獲得數(shù)據(jù)索引及行ID,一次數(shù)據(jù)文件讀操作,終于知道數(shù)據(jù)庫操作多麻煩了)。而分布式緩存例如是以Key/Value這種簡單的形式存儲在服務器的內存里邊的,內存的隨機讀寫速度是完爆磁盤IO的,因此內網(wǎng)+內存的雙內模式是比較完美的方案。
磁盤又分為兩種類型:
①機械硬盤:通過馬達驅動磁頭臂,帶動磁頭到指定的磁盤位置訪問數(shù)據(jù)。它能夠實現(xiàn)快速順序讀寫,慢速隨機讀寫。
②固態(tài)硬盤(又稱SSD):無機械裝置,數(shù)據(jù)存儲在可持久記憶的硅晶體上,因此可以像內存一樣快速隨機訪問。
在目前的網(wǎng)站應用中,大部分應用訪問數(shù)據(jù)都是隨機的,這種情況下SSD具有更好的性能表現(xiàn),但是性價比有待提升(蠻貴的,么么嗒)。
二、實現(xiàn)的分布式存儲2.0 案例總體預覽
(1)模擬的登錄案例場景
假設我們有一個基于ASP.NET的信息系統(tǒng),這個系統(tǒng)使用一個統(tǒng)一的系統(tǒng)登錄頁面進行用戶登錄,登陸后默認跳轉到一個用戶中心主頁,并顯示:歡迎您,{用戶賬號名稱}。
①系統(tǒng)登錄頁面效果:
②用戶主頁效果:
(2)模擬的技術體系選擇
ASP.Net MVC+EF Code First+MySQL+
2.1 初始準備工作
(1)新建一個ASP.NET MVC4的空項目,視圖引擎選擇為“Razor”即可;
(2)在項目中新建一個文件夾,取名為“Lib”,主要存放一些必要的DLL文件;
(3)在項目中添加對這幾個DLL的引用,注意這里引入.dll是為了支持后面的開發(fā)方式,EF版本必須在4.1及以上。PS:你也可以通過 來安裝。到此,我們的準備工作就做好了,接下來就可以開始正式的工作了。
2.2 借助EF 生成MySQL數(shù)據(jù)庫
首先,EF是一種ORM(- )框架,它能把我們在編程時使用對象映射到底層的數(shù)據(jù)庫結構。ORM框架負責把從數(shù)據(jù)庫傳回的記錄集轉換為對象,也可以依據(jù)對象當前所處的具體狀態(tài)生成相應的SQL命令發(fā)給數(shù)據(jù)庫,完成數(shù)據(jù)的存取工作(常見的數(shù)據(jù)存取操作可簡稱為CRUD:、Read、、)。
EF給數(shù)據(jù)庫應用系統(tǒng)開發(fā)帶來了更高的效率,使用它能更容易地寫出易維護、易擴展的系統(tǒng),而且性能雖然比不上ADO.NET,但也足夠好,能滿足大多數(shù)開發(fā)場景的需求。與ADO.NET不一樣,EF的抽象層次較高:它把數(shù)據(jù)庫映射為,把數(shù)據(jù)庫中存取的數(shù)據(jù)直接映射為實體()對象,屏蔽了底層的數(shù)據(jù)庫內部結構,無需直接使用下層數(shù)據(jù)存取引擎所提供的底層對象(比如ADO.NET所提供的,等)完成CRUD。
EF支持三種開發(fā)模式:Code First、 First和Model First。這里我們使用Code First模式,它能幫助我們實現(xiàn)快速開發(fā)迭代的目標。最后,EF不是本文的重點,如果你還不了解EF或者Code First,可以參閱金旭亮老師的《EF走馬觀花》系列文章,這里就不再贅述了。
(1)在文件夾新建一個類,取名為“”。它作為我們的實體類,映射到MySQL數(shù)據(jù)庫中的表(這里MySQL數(shù)據(jù)庫中還未創(chuàng)建這樣的數(shù)據(jù)表)
View Code
(2)再在文件夾新建一個類,取名為“”,使其繼承于,作為EF操作的數(shù)據(jù)庫上下文使用。需要注意的是:這里的name=,為數(shù)據(jù)庫的名字。而這里this..()方法則用于判斷這個數(shù)據(jù)庫是否已存在?如果不存在,那么就創(chuàng)建一個。
View Code
(3)在Web.中新增一個數(shù)據(jù)庫連接字符串,設置為“”,與上面的中的構造函數(shù)中的name保持一致。
View Code
至此,因為我們只用到這一個表,所以現(xiàn)在我們有關數(shù)據(jù)庫與EF的代碼就到此結束。
2.3 自己封裝幫助類對外提供服務接口
(1)在Web.中新增一個,保存服務器的地址列表:
(2)在中新建一個類,取名為“”,我們將其設置為靜態(tài)類,所以方法全是靜態(tài)的,我們無需實例化便可直接調用。可以看到,我們這里使用了靜態(tài)構造函數(shù)來初始化全局靜態(tài)對象,它不屬于任何一個實例,所以這個構造函數(shù)只會被執(zhí)行一次,而且是在創(chuàng)建此類的第一個實例或引用任何靜態(tài)成員之前,由.NET自動調用。
View Code
這里我們沒有對進行一列的配置,采用其默認的配置即可。但是要注意的是:的和的必須保持一致。
2.4 用戶登錄時調用幫助類接口存儲用戶登錄狀態(tài)
(1)在中新建一個控制器,取名為“”。主要用來顯示系統(tǒng)登陸頁和進行用戶驗證的AJAX操作(將用戶存入也在此操作中)。至于登錄頁面的HTML和JS腳本在此就不再贅述,請自行下載Demo文件查看。
View Code
現(xiàn)在來看下這個控制器的核心代碼:
①Index這個主要用于顯示登錄視圖,頁面代碼不再貼出來,只看看下面這個AJAX請求代碼:借助 AJAX向這個提交用戶名和密碼,如果服務器端校驗成功則返回一個JSON對象,瀏覽器端判斷屬性是否為true,如果為true則跳轉到系統(tǒng)主頁。
View Code
②這個則是根據(jù)瀏覽器提交過來的AJAX請求,判斷用戶名和密碼是否正確。這里用到了我們剛剛寫的,首先對它進行實例化。(這一步非常重要,這時我們的MySQL數(shù)據(jù)庫中還木有這個數(shù)據(jù)庫,當?shù)谝淮螌嵗瘯r,EF會幫我們在MySQL中創(chuàng)建這個數(shù)據(jù)庫,其本質其實就是幫我們生成一串:crate ;之類的SQL語句),然后使用表達式這種優(yōu)美的語法去進行校驗。如果你對表達式不熟悉,可以參閱MSDN的《表達式(C#編程指南)》一文來學習下。
③就是最核心的部分,我們采用Guid(保證作為Key的唯一性)作為寫入瀏覽器端的中,并以此作為Key存入分布式緩存中,還給它設置了默認失效時間(這里為20分鐘)。之后,每次瀏覽器向服務器端提交請求時,在HTTP報文中都會附帶上這個,服務器端就可以通過這個作為Key去服務器中查找對象。
PS:一般來說,的數(shù)據(jù)Key的命名是有講究的,這里傳智的老馬推薦了一個命名規(guī)則:{命名空間}-{部門名稱}-{項目名稱}。我們這里就簡單一點,使用Guid來作為Key,因為每次生成的Guid是唯一的。
2.5 封裝解決觸發(fā)前的校驗規(guī)則
(1)在以往的信息系統(tǒng)項目開發(fā)中,我們在系統(tǒng)里邊會做一個全局的校驗器,判斷用戶的每次操作請求是否具有相應的權限,這里我們主要是校驗用戶是否登錄,以便我們在具體的模塊中獲取用戶的對象。單機情況下,我們一般就存入本機進程內的,因此就主要判斷中是否存在登錄狀態(tài)。這里我們使用來存儲對象,那么我們就在每個執(zhí)行前加一段規(guī)則:判斷中是否有當前用戶的登錄狀態(tài),如果有,則繼續(xù)執(zhí)行。如果木有,那么對不起,請進行登錄。
(2)在中我們可以通過寫一個,使其繼承于Page,重寫事件,再讓其他頁面繼承于,就可以對用戶是否登錄這個校驗應用到所有繼承了的Page類中。那么,在MVC模式中,請求對象不再是xxx.aspx頁面類型,而是//的路由,因此我們需要尋找一種針對的全局過濾方法。額,在此我們不禁想到了一個高大上的英文:AoP( ),它是軟件開發(fā)中一個較為熱門的話題,利用AOP可以對業(yè)務邏輯的各個部分進行隔離,從而使得業(yè)務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發(fā)的效率。因此,我們對增加全局校驗規(guī)則就是將的校驗方法與的業(yè)務處理相分離,使其耦合度降低,也提高了的校驗方法的重用性,符合了AoP的思想。那么,扯了大半天,在ASP.NET MVC中到底如何實現(xiàn)呢?別擔心,我們可以像寫一樣,寫一個來進行處理,使其繼承于,然后將其他需要使用用戶登錄狀態(tài)校驗的都繼承于即可。
View Code
(3)現(xiàn)在來看看上面這段代碼: