哈佛公開課:構建動態網站——第九講 可規模性

1.工具Memcached Daemon,如果當生成很多無需每次請求都更改的動態內容,可以將php等代碼生成的html緩存起來,而不用像通常那樣用戶每次訪問foo.php,都需要重新解釋代碼然後重新生成html。比如維基百科,看的人肯定比寫的人多,因此只需要等有人編輯後才需要用php之類的程序重新生成html。

2.以下書籍將何爲可規模性及如何實現可規模性


3.構建RAID,有不同的RAID級,比如RAID0,硬盤分割,可以讓數據一半放在硬盤A上,另一半放在硬盤B上,這能增加運行效率。

4.垂直規模化就是指提升cpu速度,存儲容量等等,但這會受現實世界的限制,cpu在這個時期內總有最快速度上限。

   而水平規模化,就是購買很多廉價服務器,然後通過軟件設計,將負載分到這些服務器上。比如google,當你使用google搜索時,你的請求任務不是發送給一臺服務器,而是被分成多個小塊,然後把各個小塊的工作分到多個服務器上去處理。開源軟件Hadoop,它能讓你實現所謂的mapreduce,這是一種計算機科學編程技術,它能將問題分解成小塊分出

5.一些軟件可以用於緩存解釋php文件完成後生成的html,來直接提供給用戶,避免每次用戶訪問都需要服務器重新解釋一次。


6.當我們在代碼中加入這些東西后,接下來要做的就是將代碼分到多臺服務器中去。我們現在有一個CDN服務器供視頻使用,還有一個CDN服務器待命隨時可以分擔流量。

SSH到cloud.cs75.net時也有多個登陸服務器,在登錄時有時會到login0,有時會到login1,如果我們開啓了的話甚至可能到login2 ,login3,login4等等,這些都是配置相同的不同虛擬機,都可以登陸到你的帳戶上(你的文件夾等等),但實際上他們是不同的虛擬機,並且一般都在不同的物理機器上。

     此時就有出現問題了,當你SSH到cloud.cs75.net時,我們會將你的通信發送到其中一個機器,這就要使用所謂的負載均衡器,很多大流量網站都用這個。

      使用這個技術的原因一,比如哈佛大學某個課程有兩個班總共700多個學生,在項目提交當晚,多VM機制能幫助我們提高性能,這樣大家的響應時間會更快。

原因二,如果出了什麼漏子,可能有人跑了死循環讓其中某臺服務器掛掉,我們仍然需要整體能夠順利運行,所以說負載均衡還能有效實現冗餘。

當某用戶通過因特網連接到這裏,然後回到下圖黑箱中,即負載均衡器,在負載均衡器之後的就是N臺web服務器。然後這個負載均衡器是使用的輪詢調度,也就是說第一個請求分配到srv1 第二個請求分配到sev2這樣。當然還有更好的分配方法,而這裏用的是輪詢調度這個分配方法。

但如何實現黑箱中的負載均衡呢,肯定不會僅僅是維持計數功能這麼簡單,他是如何接受請求,檢查計數,然後發送到別的服務器。DNS是一種選擇,第一講談到過了A記錄和CNAME記錄,域名映射到特定IP地址。

還可以使用虛擬IP地址,這也是哈佛用的方法,當SSH到cloud.cs75.net,通過TCP/IP協議會到達一個稱爲代理的虛擬機,這是一臺運行linux的虛擬機,上面有款免費軟件叫HAProxy (high-availability proxy),配置起來很賤,只用配置一個文本文件,這個軟件會一直運行,在不同端口監聽TCP連接,哈佛就開了三個端口,其中端口22是SSH,80是web, 443是SSL。因此看cloud.cs75.net的IP地址它不是映射到login1或login0,而是到所謂的代理裝置,也即是一臺運行HAProxy的Linux機器,它實現了輪詢調度的功能,該軟件讓你能夠用TCP連接到他,並且他會瞭解到用戶需要端口22,而我們有兩臺提供端口22服務的VM也即login0和login1,因而會通過輪詢調度向login0或者login1建立TCP連接,基本上,你又了某種隧道,這不是VPN,因爲沒有加密,只是創建一個TCP隧道,讓用戶的通信能通過黑箱,到達後端的某個設備上,然後用戶就能得到相應的響應,可以是登陸提示或者屬於該用戶的主目錄,也可以是web情況下的http響應。

這又什麼好處呢,如果我們需要讓其中一臺vm停止運轉,比如現在login1沒了,我們讓他停止爲了升級其所在物理機器的內存,此時編輯負載均衡器的配置文件,在這之後所有人都不會被髮往login1的ip地址,當晚上不再有學生連接進來的時候,就關掉電源,插入內存,還原配置文件等等,這樣第二天新的通信又可以到這個vm上了。

這和nat很類似,但進行代理的並不是nat,有其他設備實現nat,下圖黑箱之後應該還有個路由器來實現nat,圖上沒有畫出來,黑箱不需要知道nat的存在。而且我們還有個應急方案,就是當vm負載過大時,會轉發到我們在亞馬遜ec2的賬戶,那裏有vm,因此可以在用戶毫不知情的情況下將用戶的入站連接發給亞馬遜ec2雲服務器.

無需在單獨的VM上運行SSL,有種專門爲SSL計算優化的硬件,讓所有人都能通過它連接到不同的設備上,也就是在這個硬件之後連接到其他後端服務器都無需經過加密,畢竟我們這裏的可信度較高,不像因特網上的情況那麼複雜。

關於輪詢調度還有個問題,就是當人們的請求依次被髮送不同服務器如此往復,這對SSH還行,因爲大家的連接會保持固定,沒人會關心具體連到哪個機器,但在本課中講過session這是通過存放在/temp中的文件來實現的,這是本地硬盤上的一個目錄或者說一個文件夾,它只對應特定機器,這意味着當我已經在購物網站登陸完成後,此時重新連接,那麼有概率會被輪詢調度到別的服務器上,而別的服務器上並沒有我的session,因此就會很麻煩,比如我剛纔在這購物網站中點了一堆要買的物品進購物車了,那麼此時就全都沒有了。

負載均衡器分配通信還有另一個問題,雖然理論上每個機器能夠處理一半負載,同時如果一個掛掉,另外的也能補上,只是掛掉的那臺服務器上的用戶可能遭殃需要重連接或者丟失未保存的信息,但至少整個網站系統能夠正常運行,這個問題就是輪詢調度完全沒有顧及服務器內的情況,在linux系統中可以通過命令查看使用最多內存和cpu的進程,而負載均衡器卻對這些都一無所知,比如有個人被隨機分配到一臺服務器上而這人進行了大量的運算,此時還有用戶連接進來依然同概率被分配過來,按理說應該提高分配到其他服務器上的概率,才能是更好的均衡。同理比如一堆老手成天泡在服務器上跑程序,而他們恰好都被分配到同一個服務器上,而另一堆菜鳥不會常泡在服務器上卻被分配到另一個服務器上,那麼此時老鳥們所在服務器的負載就很大。所以說輪詢調度雖然不錯,很簡單,普通情況它也能應對,但極端情況就無法處理了,甚至某些消耗cpu的通常情況它都很吃力。當然還有些其他負載均衡技術能和後端服務器之間形成雙向聯繫,負載均衡器每隔幾秒就會詢問後端服務器負載多少,用戶數多少等等,然後根據回答來確定如何分配用戶。

但冗餘依然是個問題,我們有冗餘www0, www1或login0, login1,但這裏負載器沒有冗餘,一直在宣揚冗餘的好處如果某臺機器掛掉了依然可以高枕無憂,但如果運行負載均衡軟件的vm掛掉了,那所有人都完了,後端服務器都在等待連接,但沒人連的上,因爲nat的關係,所以因特網的用戶無法通過ip地址訪問它們,因爲這些ip地址是私有的。

那麼怎麼解決這個問題呢?我們先拿掉負載均衡器,也就是不要它,可以怎麼做呢,剛有同學說用DNS,這確實可以。如果後端SSH和web服務器有多個,我們或許可以不用nat而使用共有ip地址,此時我們使用這個同學講的DNS作爲“負載均衡器”,DNS能爲特定域名設置多個ip地址,而dns服務器自身能進行輪詢調度類似的功能,比如上圖的情況用dns服務器作爲黑箱,此時域名cloud.cs75.net就不僅只有1個ip地址了,而有多個ip,每個ip指向不同的vm,而且dns服務器有多臺的,何況還有根服務器,所以在冗餘方面還是不錯的。但是改用dns服務器作爲負載均衡器的缺點就是它和後端服務器之間沒有雙向溝通,因而無法考慮實際上各個服務器的負載情況。

講例子。有個網站www.don.com,沒有用第三層或第四層的網絡層 負載均衡,而是用第五層或第七層的負載均衡,也即是應用導向的負載均衡,那麼當你訪問站www.don.com時,網站會有php這之類的語言發送http重定向location:header信息,然後將你送到服務器站www1.don.com或站www2.don.com等等,這樣的負載均衡有點可憐,只用了不到3行代碼。這樣的問題就是,當有用戶在www3.don.com時收藏了這個地址,而後來網站關閉了www3.do.con,那麼這個用戶如果是非專業的話,直接使用收藏地址就無法訪問了,他們會認爲這個網站關閉了,然後也許還存在前面講的session存儲在另一臺www1的服務器上,而用戶重新連接後卻訪問的是www2。

回到DNS的問題上,很多dns服務器都是用免費軟件BIND,下圖中的4個IP是www.cs75.net擁有的,這是使用標準 dns服務器完成輪詢調度。


現在的問題在於如果通過均衡負載,讓用戶到不同服務器,下次再通過http連接時,他可能被分配到其他服務器上去,這樣就得不到原來的session了。

一,可以使用共享數據庫,至少php支持mysql等數據庫作爲後端以此替代/temp,只要所有web服務器能連接到此數據庫,那麼session數據就能存在這一個中心數據庫中了。但解決一個問題往往意味着一個新問題的誕生,這種方案很麻煩,使用單一數據庫是最簡單的易於實現易於維護,但單個就損失了web服務器冗餘的優勢,此時web服務器或ssh還有冗餘的,但數據庫同/temp目錄比起來則是沒有冗餘的。

數據庫當然可以有冗餘,但我們還有別的辦法,一個同學提出可以用cookie,cookie本身是個很大的隨機數,可以把隨機數頭爲數字0-5之間的分配到這個服務器,另一個範圍的分配到另一個服務器,這實現起來不困難,因爲負載均衡器會查看入站請求,很多商業負載均衡器都是這麼做的,即網入站和出站http請求中插入他們自己的cookie,這不是由服務器設置也不是由客戶發送的,而是作爲中間人插入進來跟蹤走向。

還有什麼辦法能讓用戶始終被分配到相同服務器?cookie太容易被黑了,我們其實可以使用源IP,然後哈希任何人的IP,然後用這哈希表來將它們接入到login0,login1,login2或,login3等等其中之一 ,這樣至少能讓相同用戶到相同設備,cs75這門課就是用的這種方法,對這門課參與的學生來說問題不大,因爲只是http,但另一門課要讓學生在命令行運行編譯器,調試器,這也需要連接到相同的服務器,因此如果某學生在這裏開了個ssh窗口在服務器A上,又在服務器B上開了另一個程序,然後試圖用A來調試B的那個進程,這是不行的,因爲進程id並非存在於兩個服務器上的。

那麼還有什麼辦法呢,我們不一定要引入數據庫,還是可以用/temp的,我們只需要在本地硬盤讓/temp成爲共享存儲,即所有系統均可見的硬盤空間,而在哈佛雲服務器上就是用了這個方法,不管用戶是登入的login0還是login1,都可以看到自己的主目錄,這得益於NFS技術(在unix和linux系統中存在很久了,甚至mac都支持),全稱爲network file system(網絡文件系統),它和windows以及蘋果的文件共享很像,可以像本地內容一樣瀏覽外部硬盤內容。但NFS也有缺點,所以如今最常用的還是iSCSI協議,它能在以太網中實現存儲共享,講到SAN, storage area network(存儲區域網絡),通常就要用到一個或多個這種協議,設置的做法就是,把所有web服務器數據庫服務器等弄好之後,若還需要更快更大的硬盤空間在不同服務器之間共享,可以弄一個單獨設備它具有多網卡插入,這個共享存儲設備有多個大容量的硬盤,然後把這個設備加入網絡交換機,然後通過網絡共享。

實現負載均衡方法很多,難易程度各不相同,哈佛現在用的免費軟件HAProxy,我們有兩臺ssh登陸服務器登陸大家的主目錄,web服務器則完全分離。所以cloud.cs75.net雖然是一開始你的ssh連接到的地方,是你在80端口或443端口上的web 請求最開始到的地方,但HAProxy會根據目的端口將其分發到不同的機器上或者將mysql數據庫通信弄到不同vm,即LAMP1 Linux Apache MySQL-1,這樣做主要爲了防止學生在折騰時不僅弄掛掉的登陸服務器還弄掛掉數據庫或者web服務器。另外它還能讓我們運行其他版本的os軟件和工具,我們可以根據代碼選擇不同版本的操作系統。並且還有個好處,當想要第2或第3或第4臺web服務器,還是一樣只需要在HAProxy中再設置一些即可,它會爲我們實現負載均衡。


LVS,Linux虛擬服務器是一個熱門的選擇,可以在網絡層實現負載均衡。但建議大家選擇HAProxy開始入門。

7.接下來從軟件層面考慮如何提升性能,實現規模化。回到以前講在mysql會議討論的事情時講到的緩存的事,那麼有哪些可以緩存呢


首先,課程中PHP用的很多,顯然我們能對它們解釋後的結果進行緩存輸出,而不用計算機每次重複編譯解釋相同的頁面。

其次,mysql中,如果你不斷選擇相同的數據 ,也是可以讓其記住最近輸入和輸出的,讓其遇到相同問題時,如果表格或行沒有變化,就不要再到數據庫折騰了,直接給出答案。

上圖中.html,直接使用靜態頁面好處就是有效率,但壞處也很多。

再來看看mysql查詢緩存,要想開啓這個功能直接在my.cnf文件中插入這一行:query_cache_type = 1即可。一般這個文件在/etc裏面。但缺點之一就是對於一般的網站沒有大量用戶訪問的,沒有爆炸性新聞會使得短時間內有大量用戶請求的,那麼可能存在的問題就是當緩存滿了。刪掉一些,之後又收到請求,那請求剛好請求的是刪掉的那些。所以有無法控制的缺點。

接着在看看memcached,如果你剛使用php生成了一些html,你可以將所有html捕獲到一個變量內,也可以將其存到緩存內,又或者你查詢數據庫請求比如用戶id,資金等等很多數據,但是又不想反覆詢問數據庫同樣的問題,類似的,不僅可以放入html字符串,還可以存放php對象到這種數據庫中,只要對象能夠序列化,只要它能表示爲字符串能轉化爲某標準格式,這樣做的php代碼大致就是如下這樣:

$memcache = memcache_connect(HOST, PORT);  //memcache_connect此函數明確主機和端口
$user = memcache_get($memcache, $id); 	// memcache_get函數在緩存中通過鍵值查詢值,這裏取得的是id爲$id的用戶
if (is_null($user))  //如果得到null表示緩存中沒有此用戶
{ 
 mysql_connect(HOST, USER, PASS);  //查詢mysql數據庫
 mysql_select_db(DB);
 $result = mysql_query("SELECT * FROM users WHERE id=$id");
 $user = mysql_fetch_object($result, User); //然後有用戶對象
 memcache_set($memcache, $user->id, $user); //將此用戶對象緩存起來,理想情況存到內存中
} 



或者下面這樣

$memcache = memcache_connect(HOST, PORT); 
$user = memcache_get($memcache, $id); 
if (is_null($user)) 
{ 
 $dbh = new PDO(DSN, USER, PASS); 
 $result = $dbh->query("SELECT * FROM users WHERE id=$id"); 
 $user = $result->fetch(PDO:FETCH_ASSOC); 
 memcache_set($memcache, $user['id'], $user); 
} 
通話就項目而言,儘量少接觸數據庫是好事,有多個數據庫是好事,但同時也有多個緩存服務器總沒錯,這樣能有效避免很多昂貴的操作,即使在小規模環境下也是如此。儘量少接觸硬盤,這是最理想的,儘量將東西存放到內存,所以我們雲服務器有48G內存,我們想將所有的主目錄及文件儘量放到內存中這樣才能更流暢。

所以memcached是很棒的一個工具。

8.最後講講mysql本身,之前講過表格設計,用過主鍵和外鍵,不過這裏還有一些需要做決策的,當存儲量達到10萬推文這麼大時,顯然需要考慮使用哪種數據庫引擎,如何定義鍵,如何搜索等等。因此在mysql中,大家應該瞭解所謂的MyISAM,它是mysql默認存儲引擎,存儲引擎相當於數據庫中的文件系統,比如說操作系統有NTFS,HFS+,EXT3這些文件系統,而MyISAM則是數據庫存儲數據的格式,另一個很常用的叫InnoDB,它支持事務(transactions),這能讓你鎖定數據而且是在行層面,無需像MyISAM中那樣鎖定整個表格,只在單行處防止同時讀寫顯然更高效,當表格很大,很多用戶同時要用時,這顯然更好,除這兩之外還有其他引擎,這裏列了兩個

HEAP存儲引擎,即內存存儲引擎,它爲只存儲在內存中的表格所設計,對於臨時性的內容,這顯然很棒,如果你只想臨時性存儲,快速搜索,只要不關機,都不用擔心數據丟失,那你就可以創建這種HEAP表,一切都一樣,行列,鍵等等,只是要告訴數據庫服務器存到內存。

然後還有NDB,用於集羣,這門課不會講這個,反正mysql支持某些複雜的集羣環境,比我們今天講的要複雜。

同一數據庫內,每個表可以有不同數據引擎,所以在設計表格時,可以考慮各個表格用什麼引擎,有時用InnoDB它支持事務,有時用MyISAM它性能更好對特定操作更高效。

下面這圖摘自mysql文檔關於各個引擎的不同。關於外鍵就是某張表中有鍵,但它是其他表格中的主鍵,而不是這張表的主鍵,InnoDB的好處就是有外鍵的概念,我們可以創建很多有趣規則,這些就是DBA(數據庫管理員)所做的,可以標記某表中的字段,依賴於其他表中的字段,於是不用寫任何php代碼,就能讓數據庫具備自動關聯式,比如我刪除用戶,數據庫就會自動將該用戶所有表中的相關行都刪除,這樣一來最後想讓表格保持一致,連代碼都不用寫,


那麼如何將這些數據庫服務器連起來呢,之前提出的問題是我們只用了一臺數據庫服務器,和負載均衡器一樣,這是單故障點,這當然能解決,我們只是圖方便。其實,往結構中引入從服務器也並不難。下圖是數據庫層實現冗餘的一般結構除了主服務器1外還有多個從服務器與之相連,任何時候數據寫入主服務器1的時候,會同時複寫到2-4號從服務器,但反方向則不行,所以主從關係是指從服務器是讀出還是寫入,寫入進主服務器,要讀的時候從從服務器中選其中一個來讀。這樣能處理越來越大的讀請求,在mysql中這樣做,一般需要進行一些網絡及防火牆設置,告訴主服務器寫入從服務器,從服務器接收,不過這裏還是有單故障點。


我們用一種明顯方式來解決它吧,若主服務器是單故障點,那就弄兩臺,如下圖,兩個主服務器相互連接,只要有數據寫入左服務器,右側服務器也會寫入。課程中只介紹了最簡單的php對於mysql的api,叫做所有的函數叫做mysql_connect,使用這些函數就能維持多數據庫連接,所以代碼中可以分開寫成一個寫連接和一個讀連接,這就能在代碼層面簡單實現控制,如果要做的更好一點,可以讓所有選擇發到這裏,而讓所有插入更新刪除發到那裏,


如果需要在主服務器1和2之間做選擇,因而可以同正確的對應於該主服務器的從服務器通信,可以硬編碼到代碼中,不過通常最好提出來,我們沒有讓名字開頭字母是什麼的ssh到這個服務器,而其他字母的到另一臺服務器,對用戶及代碼隱藏這些最好,理論上可以用之前講的一些包含負載均衡器的結構,如下圖:用戶發送來請求會由負載均衡器隨機分配到後面的web服務器上,然後若用戶是讀請求則連接到從服務器的負載均衡器上,若是寫請求則連接到主服務器上。


這裏還有要當心的特別是我們需要避免競態條件,有幾種辦法,一種是分割,fb一開始就是這種辦法,那時候其URL形如harvard.facebook.com,mit.facebook.com, bu.facebook.com,扎克伯格最開始的情況是所有代碼和數據庫服務器都在一個學校,之後他們要去別的學校,最簡單的辦法就是創建另一個子域名,然後用cp -r遞歸複製所有代碼並拷貝數據庫,然後以這種方式分割不同學校,一開始facebook限制用戶在自己的學校,這樣區分對性能有好處,也有可能所有這些還是在一臺服務器上,只是用不同數據庫和代碼文件夾,這樣的好處就是可以將mit.facebook.com這些移到不同服務器,從而實現水平規模化,這只需要增加一些有空餘週期的不同服務器,而不用修改代碼,然後用一些域名技巧,就讓用戶映射到了別處,不管按學校,名字還是別的什麼來分開用戶,通常稱作分割(Partitioning),這在數據庫中很常用,能將用戶分開以避免一些問題,比如競態條件之類的,而且仍然能夠橫向增長並處理多個用戶,不過像下圖這樣所有a-m的到左邊,所有n-z的到右邊有缺點,facebook的例子是來自harvard的域名到這裏,來自mit的域名到那裏這樣,當然也可以根據用戶登錄的郵箱的結尾來分,但當沒有這種明顯的方式分割用戶,那麼就需要更多的工作了



下圖中有負載均衡器連接兩臺主服務器,兩臺主服務器相互複製,負載均衡器不僅是盲目的將用戶用戶輪換送到A和B,它可能不知道每臺服務器的負載,但它顯然需要不斷ping兩臺服務器A和B,來看它們是否還在運行,當某服務器停止響應時,它就應該停止向這服務器發送用戶,HAProxy就是這樣做的,所以得益於不斷ping,因此有了高可用性



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章