PHP+Nginx+MySQL網站併發性能調優記錄
一、背景
高併發系統的優化一直以來都是一個很重要的問題,下面基於我在 AQNUOJ 系統的實踐,記錄一下自己在服務器端處理高併發系統的一些調優和優化策略。
AQNUOJ 上線半年以來,一直平穩運行,百度統計顯示近30天瀏覽量(PV):457537,訪客數(UV):5765,IP數:1956,日峯值PV達58322,已然達到一箇中小型網站的流量標準。在應對系統穩定性和高併發性方面有更高的要求。
目前系統部署在校內服務器,由單臺服務器承擔所有服務,服務器配置爲32核心CPU、64G內存。僅開放校內訪問端口,若將來開放外網,用戶訪問量將更大。
$ cat /proc/cpuinfo | grep MHz|uniq
cpu MHz : 1210.218
cpu MHz : 1223.523
cpu MHz : 1209.101
cpu MHz : 1200.976
cpu MHz : 1230.937
cpu MHz : 1218.648
cpu MHz : 1229.515
cpu MHz : 1200.062
cpu MHz : 1199.960
cpu MHz : 1200.062
cpu MHz : 1200.164
cpu MHz : 1199.960
cpu MHz : 1200.062
cpu MHz : 1204.734
cpu MHz : 1217.531
cpu MHz : 1224.945
cpu MHz : 1241.093
cpu MHz : 1212.656
cpu MHz : 1227.078
cpu MHz : 1222.710
cpu MHz : 1237.234
cpu MHz : 1249.828
cpu MHz : 1200.570
cpu MHz : 1200.164
cpu MHz : 1200.773
cpu MHz : 1200.570
cpu MHz : 1200.265
cpu MHz : 1202.296
cpu MHz : 1200.164
cpu MHz : 1200.570
$ cat /proc/meminfo
MemTotal: 65938856 kB
MemFree: 26082116 kB
MemAvailable: 32129052 kB
二、問題
平時使用基本上沒有問題,當創建一個500人左右的比賽中,客戶機上頻繁出現 502 Bad Gateway 錯誤。502 Bad Gateway是指錯誤網關,無效網關;在互聯網中表示一種網絡錯誤。表現在WEB瀏覽器中給出的頁面反饋。它通常並不意味着上游服務器已關閉(無響應網關/代理) ,而是上游服務器和網關/代理使用不一致的協議交換數據。鑑於互聯網協議是相當清楚的,它往往意味着一個或兩個機器已不正確或不完全編程。
簡單來說,這種問題是就是網絡連接超時,我們向服務器器發送請求,由於服務器當前鏈接太多,導致服務器方面無法給於正常的響應,產生此類報錯。
客戶機上頻繁出現這種錯誤,導致比賽無法正常進行,這是致命的問題。
三、分析
遇到這種錯誤,可以從三個大的方面來進行分析和處理。分別是客戶機端、系統設計層面、服務器端。本篇文章三種方式都有提及,但主要是最後一種,服務器端的優化操作。
3.1 客戶機端
對於普通用戶來說,基本上無能爲力。一是可以切換網絡進行嘗試;二是採用強制刷新方法,可以使用Ctrl + F5的方式進行刷新,因爲基本刷新有可能只是從本地的硬盤重新拿取數據到瀏覽器,並不一定重新向服務器發出請求。大部分用戶很多時候都是這樣刷新的,遇到502報錯就沒有任何效果。應當使用強制刷新,這個時候就會從服務器重新下載數據,如果服務器相對空閒的話,會連接成功。
3.2 系統設計層面
-
HTML靜態化
效率最高、消耗最小的就是純靜態化的HTML頁面,所以我們儘可能使我們的網站上的頁面採用靜態頁面來實現,這個最簡單的方法其實也是最有效的方法。儘可能的靜態化也是提高性能的必要手段,將系統內的帖子、文章進行實時的靜態化、有更新的時候再重新靜態化也是大量使用的策略目前很多資訊社區就是如此。目前在系統中,採用此手段的地方還很少,只有首頁的公告欄採用此種方式,後期會考慮將公告模塊靜態化,減少數據庫訪問請求。
-
圖片服務器分離
圖片是最消耗資源的,於是我們有必要將圖片與頁面進行分離,這是基本上大型網站都會採用的策略,他們都有獨立的、甚至很多臺的圖片服務器。這樣的架構可以降低提供頁面訪問請求的服務器系統壓力,並且可以保證系統不會因爲圖片問題而崩潰。目前系統中的圖片資源相對較少,僅頭像和公告模塊,暫時無需進行優化。
-
緩存
爲了避免每次都向數據庫中取得數據,我們把用戶常常訪問到的數據放到內存中,甚至緩存十分大的時候我們可以把內存中的緩存放到硬盤中。還有高級的分佈式緩存數據庫使用,都可以增加系統的抗壓力。Redis數據庫準備在後期版本中引入進行優化。
-
數據庫集羣
主從數據庫,分佈式數據庫,這是後面版本必須考慮的問題。
-
負載均衡
負載均衡將是大型網站解決高負荷訪問和大量併發請求採用的高端解決辦法。
-
CDN加速技術
CDN的實現分爲三類:鏡像、高速緩存、專線。這個需要Money,等資金充足了後上。 -
數據庫讀寫分離
-
使用消息隊列
-
多用存儲過程等等
後面幾種方式相對高級,但也是開發中大型系統必須考慮的,這個坑以後慢慢填…
3.3 服務器端的優化*
經過分析,發現服務器端的瓶頸主要是在以下3個方面:NGINX服務器、PHP-fpm、MySQL。爲了較爲精準的定位到是哪一個方面的問題,以此做了以下的測試,分別對每種情況進行分析。另測試壓力軟件使用的是Apache Bench,在我另一篇文章中講解了如何安裝。
-
測試Nginx服務器問題
Nginx是一款輕量級的Web服務器/反向代理服務器及電子郵件(IMAP/POP3)代理服務器,在BSD-like 協議下發行。其特點是佔有內存少,併發能力強。
要測試Nginx服務器問題,可以在網站根目錄下創建一個HTML文件,輸入任意簡單的內容。
然後利用AB命令進行壓力測試,如創建了一個test.html,文件內容如下:
<html> <head> <title>test</title> </head> <body> <p> 測試Nginx服務器的壓力 </p> </body> </html>
測試代碼如下:
ab -c 1000 -n 10000 http://localhost:80/test.html # -c 1000 表示併發用戶數 # -n 10000 表示請求總數爲10000 # http://localhost:80/test.html 表示請求地址
可以看到相關響應結果,在Apache Bench文章中有介紹具體參數。可以不斷更改-c和-n的數值,得到一個接近真實的壓力值。如果併發數量沒有達到和服務器性能預期的性能,可以適當調整Nginx的配置。在我項目的測試中,性能不佳,故我更改了相關配置。
打開Nginx配置文件,我的是在
/etc/nginx/nginx.conf
中。sudo vi /etc/nginx/nginx.conf
我更改了以下配置:
worker_processes 64; #可以調整至與CPU核心數一致 worker_rlimit_nofile 65535; #worker進程最大打開文件數,我調整到了最大值 events { worker_connections 65535; #單個進程允許的最大連接數,我改到了最大值 }
對於test.html的訪問壓力,有一定提升,但對項目的抗壓性,並沒有提升,可以推斷主要原因不是nginx服務器的配置問題。
更改配置後需要重啓nginx,重啓後再重新測試觀察結果,不斷調整。
sudo service nginx restart ab -c 1000 -n 10000 http://localhost:80/test.html
-
測試PHP-fpm問題
PHP-FPM(PHP FastCGI Process Manager)即 FastCGI 進程管理器,用於管理 PHP 進程池的軟件,用於接受web服務器的請求。PHP-FPM提供了更好的PHP進程管理方式,可以有效控制內存和進程、可以平滑重載PHP配置。
測試這個模塊,可以在網站根目錄新建一個test.php文件,文件內容如下:
<?php //一個簡單的10次累加操作 $sum = 0; for ($i = 0; $i < 10; $i ++){ $sum += $i; } ?>
同樣用AB進行測試,測試代碼爲:
ab -c 1000 -n 10000 http://localhost:80/test.php
一樣的不斷調整兩個參數,達到一個較爲真實的值來判斷性能。我的配置在
/etc/php/7.0/fpm/php-fpm.conf
和``/etc/php/7.0/fpm/pool.d/www.conf`中修改參數。sudo vi /etc/php/7.0/fpm/php-fpm.conf # 把process.max = 0,代表最大進程數無限制,其實最最主要的參數是在www.conf中,下面打開 sudo vi /etc/php/7.0/fpm/pool.d/www.conf
如果你的服務器內存還算比較大(>8G)可以將pm設置爲靜態方法。即
pm = static
。 可以避免大部分 PM 開銷,換句話說,它就不會有內存不足或緩存壓力的問題。如果你的服務器內存很低,那麼pm = dynamic
可能是更好的選擇。三種不同的模式:
pm = dynamic
: 子進程的數量根據以下配置動態設置pm.max_children, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers
pm = static
: 子進程的數量由pm.max_children
決定
所以現在只需要修改成合適的max_children就行了,具體計算方式是
內存/30M
得到,比如8GB內存可以設置爲100。當改成dynamic模式時,就與以下參數相關,max_children失效。
pm.start_servers:動態方式下的起始php-fpm進程數量 pm.min_spare_servers:動態方式下的最小php-fpm進程數 pm.max_spare_servers:動態方式下的最大php-fpm進程數量
更改後記得重啓PHP-fpm再進行測試。
sudo service php7.0-fpm restart ab -c 1000 -n 10000 http://localhost:80/test.php
調整後再對項目進行壓力測試,抗壓性有明顯提高,可以得出結論,在我的項目中php-fpm配置問題是一個瓶頸。
(此處有個小插曲)
我第一次測試時,把test.php中的內容寫的是10次循環,循環內容是執行phpinfo()函數,因爲本身這個函數帶來太大的php進程開銷,所以不是一個真實的測試效果,導致不管怎麼調整參數都崩了。
-
測試MySQL問題
因爲前兩種問題已經解決了,這個時候可以直接使用真實項目頁面來進行測試,結果得到MySQL的 瓶頸是最大的,當高併發的時候產生了大量MySQL連接,使內存、CPU飆升,性能大大下降,連接阻塞,產生502錯誤。
我的MySQL配置文件在
/etc/mysql/my.cnf
。sudo vi /etc/mysql/my.cnf
[client] port = 3306 socket = /var/run/mysqld/mysqld.sock # Here is entries for some specific programs # The following values assume you have at least 32M ram # This was formally known as [safe_mysqld]. Both versions are currently parsed. [mysqld_safe] socket = /var/run/mysqld/mysqld.sock nice = 0 [mysqld] # # * Basic Settings # user = mysql pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock port = 3306 basedir = /usr datadir = /var/lib/mysql tmpdir = /tmp lc-messages-dir = /usr/share/mysql skip-external-locking # # Instead of skip-networking the default is now to listen only on # localhost which is more compatible and is not less secure. bind-address = 127.0.0.1 # # * Fine Tuning # key_buffer = 10240M max_allowed_packet = 32M thread_stack = 2048K thread_cache_size = 32 # This replaces the startup script and checks MyISAM tables if needed # the first time they are touched myisam-recover = BACKUP max_connections = 4096 #table_cache = 64 #thread_concurrency = 10 # # * Query Cache Configuration # query_cache_limit = 512M query_cache_size = 1024M # # * Logging and Replication # # Both location gets rotated by the cronjob. # Be aware that this log type is a performance killer. # As of 5.1 you can enable the log at runtime! #general_log_file = /var/log/mysql/mysql.log #general_log = 1 # # Error log - should be very few entries. # log_error = /var/log/mysql/error.log expire_logs_days = 10 max_binlog_size = 100M #binlog_do_db = include_database_name #binlog_ignore_db = include_database_name # # * InnoDB # # InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/. # Read the manual for more InnoDB related options. There are many! # # * Security Features # # Read the manual, too, if you want chroot! # chroot = /var/lib/mysql/ # # For generating SSL certificates I recommend the OpenSSL GUI "tinyca". # # ssl-ca=/etc/mysql/cacert.pem # ssl-cert=/etc/mysql/server-cert.pem # ssl-key=/etc/mysql/server-key.pem [mysqldump] quick quote-names max_allowed_packet = 32M [mysql] #no-auto-rehash # faster start of mysql but no tab completition [isamchk] key_buffer = 10240M
具體配置如上,配置完成後記得重啓MySQL服務器。
sudo service mysql restart
(此處也有個小插曲)
在測試時,用的是index.php,在我的項目中,根目錄下的index.php不是真正的主頁,而是寫了一個php的跳轉頁面函數,所以相當於進行了二次轉發,併發量大了一倍,不是一個真實的測試效果。
四、結論
目前,已經根據服務器配置做了相對最優的參數,系統有了較大的性能提升。但是對於MySQL部分的優化,沒有達到預期。在高併發發生時,觀察了服務器相關運行狀態,發現還是MySQL佔用各種性能最大。初步判斷已經不是服務器端配置能夠解決的了。
系統高併發解決不是一件簡單的事,他是木桶原理,需要各方面都進行優化,有一方面的短板都不行。接下來項目的性能解決將回歸項目代碼本身,進一步瞭解網站和項目到底有多大的流量和併發,深入查看高併發源泉,使用隊列和緩存,數據庫優化,分佈式等技術,多判題機等技術進行優化。
以上是本次對PHP+Nginx+MySQL應用併發性能調優的記錄。