高并发系统的优化一直以来都是一个很重要的问题,下面基于我在 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浏览器中给出的页面反馈。它通常并不意味着上游服务器已关闭(无响应网关/代理) ,而是上游服务器和网关/代理使用不一致的协议交换数据。鉴于互联网协议是相当清楚的,它往往意味着一个或两个机器已不正确或不完全编程。
简单来说,这种问题是就是网络连接超时,我们向服务器器发送请求,由于服务器当前链接太多,导致服务器方面无法给于正常的响应,产生此类报错。
客户机上频繁出现这种错误,导致比赛无法正常进行,这是致命的问题。
遇到这种错误,可以从三个大的方面来进行分析和处理。分别是客户机端、系统设计层面、服务器端。本篇文章三种方式都有提及,但主要是最后一种,服务器端的优化操作。
对于普通用户来说,基本上无能为力。一是可以切换网络进行尝试;二是采用强制刷新方法,可以使用Ctrl + F5的方式进行刷新,因为基本刷新有可能只是从本地的硬盘重新拿取数据到浏览器,并不一定重新向服务器发出请求。大部分用户很多时候都是这样刷新的,遇到502报错就没有任何效果。应当使用强制刷新,这个时候就会从服务器重新下载数据,如果服务器相对空闲的话,会连接成功。
HTML静态化
效率最高、消耗最小的就是纯静态化的HTML页面,所以我们尽可能使我们的网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法。尽可能的静态化也是提高性能的必要手段,将系统内的帖子、文章进行实时的静态化、有更新的时候再重新静态化也是大量使用的策略目前很多资讯社区就是如此。
目前在系统中,采用此手段的地方还很少,只有首页的公告栏采用此种方式,后期会考虑将公告模块静态化,减少数据库访问请求。
图片服务器分离
图片是最消耗资源的,于是我们有必要将图片与页面进行分离,这是基本上大型网站都会采用的策略,他们都有独立的、甚至很多台的图片服务器。这样的架构可以降低提供页面访问请求的服务器系统压力,并且可以保证系统不会因为图片问题而崩溃。
目前系统中的图片资源相对较少,仅头像和公告模块,暂时无需进行优化。
缓存
为了避免每次都向数据库中取得数据,我们把用户常常访问到的数据放到内存中,甚至缓存十分大的时候我们可以把内存中的缓存放到硬盘中。还有高级的分布式缓存数据库使用,都可以增加系统的抗压力。
Redis数据库准备在后期版本中引入进行优化。
数据库集群
主从数据库,分布式数据库,这是后面版本必须考虑的问题。
负载均衡
负载均衡将是大型网站解决高负荷访问和大量并发请求采用的高端解决办法。
CDN加速技术
CDN的实现分为三类:镜像、高速缓存、专线。这个需要Money,等资金充足了后上。
数据库读写分离
使用消息队列
多用存储过程等等
后面几种方式相对高级,但也是开发中大型系统必须考虑的,这个坑以后慢慢填…
经过分析,发现服务器端的瓶颈主要是在以下3个方面:NGINX服务器、PHP-fpm、MySQL。为了较为精准的定位到是哪一个方面的问题,以此做了以下的测试,分别对每种情况进行分析。另测试压力软件使用的是Apache Bench,在我另一篇文章中讲解了如何安装。
测试Nginx服务器问题
Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强。
要测试Nginx服务器问题,可以在网站根目录下创建一个HTML文件,输入任意简单的内容。
然后利用AB命令进行压力测试,如创建了一个test.html,文件内容如下:
<html>
<head>
<title>testtitle>
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文件,文件内容如下:
//一个简单的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应用并发性能调优的记录。