概述:
針對大流量高并發,不管是高富帥還是屌絲,首先考慮的都是如何用最少的資源處理最多的業務。一般來說,網站架構最初需要考慮三個方面:數據庫瓶頸、代碼執行效率和服務器端的配置。如果說這三塊,網上千篇一律的資料還是蠻多的,例如Apache和Nginx的爭論就能連篇累牘,
針對大流量高并發,不管是高富帥還是屌絲,首先考慮的都是如何用最少的資源處理最多的業務。一般來說,網站架構最初需要考慮三個方面:數據庫瓶頸、代碼執行效率和服務器端的配置。如果說這三塊,網上千篇一律的資料還是蠻多的,例如Apache和Nginx的爭論就能連篇累牘,所以我還是結合項目開發中經驗總結一下,內容空洞枯燥無味,非碼農請慎重。
1. 合理設計使用數據庫
很多關于數據庫的疑問其實都可以歸納到這兩點:如何設計高性能的數據庫?如何使用好數據庫才能讓數據庫按照我們的意愿去工作?
首先,設計數據庫之初就應該預估可能產生的數據量和所能承受的壓力,該分表的分表,該分區的分區,該建立索引的就建立針對類型的索引,該讀寫分離的就趁早的做好數據庫主從或者數據庫集群。雖然大多數互聯網公司都有專業的DBA,但是需求第一線的碼農必須具有設計優秀數據庫的能力,否則在下一步中就不得不消耗較多的資源。
其次,合理使用數據庫,基礎的概念已經老生常談的內容:盡量少用或者不用JOIN、IN和GROUP BY等查詢和臨時表、數據庫端編程,配置my.cnf記錄慢查詢,增加查詢內存等。關于JOIN查詢,每次JOIN都是兩個表數據量的乘積(笛卡爾積),所以盡可能的在程序中處理。關于數據庫段編程,我以前倒是經常在SQL Server中寫存儲過程和觸發器,不過在MySQL中除非迫不得已一般都不會使用這些自定義函數,因為數據庫的壓力已經很大了。不過類似淘寶那種特征量對特征值的設計有時候是不可避免的使用多個JOIN查詢的,這個就需要專業的DBA去從底層優化了。
下面這個SQL語句是一個很典型的反面教程,8000條數據卡死了,要是有人當著我的面寫這樣的查詢我就地干掉他:
SELECT COUNT(*) FROM `dbkxdhk`.`shop_goods` AS g ,(SELECT goods_id, attr_value AS cut FROM `dbkxdhk`.`shop_goods_attr` WHERE attr_id=2) AS cut,(SELECT goods_id, attr_value AS color FROM `dbkxdhk`.`shop_goods_attr` WHERE attr_id=3) AS color,(SELECT goods_id, attr_value AS symmetry FROM `dbkxdhk`.`shop_goods_attr` WHERE attr_id=6) AS symmetry,(SELECT goods_id, attr_value AS clarity FROM `dbkxdhk`.`shop_goods_attr` WHERE attr_id=4) AS clarity,(SELECT goods_id, attr_value AS polish FROM `dbkxdhk`.`shop_goods_attr` WHERE attr_id=5) AS polish,(SELECT goods_id, attr_value AS cert FROM `dbkxdhk`.`shop_goods_attr` WHERE attr_id=8) AS cert,(SELECT goods_id, attr_value AS carat FROM `dbkxdhk`.`shop_goods_attr` WHERE attr_id=1) AS carat,(SELECT goods_id, attr_value AS location FROM `dbkxdhk`.`shop_goods_attr` WHERE attr_id=62) AS location,(SELECT goods_id, attr_value AS certificate FROM `dbkxdhk`.`shop_goods_attr` WHERE attr_id=8) AS certificate WHERE g.goods_id = cut.goods_id AND g.goods_id = color.goods_id AND g.goods_id = symmetry.goods_id AND g.goods_id = clarity.goods_id AND g.goods_id = polish.goods_id AND g.goods_id = cert.goods_id AND g.goods_id = carat.goods_id AND g.goods_id = location.goods_id AND g.goods_id = certificate.goods_id AND g.is_on_sale = 1 AND g.cat_id=1 AND g.is_alone_sale = 1 AND g.is_delete = 0 AND g.cat_id IN ('1','2','7','9','8')
最后,關于數據庫緩存,現在一般都使用Memcache,PHP等語言也已經對Memcache提供了足夠多的支持。此外也可以優化下數據庫自身的緩存設置,例如增加查詢內存等。而Key-Value的NoSQL的火爆能否完全解決關系型數據庫的瓶頸這個我也沒有實際的經驗,我只能說在某些領域內或許NoSQL可以幫助我們更好的使用關系型數據庫。
2. 代碼效率 - 細節決定成敗
一個最基礎的例子,PHP中單引號和雙引號的區別。我開始寫PHP的時候基本上都用雙引號,因為這樣在字符串中我可以直接填入變量,例如:
$sql = "SELECT * FROM test WHERE id=$id";
$arr["key"] = $value;
不過,zend在編譯PHP代碼的時候,如果是雙引號的字符串,會首先針對整個字符串進行一次掃描以判斷這個字符串是否有需要被替換的變量存在,而單引號的字符串則不會存在這個問題,所以說單引號字符串的執行效率是要高于雙引號字符串的。
$sql = 'SELECT * FROM test WHERE id='.$id;
$arr['key'] = $value;
不要覺的這樣的寫法吹毛求疵,如果一個系統龐大的一定地步,這樣的效率損失的絕對要避免的。其他代碼問題網上有很多建議。
3. 選擇最合適的服務
關于Apache和Nginx的爭論從來沒有少過,就像不同開發語言之間的爭論一樣。infoQ上有一篇不錯的文章:和LNMP相比,LNMPA是否效率更高?。同樣,作為一個從Apache學起的碼農,我是比較青睞Apache,在Ubuntu配置開發環境很方便,并且了解的Apache模塊比較多,對Nginx了解的比較少。不過Nginx確實在處理靜態文件上的效率要高出很多,所以目前大部分項目均采用Apache的前提下,已經開始實施把css、js、圖片等靜態文件部署到Nginx服務器上,無法實現靜態話的程序比如一些API還是部署到Apache。在部署我自己開發的博客之前,我也曾經在服務器上編譯了Nginx、MySQL和PHP-fpm,不過后來考慮到知之甚少,深入學習會影響到我最近的安排而作罷。
4. 使用緩存或者CDN減少HTTP請求
一個HTTP請求要經歷連接、請求、應答和關閉4個主要過程,數據包經過多個層次的網絡協議,所以減少HTTP請求是可以有效的降低服務器壓力的。一般來說,網頁中最消耗流量的圖片甚至是js和css都可以盡可能的緩存到用戶的瀏覽器,這樣不僅僅是可以提高再次訪問的速度。如果經濟能力允許,最好的緩存到CDN。
5. 合并壓縮靜態文件
可以使用雅虎的Yslow去頁面分析,將Css中用到的圖片盡可能的合并為一張大圖,同理,也不要使用過多的Css和java script文件,能合并的合并,能壓縮的壓縮。然后跳回上一步中執行緩存或者CDN即可。然后,頁面內容可以采用Apache的gzip模塊進行壓縮,文字內容的壓縮比例是非常高的。
6. 頁面靜態化
靜態化與否和SEO沒有關系,不管是動態還是靜態的頁面,爬蟲抓取到的內容是一樣的,不一樣的只有抓取的速度問題。同樣訪問者也一樣,靜態頁面可以在一定程度上提高加載速度,最重要的是可以強有力的緩解數據庫的壓力。以我這個博客為例,頁面全部靜態化,訪問者唯一能夠觸發的就是頁面訪問次數的更改,其他時候只有在頁面內容發生改變,比如新增加了評論或者新修改了文章內容后才會重新讀取數據庫生成頁面。
理論上來說大部分網站都是可以靜態化的,不過靜態化存在服務器IO這個很嚴重的瓶頸,所以現在大互聯網公司采用緩存的方式較多,緩存+squid或者varnish的方式除了數據庫效率外和靜態頁面的加載速度確實基本上沒差別的。當然還有很多應用是不能生成靜態頁面的,比如微博、SNS以及API等。
上面的是我淺顯的總結了一些高流量高并發網站的基礎工作,可能存在不少適用范圍較小的地方。不足和缺點請不吝賜教。

