做爲擁有世界上最多的互聯網用戶羣體國家,尤爲是移動互聯網的大熱,作到一個百萬級的應用幾乎是分分鐘的事情。相應對技術的壓力,和要求也是很是高的。
要應付這種大併發須要高性能系統的開發,先從經常使用的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)任務系統。將不少的耗費性能的服務,集成爲一個專門的服務系統,好比,定時任務服務,圖像處理,分佈式文件同步等等。
以上在高併發,高訪問量的應用中,一些技術問題的解決方案的彙總。