1. 程式人生 > >後端架構-架構層面

後端架構-架構層面

架構層面:
日誌集中
所謂日誌集中就是把程式的所有日誌和異常資訊的記錄都彙總到一起,在只有一臺伺服器的時候我們記錄本地檔案問題也不是最大,但是在負載均衡環境下再記錄本地日誌的話就出現問題了。在想檢視網站日誌的時候到哪臺機器去查都不知道,難道有100臺機器就100臺機器逐一遠端連上去看?因此,把這些資料彙總在一起儲存對於大型網站系統來說是很必要的,這樣我們就可以直接進行檢視、搜尋,也很明確可以知道是哪臺機器的業務出了問題。至於這種日誌資料是寫到RDBMS還是NOSQL甚至是搜尋引擎這就看需要了,總之避免寫本地的文字檔案,否則真的是災難。當然寫一個日誌遠遠沒有想的這麼簡單:

為了達到比較好的效能,日誌是否先寫本地記憶體佇列然後定時刷到資料庫中去?
各種日誌混在一起也難以搜尋,是否要新增一些搜尋欄位?比如分模組?
如果資料庫不可用的話是不是先寫本地日誌以後再彙總過去?
日誌是否易於辨明問題還是看日誌怎麼記錄,如果日誌中只寫錯誤那麼記錄了也白記錄,一定要寫清楚這是哪個模組哪裡出現了問題,有條件的話還可以寫上一些引數資訊和當前的狀態。
對於未處理的異常資訊不太可能手動去記錄,一般而言很多框架或伺服器都會提供一個介面點可以回撥我們自己的程式碼,在這裡我們就可以收集這些未處理異常然後統一記錄,最後把使用者帶到友好的錯誤頁面。不記錄任何一個未處理異常都是可怕的事情,想象一下使用者已經看到了色彩斑斕的頁面,而開發還一無所知,這樣的話這個問題始終會存在,即使使用者投訴了反饋了我們也很難重現。

配置集中
配置集中和日誌集中的道理是一樣的,就是統一管理。任何一個系統其實或多或少都會有一些不能在程式中寫死的引數(比如至少的資料庫連線字串和外部服務地址),一般情況下會寫到配置檔案中,這樣存在幾個問題,第一就是在多伺服器的負載叢集情況下要修改配置需要逐一修改每一臺伺服器的配置,第二就是在修改配置還可以需要重啟服務或網站才會生效,第三無法統一管理也無法知道是否所有網站都統一配置了相同引數。解決的辦法還是一樣的就是彙總儲存在統一的地方比如儲存在資料庫中,然後每一個網站都從資料庫中獲取配置的值,在實現的時候簡單有簡單的做法,複雜有複雜的做法,比如要考慮以下問題:

引數的值是直接儲存強型別的那還是儲存字串在使用的時候轉?甚至說值是支援物件和陣列的,而不是簡單型別。
引數的值不可能每一次獲取都從資料庫取,怎麼做快取,快取多長時間,值修改了怎麼同步回來?
程式是否允許修改值?還是說程式只是讀取,不允許修改,修改引數的值只能通過資料庫或後臺進行。
是否是需要根據不同的部署環境、使用者的語言、伺服器的IP來設定不同的值。
不管怎麼樣,至少一個最簡單的配置服務,從資料庫中讀取引數值,哪怕讀取後永遠快取只有重啟服務才能生效,也會比直接從本地配置檔案中讀要好很多。示例分享

快取
快取這個架構手段實在是太常用了,幾乎所有的人都知道快取這個設計和效能優化手段。對於一個網站系統來說又有太多的地方可以做快取,真正能把快取用好,在合理的地方用快取,監控快取的命中率,想辦法提高快取的命中率其實不是這麼容易的,一般來說有這些地方可以做快取,從上到下:

瀏覽器和CDN快取:通過這兩種整頁的快取可以儘量減少請求打到網站伺服器的機會,也就提高了網站伺服器的負載能力,當然一般來說靜態的資源比較容易走這種快取,動態的資源其實也可以走,只不過要能根據訪問的條件做出合適的Key,對於和每一個使用者都獨立的頁面可能要直接進行頁面快取比較難一點。
反向代理的快取:反向代理作為伺服器的代理可以進行整頁或是片段頁面的快取,可以提高直接把請求達到網站伺服器的機會提高效能。
資料的快取:如果請求真正到了網站伺服器,那麼我們也不一定要所有的資料都從資料庫中取,可以嘗試把部分資料儲存在分散式快取中。只要Key合理,並且請求有規律那麼可以保證比較高的命中率,從而減輕資料庫的壓力,也減輕網站伺服器的壓力。
大塊資料的記憶體中快取:對於有一些大塊的資料是無法儲存在分散式快取中的,那麼可以直接在網站啟動的時候把這種不太會改變的大塊資料全部加到記憶體中來,這樣這快資料的訪問效率和計算效率就很高了。

分散式快取
所謂分散式快取就是快取的資料是分佈在多個節點上的,好處是一來可以儘量利用伺服器的資源,比如一臺伺服器可以有1GB記憶體空閒,找50臺伺服器就是50GB了,如果不用這50臺伺服器其實也就是這麼多記憶體空著(一般來說網站伺服器使用的記憶體是相對固定的,主要業務不怎麼變化的話,而且諸如Memcached之類分散式快取對CPU的使用是很低的,完全可以把Memcached寄居在大記憶體的Web伺服器或是應用伺服器上,實現神不知鬼不覺的分散式快取);第二個好處就是可以減少單點故障帶來的影響,一般來說分散式快取都有類似於一致性雜湊的演算法,即使有單點故障的話也只是少部分快取資料會不命中,損失不是太大。在使用分散式快取的時候要牢記下面幾點:

快取就是快取,資料是允許排出和丟失的,當成檔案系統用的話就錯了。
分散式快取的資料是通過網路存取的,資料傳輸走網路和本機記憶體中效率不能比,而且資料需要序列化和反序列化要考慮到效能開銷。
快取的Key生成的策略是很關鍵的,Key生成的引數過多的話很可能命中率會幾乎為0,這種快取做了也白做,因此需要針對分散式快取的命中率有監控。示例資源

佇列
佇列也是實現高效能架構的一個必不可少的利器,通過把執行時間比較長的任務在佇列中進行排隊,通過限制佇列的最大容納專案數,實現一個抗高壓的能力,並且佇列後端的點也能有一個比較平穩的壓力。佇列說到底也只是一個容器,如果前端的壓力永遠比後端處理能力大的話,佇列總是要爆的,因此佇列也不是萬能。往往對於網站系統即使後端的業務有佇列,前端的頁面如果扛不住高壓力,甚至是驗證碼之類的都刷不出來的話也是沒用的,架構上就是這樣系統中任何一個點都會拖累整站的架構,架構優化針對最薄弱的地方而不是最強的地方。形式上來說佇列有兩種:

生產者和消費者:生產者生產出來的資料只能被一個消費者消費,也就是任務只能執行一次的。往往這種形式的資料是需要持久化的。
釋出和訂閱:任何一個事件都允許有多個釋出者和多個訂閱者,訂閱者訂閱自己感興趣的東西,只要釋出者釋出了訂閱者感興趣的內容就會傳播到所有的訂閱者。一般來說這種形式的資料可以是允許丟失的不需要持久化的。

池技術
所謂的資料庫連線池,執行緒池都是池技術的一個應用。池技術說到底就是把建立開銷比較大的物件快取在池中避免重複建立和銷燬,物件用的時候從池拿出來用,用好了重置後還到池裡面別人還可以接著用。比如說資料庫連線池就是避免了頻繁建立代價高的TCP連線,執行緒池就是避免了頻繁建立代價高的執行緒。雖然說原理上是這樣,但池其實也有一些演算法需要考慮的:

建立的富裕的物件的回收策略怎麼做?
物件損壞怎麼處理?
一般來說可以參考網上的池實現方式來實現一個通用的池,這樣各種物件都可以進行管理了。

分散式檔案系統
分散式檔案系統並不是所有網站都必須的,一般小網站會把使用者上傳的圖片直接儲存在Web伺服器本地,這麼做是可以的,但量大了之後會有問題,首先一臺伺服器儲存不下怎麼辦,怎麼知道哪個圖片在哪個伺服器上?其次讀的請求怎麼進行分離,怎麼把圖片同步到其它伺服器上去。分散式檔案系統就是來解決這個問題的,通過把一組伺服器當做一個檔案系統使得我們的檔案資源可以分散儲存在多個伺服器上並且確保有一定的資料備份。在網站規模不是很大的時候其實可以儲存在單臺伺服器上,然後使用檔案同步工具同步到另一臺伺服器,之前再架反向代理解決,資料量再大一定要分散式的話就要上分散式檔案系統了。從原理上來說分散式檔案系統其實不是很複雜的,但選型的時候也要進行穩定性和效能的考量。有的人是把資料庫儲存在資料庫中的,雖然這樣可以實現單點雖然這樣可以實現備份,但這顯然不是很合理的,會極大增加資料庫的壓力。

分散式搜尋引擎
如果有站內搜尋需求的話就要上搜索引擎了,現在開源的搜尋引擎非常多,不過很多都是Lucene的封裝,在選型的時候要根據自己的需求進行選型。所謂分散式也就是如果單點的索引和查詢不能滿足效能容量需求的話就需要分佈到多點了。從原理上來說搜尋引擎主要還是一個分詞和倒排索引,但是搜尋引擎在內容的排序等細節上點還是有很多演算法的,除非必要不推薦自己去實現搜尋引擎,可以直接針對開源元件進行封裝和改良。搜尋引擎做的好其實遠遠不止全文搜尋這麼簡單,甚至可以根據使用者搜尋的內容給予搜尋的建議,可以根據使用者搜尋的內容進行索引的自完善,還可以根據使用者的搜尋結果進行大量的使用者行為分析,為網站的產品進行改良,如果站點具有自己的搜尋模組的話其實有很多事情可以做的。示例資源

NOSQL
NOSQL就是非關係型的資料庫,NOSQL不是用來取代關係型資料庫的,之所以NOSQL這麼火是因為其效能。從本質上來說,程式就是一組程式碼,對於相同的硬體配置來說,程式效能的高低也就取決於實現相同的操作要有多少程式碼在CPU中執行一遍,要有多少IO操作要在磁碟上過一遍,往往通用性的東西就會有比較多的計算和IO,往往定製化的東西效能就會比較高。我們說RDBMS效能不高,但是我們也應該看到RDBMS要確保資料的完整性,永續性,要實現通用的功能,要把它的效能和IO達到記憶體中然後定期刷磁碟的元件來比就不合理了,因此我們要根據業務在合理的地方使用合理的NOSQL,對於資金相關的業務需要事務的業務不太適合NOSQL,對於允許延遲允許資料丟失需要大訪問量的業務比較適合NOSQL。任何一種NOSQL往往都是某個大型公司針對自己的需要定製出來的產品,只有這樣定製化的東西才可能實現高效能,因此市面上才會有這麼多NOSQL,那麼我們在選擇的時候也要看這個NOSQL針對的應用場景是否就是符合我們業務需要的。另外,NOSQL逼近是一個新新事物,其使用者群不可能有MYSQL或ORACLE這麼多,因此我們也不能期望其穩定效能達到非常高的標準,而且NOSQL由於其定製化的特點,在使用的時候不一定都可以通過標準SQL來使用對於學習成本也是我們需要考慮的因素。不管怎麼樣在該需要用的時候還是要用,有的應用單靠RDBMS是沒有可能實現這麼高效能的,不用NOSQL就是死路一條,用了即使它不穩定還有可能活。

NOSQL之Mongodb
Mongodb是一款效能超高的文件型資料庫,之所以它這麼火不僅僅是因為效能高,而且它功能也很全,相比其它NOSQL它幾乎可以實現90%以上的SQL操作,並且也有豐富的高可用性的配置方式。總結下來Mongodb是一款效能幾倍於傳統RDBMS的最接近於RDBMS功能的NOSQL。對於對效能要求很高,特別是寫資料併發要求很高的應用來說使用Mongodb是比較適合的,比如存業務日誌或系統日誌。值得一提的是:

Mongodb也不是萬能的,對於超大級別的資料量如果不進行資料分割槽,那麼Mongodb也救不了你,並且不要期望Mongodb的自動Sharding功能能有多好,自動的畢竟沒有手動這麼準確,如果你能想到怎麼進行資料分片的話還是推薦手動進行。
Mongodb的效能是很好的,但這也是有限度的。只要擁有比較大的記憶體,那麼熱資料可以在記憶體中儲存,讀取效能不會太差,但是資料量達到一定的限度之後,如果你的索引資料都在記憶體中放不下的話,那麼其效能會很差的,其實我們也很容易想到為什麼,舉個例子,我們在查字典的時候需要翻索引的,至於字典真正的內容在哪裡其實問題不大的,如果一本百科全書即使由100本書構成,只要索引在手裡查到了哪一本再去取問題不大,如果這個索引也是由100本書構成,查索引就不能一次性在手裡查完還需要翻不同書的話這個效能就會非常差。

NOSQL之Redis
Redis於其說是一款NOSQL還不如說是一款快取元件,在大多數時候把NOSQL當成一個服務端可以做計算的,儲存複雜型別的,又具有一些諸如佇列、管道等小功能的升級版本的Memcached是不錯的。我個人使用Redis的心得是:

Redis不像Memcached,它可以儲存多種形式的資料,而不僅僅是一個字串,這是很有亮點的,意味著我們直接可以在服務端針對大量資料進行一個計算,然後服務端直接返回計算結果,而不是要從快取中把所有資料都取出來在客戶端進行一番計算然後再儲存回快取中的(這裡的客戶端是指使用NOSQL的客戶端,不是瀏覽器),也就是說要用好Redis是要寫一些定製性的程式碼的,把我們真正的業務邏輯嵌入到Redis中去,如果只是存KeyValue的話效能不一定比Memcached高多少的。
一般情況下不建議過多依賴Redis的磁碟VM的,有16GB記憶體儲存32GB的資料是可以的,有16GB的記憶體用Redis儲存1TB的資料那就是用錯Redis了。NOSQL的產品其實原理上來說都不是特別複雜的,使用NOSQL之前最好熟悉一下它的原理,這樣我們更容易用好NOSQL產品。總之我的觀點還是這樣,由於記憶體和磁碟效能的巨大差異,如果說能在記憶體中做大部分事情的話,這個效能就會比較好,如果要來回在記憶體和磁碟上翻騰的話這個效能也好不到哪裡去。示例分享

NOSQL之HBase
HBase、Hadoop適合的是超大資料量的儲存和計算,它其實是真正的一個分散式的概念,資料不再能在一個單點上儲存了,需要分散到很多機器上,然後計算結果也是分別計算後彙總在一起。對於這套東西我的體會是不要輕易引入:

除非是億級以上的資料量並且資料的格式比較簡單,否則要考慮是否適合引入,HBase的學習曲線不低的,而且最好是在社群熟悉過一段時間的,否則連用哪套版本的Hadoop什麼的都搞不清楚,版本沒用對要麼有很多難以解決的BUG要麼就是連基本的連通性都搞不起來。
要想有很好的效能,至少有20臺以上的伺服器再來搞HBase,一兩臺機器就算了,還沒到分散式的這個需求。想想也知道所謂通過並行來提高效率首先是你有這麼多資源可以把資料並行分散出去,然後同時進行計算彙總才能節約時間,如果根本資料都沒分出去怎麼可能節省時間。