互聯網下,各種高性能后端系統,技術解決方案


作為擁有世界上最多的互聯網用戶群體國家,尤其是移動互聯網的大熱,做到一個百萬級的應用幾乎是分分鍾的事情。相應對技術的壓力,和要求也是非常高的。

 

要應付這種大並發需要高性能系統的開發,先從常用的MySQL數據庫碰到的性能瓶頸,來做分析。因為通常一個小項目剛開始一般都會只用mysql做為數據存儲,當用戶量增加的時候,就會出現數據庫負載過高的問題,也就是所謂的慢查詢。解決慢查詢的問題一般來說,解決方案是優化SQL查詢,讀寫分離和主從數據庫,不斷地切庫分表。

 

1.SQL優化,最常見的方式是,優化聯表查詢,以及優化索引。這里面包括,盡量使用left join 替代 where聯表;當碰到,頻繁查詢字段A和字段B,以及AB聯合查詢的情況時,對AB做聯合索引,能夠有效的降低索引存儲空間,提升查詢效率。在復雜聯表的情況下,可以考慮使用 Memory中間表。

 

2.主從數據庫和讀寫分離,主從分庫是用來應對訪問量增加,帶來頻繁讀寫導致數據庫的訪問和操作性能下降的問題。對數據庫的操作,為了保證數據的完整性,通常涉及到鎖的機制的問題。MySQL的InnoDB引擎支持行級鎖,而MyIsAM只支持表鎖定。這樣的話,如果讀寫集中在一個表中的情況下,當訪問量增加,就會造成明顯的性能下降。因此,通過主從數據庫的方式可以實現讀寫分離。一般來說,使用InnoDB來作為寫庫,使用MyISAM作為讀庫。這種方式是缺點當然是,數據庫的維護難度增加,但是通常都會有專門的DBA這個職位來負責。而且幾乎是必須的解決方案,算是基礎設施了。

 

3.數據庫分庫和分表.有的時候會出現,某個表變得越來越龐大,比如存放message信息表,這樣會造成讀取性能的增加。這種情況下,你可以通過分表的方式來解決。將一個大表切分成若干個表。

 

一種簡單算法是:

      設定表的大小為M,用戶訪問記錄的ID和表的實際ID有差異,這個時候就需要做下換算,表的id = 用戶訪問的id 對M 進行求余;這種算法簡單實現容易,單向擴展簡單,但是缺點很明顯,他是按照數量進行分配,但是往往實際情況會出現,訪問量會集中在某幾個表,而其他表訪問不大,這樣這種算法實際上實際上沒太大的效果。而且重新調整的話,就比較困難。

 

類似分表的算法,還有很多,具體可以參考百度。

 

4.使用存儲過程.將一些操作,直接通過存儲過程的方式,預先設置在MySQL,客戶端只需要調用存儲過程就可以操作數據。在日常實踐中,經常會出現,DBA在備份和恢復數據庫的時候,遺忘了存儲過程的情況。其次,業務調整的過程時,要對線上的存儲過程進行調整,容易出現意想不到的問題,增加運維的成本。因此很多的團隊不太願意使用存儲過程.

 

訪問壓力增大之后,最容易想到的解決方案就是,使用緩存了。實際上現實中,最常用的緩存無處不在。但是緩存細細說來其實還是比較復雜。

首先分為前端緩存,和后端緩存,兩種技術解決方案。

 

先說前端頁面的緩存,又可以分為PC端頁面和移動客戶端的緩存技術方案。

 

對PC端來說,對靜態資源進行緩存,包括JS,CSS,圖片等資源文件的緩存。

 

1. 一般來說,資源文件的緩存都是通過專門的CDN緩存加速,由於國內線路不通訪問速度也不同,所以有專門的服務商提供專門的靜態資源加速緩存服務保證不同的線路,以及用戶可以就近快速訪問。

 

2.對動態頁面進行緩存,比如網站首頁,內容頁等這種改動不會太大的頁面或者幾乎不太會改變的頁面,有兩種方式:1)直接生成靜態html頁面,需要更新時通過后台成生成新頁面進行覆蓋。然后可以把靜態頁存放到本地硬盤或者同步到CDN上。2)使用vanish服務器作為方向代理,對php生成的動態頁面進行緩存,由於vanish可以使用內存作為緩存,因此訪問速度更快,且對生成頁面的php代碼不需要做任何的修改,就可以實現靜態頁面緩存。所以可以很好地解決因為一直遺留下來的問題導致代碼修改成本高的情況。缺點也是,提升了運維成本。

對頁面上某些內容經常變化的頁面,比如用戶中心頁,其實也可以使用Ajax的方式來處理,將頁面的基本內容緩存成靜態頁,使用ajax動態加載服務器端的動態數據。

 

瀏覽器緩存

通過Cache-Control,以及Last-Modified等控制緩存頭的設置來告訴瀏覽器緩存頁面。這樣不必每次,都從服務器端重復請求新文件,也可以防止用戶頻繁刷新頁面。

在過去帶寬有限的情況,下瀏覽器緩存非常的重要,但是目前來說,由於帶寬的提升,服務器性能提升,相對來說,這方面的瀏覽器的緩存要求相對下降了。使用瀏覽器緩存有的時候反而使得業務變得更加復雜,因此很多情況下,不少業務干脆不用瀏覽器緩存。

 

后端緩存

 

1.代碼緩存:使用的各種PHP框架,本身會生成各種代碼緩存,比如使用模板的視圖文件,配置文件,都會解析成相應的php代碼。通過這些方式,提升框架的運行效率。

 

2.數據緩存:這一類緩存實際上是最為復雜的一類的,也是經常會碰到,需要根據業務進行不斷地調整一類的緩存。通常,我們常見的是對原先存儲在MySQL數據的數據進行緩存加速。這也是前面涉及到的數據庫性能瓶頸的通常解決方案之一。一般來說,過去常用的緩存服務器是memcached,但是隨着Redis的出現之后,很多的創業公司,和項目,直接繞開memecached使用Redis做為緩存服務器,而且Redis不只是緩存服務,還有更多的高級特性。這里放在專門的段落來討論,使用NoSQL替代MySQL的話題。

 

使用緩存最大的問題,是出現當緩存失效之后,如何解決驚群效應帶來的服務器突然壓力上升問題。當某個緩存失效之后,一般的做法是,再從后端的數據庫中查找新的數據然后再重建緩存,但是在這個過程中,如果這個緩存內容同時有很多並發請求,就會出現,在重建新的緩存的時間段內,大量涌向后端數據庫的訪問,引起慢查詢,導致數據庫崩潰。一般來說,我們采取的方案是,主動更新緩存內容,同時延長緩存的時間,實際失效時間會比告知客戶端的約定失效時間要長一些。比如實際失效時間是30s,約定失效時間是20s,后端的worker會對緩存進行主動更新,一般會使用兩個key1,key2的方式進行輪流緩存和訪問,比如,客戶端訪問的時候,先訪問key1如果key1不存在則訪問key2,緩存更新時,先生成key2,然后再刪除key1。反之,亦然。

 

        再來說說所謂驚群效應,來源於一個很有意思的場景,在廣場上,有一大群的鴿子,游人過來拋灑食物的時候,原本平靜的廣場,突然一大群鴿子都擁過來爭搶游客手中的食物,當食物被吃完之后,又恢復了之前的平靜。等到下一次食物到來的時候,又出現相同的情景。這種現象,我們叫做驚群效應,其實在生活中這種現象隨處可見,比如:商場大促銷,可以看見一大堆的人早已等候在門外,等商場開門的時候,大量的人如潮水一般涌入。還有線上的一些電商促銷活動,比如淘寶雙十一,各種秒殺活動等。都是常見的“驚群效應”現象。

        在技術開發中,我們經常也要考慮驚群效應出現的場景。

        我們最熟悉的一個業務場景就是,線上的秒殺促銷活動的業務開發,在這個業務中就可以看到驚群效應的影響。我們可以假定一個業務場景,比如小米手機開放搶購。只有一萬台手機,實際上參與秒殺的人,超過100萬,假定時間是上午12點開始搶購,但是實際上真正的秒殺過程就是1兩秒鍾最多了,肯定被搶完了。

      驚群效應會導致,大量的服務器資源浪費,在服務器訪問壓力圖表中,看到大多數情況下服務器是處於閑置狀態的,一旦壓力增加之后,服務器資源迅速被消耗殆盡,甚至導致崩潰,整個系統癱瘓。

       高並發狀態下,驚群效應是經常出現,尤其是在基於社交的移動互聯網產品中,幾乎是家常便飯。我們經常處理的技術解決方案是,使用隊列來進行處理,這也是NoSQL數據庫常被用到的地方。將用戶所有的請求,寫入到隊列中,然后通過后端的worker對隊列的請求進行處理,這是一個生產者-消費者模型的經典使用場景。當處理完獲取到規定數量級的結果之后,通知請求代理服務器,關閉請求通道,並重定向到別的頁面,告知用戶服務已經完結。比如,秒殺的時候,前端代理服務器負責將用戶的請求發送到Redis隊列服務器,然后后端的worker進程,消費隊列的數據,當發現,秒殺的產品數量已經被搶光之后,則通知前端代理服務器,關閉秒殺請求的通道,重定向用戶到一個靜態提示頁面,告知用戶秒殺結束。這樣可以保證,不會出現庫存和訂單不一致的情況,出現用戶多搶的情況。包括抽獎也是一樣,在高並發的情況下經常出現,用戶搶到超出庫存設定的相同商品。

 

      這里談談使用NoSQL在大型項目中的使用。

      這里說說我們常用的NoSQL開源項目,Redis,MongoDB,CounchBase等等,包括甚至一些新的語言,如,Node.JS等,這些新的技術產品,很多生來就是為,移動互聯網大數據服務的。過去我們很多人都認為,所謂高並發,大數據,等名詞都出現在BAT等少數公司里。但是隨着移動互聯網時代的到來,其實很多的新興的移動互聯網創業公司,很容易的,就能出現一夜間到上千萬甚至過億的用戶,比如,之前在朋友圈風靡的,瘋狂猜圖,神經貓,這類小應用。所以在這些新興的移動互聯網產品中,NoSQL的使用幾乎是基礎服務。尤其是在社交類的應用中,對Feed數據的處理,消息推送,基於社交關系鏈的維護,用戶社交行為的統計,都使得對代碼的質量,數據的優化存儲,業務的架構的設計等等都會有更高的要求。

 

NoSQL具體業務應用場景有以下幾個方面:

 

1)隊列服務。前面所提到的秒殺,抽獎,各種道具的交易等都會用到隊列服務。其次是,消息推送,粉絲關注列表,等,都會用到隊列服務。消息隊列開源軟件有很多,類似RabbitMQ,ZeroMQ,Redis等等,但是常用的還是Redis比較多。

對Feed的處理,是隊列常用到的場景。比如微博的fee的消息,通常都是采用push和pull的策略,對活躍用戶,一般通過緩存算法,LRC,LRF等進行計算,系統維護一個活躍用戶的緩存池。然后為每一個活躍用戶,生成他所關注的對象列表的消息隊列,當用戶上線的時候,主動推送這些消息隊列。對不常活躍的用戶,只有當他登錄的時候,才會從后端的數據庫去查詢數據,生成結果。

這種方式可以有效的提升性能,同時節約存儲空間。

 

2)計數器和限速器。移動社交應用中,用戶的點贊行為非常常見,為了保存用戶的點贊數據,我們經常會用到計數器服務,用被點贊的記錄id作為key,對應的結果值為點贊次數。redis的incr,incrby等命令就經常被使用到,還可以通過expire來配合使用,在指定時間里面記錄用戶的數據。另外為了防止用戶頻繁訪問API接口,尤其是惡意訪問服務器數據服務,導致服務器過載,可以對用戶的訪問進行限速。

 

3)Top Rank,尤其是在游戲中經常會出現排名榜,電商網站也會有對商品的購買量進行排行,或者是會員等級排名等等。redis的ZSet經常被使用到,可以把記錄的id存入zeset,設置對應的score為排名的依據的值,然后使用zRank來讀取用戶的排名,也可以使用zRange命令來獲取到前指定位數的用戶排行榜信息。

 

4)消息訂閱。redis的pub/sub服務,可以實現消息訂閱的功能,通常是用在用戶的消息推送中,向指定用戶推送相應的消息。其次是,任務分發,將用戶產生的行為或者請求,發送到后端,在后端服務器中由后台worker進行處理,這樣好處是,降低前端服務器的處理請求的壓力,減少用戶等待時間,同時減輕帶寬的壓力,也大大的降低服務器帶寬成本,節約資金。

 

5)存儲session。普通的網站使用file文件的方式存儲session,使用redis作為session進行用戶登錄存儲,好處很明顯,一方面是訪問性能提升,另外,redis使用set來存儲在線用戶的uid數據,這樣可以很方便的使用sCard命令統計當前在線用戶數,防止用戶重登錄。其次是避免,分布式環境下,帶來用戶登錄訪問不一致的問題。因為用戶session信息,統一存儲在獨立的NoSQL數據庫中一般來都是,使用redis服務器,這樣,除了提升性能之外,也降低了代碼處理的復雜度,甚至避免了過早使用大型的SSO系統,導致開發成本增加。

 

6)地理信息位置。MySQL其實也有存儲地理位置信息,但是不如MongoDB,CounchBase等新興數據庫功能強大。基於地理位置的社交服務,用戶會帶着手機客戶端,不斷移動位置,所以用戶的位置會不斷地上報到服務器,然后根據服務器實時的地理信息位置,快速計算出附近附近的人,這對服務器的訪問壓力要求比較高。

 

通過代碼,和各種軟件的使用,來提升性能之外,整個系統架構的設計就提上日程了,最重要也是最通用的方式,自然是,堆服務器了,換個高大上的叫法,也就是所謂的分布式,系統集成。在高大上一點,就是所謂的雲服務,各種所謂的雲。

 

1)反向代理模式。所謂反向代理模式,用戶所有的請求都統一發送到反向代理服務器,而反向代理服務器本身並不處理具體的請求,他只負責將請求發送到后端,獲得結果之后,再傳遞給用戶。這樣的方式,能夠加快整個系統的服務器響應,即使后端某個系統的崩潰,也不影響整個服務系統的奔潰。另外,這種反向代理服務器,一般來是應用服務器,本身也是分布式的,用戶訪問請求是被分發到不同的服務器上,這樣能夠支撐更大的並發請求。后端服務器,並不需要接入到互聯網中,這樣就不需要占用寶貴的帶寬,只需要在本地網絡使用本地線路進行連接,專注各種數據的運算服務,這樣能夠大大的降低成本,提高系統的安全性,同時提升整個系統的性能。

 

2)過濾器中間件。很多的請求並非都是立即被執行,或者是都會被請求。並非所有的客戶的無理要求都必須滿足一樣,有的餐廳就並不一定提供住宿服務一樣。當用戶請求,被傳遞到中間層中,會有各種中間件會對用戶的請求進行過濾,比如秒殺或者抽獎時,用戶的頻繁刷新請求,會被過濾器進行過濾,合並或者拒絕用戶的請求。這樣真正傳遞到后端的請求就會減少很多,有效地降低了后端服務器的壓力。

 

3)數據視圖。用戶的很多重要數據都最終存在數據庫中,但是用戶的每次請求,並不需要一次性訪問所有的數據。比如,判定用戶是否登錄,只需要訪問用戶的是否登陸的標記。這些數據可以存在redis的key中,並不需要通過數據庫去保存。再有,一些信息頁的數據,由於變動不會太大,由后台編輯生成,而並非是用戶生成的信息數據,往往是樹狀結構,每次通過數據庫查詢,需要大量重復的join或者多次的sql查詢才能讀取。一方面增加了,代碼的邏輯設計和維護的成本,另外一方面,運行成本也大大提升,即使通過緩存的方式,也會有驚群效應的問題需要解決。所以不如通過主動視圖的方式來解決,這一類的數據,可以通過使用后台使用mysql進行存儲和編輯,而面向用戶的前台數據,使用MongoDB等高性能的NoSQL的數據庫存儲,相當於,MongoDB作為MySQL的視圖。

 

4)緩存策略。

對於用戶產生的UGC內容,它的特點是,有的用戶行為讀寫頻繁,其次是往往出現不可預測,突然一下子並發提升。比如,用戶的內容分享,轉發,收藏,點贊。有的帖子,會突然出現被病毒式傳播的現象,大量的用戶分享,轉發。可能這篇文章的訪問量,就會突然增加,相應的訪問壓力也提升,對數據庫的讀取壓力也增加。

然而並非所有的帖子都是熱門帖子,都會被訪問,往往在一個系統中,大部分的帖子都處於冷門或者無人問津的狀態下,只有少部分的帖子匯出現熱門的狀態。

因此並非所有的內容都應該被緩存,提供統一的,緩存策略服務就顯得很有必要。緩存策略服務器系統,使用不同的算法策略,維持一個緩存數據的池,並非是簡單地緩存數據。使用有效地緩存策略,對低訪問壓力的請求直接穿透緩存使用后端訪問,高訪問壓力使用緩存服務。

緩存策略的算法有很多種,常見的有:

Least Frequently Used(LFU)

Least Recently User(LRU)

Least Recently Used 2(LRU2)

Two Queues(2Q)

具體還可以百度查詢,翻閱更多的詳細介紹。

 

5)任務系統。將很多的耗費性能的服務,集成為一個專門的服務系統,比如,定時任務服務,圖像處理,分布式文件同步等等。

 

以上在高並發,高訪問量的應用中,一些技術問題的解決方案的匯總。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM