这篇主要会log一下最近碰到的问题,关于初始搭建大家可以看WordPress + Nginx + PHP-FPM在AWS LightSail上的搭建
网站报错
这个网站是我搭建的自己玩耍的博客,我时不时会登陆一下网站看看运行情况。某一次登录的时候发现了报错:
Error establishing a database connection
这个报错很明显是数据库挂了,所以马上登陆服务器查看MariaDB的日志:
Version: '5.5.68-MariaDB' socket: '/var/lib/mysql/mysql.sock' port: 3306 MariaDB Server
230101 12:21:10 mysqld_safe Number of processes running now: 0
230101 12:21:10 mysqld_safe mysqld restarted
230101 12:21:10 [Note] /usr/libexec/mysqld (mysqld 5.5.68-MariaDB) starting as process 18821 ...
230101 12:21:10 [ERROR] mysqld: Out of memory (Needed 128917504 bytes)
230101 12:21:10 [ERROR] mysqld: Out of memory (Needed 96681984 bytes)
230101 12:21:10 [ERROR] mysqld: Out of memory (Needed 72499200 bytes)
230101 12:21:12 InnoDB: The InnoDB memory heap is disabled
230101 12:21:12 InnoDB: Mutexes and rw_locks use GCC atomic builtins
230101 12:21:12 InnoDB: Compressed tables use zlib 1.2.7
230101 12:21:12 InnoDB: Using Linux native AIO
230101 12:21:12 InnoDB: Initializing buffer pool, size = 128.0M
InnoDB: mmap(137756672 bytes) failed; errno 12230101 12:21:12 InnoDB: Completed initialization of buffer pool
230101 12:21:12 InnoDB: Fatal error: cannot allocate memory for the buffer pool
230101 12:21:12 [ERROR] Plugin 'InnoDB' init function returned error.
230101 12:21:12 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed.
230101 12:21:12 [Note] Plugin 'FEEDBACK' is disabled.
230101 12:21:12 [ERROR] Unknown/unsupported storage engine: InnoDB
230101 12:21:12 [ERROR] Aborting
数据库的报错提示无法获取足够的内存,接下来就需要找到Root cause
Root Cause猜测
因为是内存不足的报错,我的机器是单核1GB内存的机器,配置确实比较低。但是服务初始化之后,内存占用是270MB左右。剩下了接近700MB的内存,难道是短时间能有很多人访问我的网站吗?
所以打开CloudFlare的Analyst页面查看那段时间的登陆情况(我有一个Nginx服务,也能从Nginx那边查看那段时间的request发起情况):
不出意外,访问数量不多。峰时也只有30个独立User,344个请求。这些请求数,按道理是不会导致内存溢出的。所以接下来怀疑的原因是内存回收出现问题。WordPress主题使用2个服务:
- MariaDB的数据库
- PHP-FPM的PHP后端
那么对应的问题是:
- PHP-FPM
- 线程池配置失败:344个request对应了344个线程的话。根据经验344个PHP线程肯定会超出内存限制
- 内存泄漏:我为了WordPress网站管理可以变得方便,会安装多个插件。可能插件存在内存泄漏问题
- MariaDB
- InnoDB Buffer Size:就像报错的提示一样,首先怀疑了InnoDB的buffer配置不正确。
MariaDB配置
由于报错还是MariaDB,所以首先怀疑的对象还是MariaDB的InnoDB配置问题。查询MariaDB官网的内存配置页面Configuring MariaDB for Optimal Performance。我这边列几个重要的参数:
- innodb_buffer_pool_size:InnoDB的缓冲池大小。默认是128MB,推荐设置为可用内存的70%-80%
- innodb_log_file_size: InnoDB的回滚log。增大这个值,可以减少磁盘flush的操作,提高性能,但是它会增加内存使用量
- query_cache_type和query_cache_size:Query Cache可以储存SELECT语句的结果。如果能有效利用到query cache,可以极大的提高性能。虽然我的case是可以利用到query cache的,奈何内存太小。关闭query cache
MariaDB也会给出一些默认的配置:
- my-small.cnf
- my-medium.cnf
- my-large.cnf
- my-huge.cnf
为了减少报错概率,我先试用了my-small.cnf为模版,设置了我的配置文件:
innodb_buffer_pool_size = 16M
innodb_additional_mem_pool_size = 2M
innodb_log_file_size = 5M
innodb_log_buffer_size = 8M
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 50
skip-external-locking
key_buffer_size = 16M
max_connections = 70
max_user_connections= 50
max_allowed_packet = 1M
table_open_cache = 8
wait_timeout = 20
query_cache_type = OFF
query_cache_size = 0
sort_buffer_size = 512K
net_buffer_length = 8K
read_buffer_size = 256K
read_rnd_buffer_size = 512K
信心满满的重启MariaDB。然而几天之后MariaDB还是光荣牺牲了。这个时候MariaDB只能占用不到100MB内存,那问题大概率不是MariaDB的配置。
PHP-FPM配置
接下来只能怀疑PHP的内存泄漏和线程池问题了。对于这俩,有几个参数可以配置:
- pm.max_requests:每个子线程在服务了x个request后,会自动重启。这样可以有效的减少第三方代码的内存泄漏。默认设置为0,不存在自动重启
- pm.max_children:最多的子线程数。默认设置为dynamic
- pm.max_spare_servers:期望的空闲时线程保留数
我自己写了一个简单的Python程序去ping我的网站。随着本地python并发提高,在默认参数下,php的线程数快速上升,而且平均每个线程就能占到10MB以上的内存,且随着服务在线时长增加,不断增加。最后我的服务器就在大量的PHP的线程下,耗尽了内存。
由于每次网页请求,大概会占用14%的CPU,所以最后我的设置:
pm.max_requests = 500
pm.max_children = 10
pm.max_spare_servers = 10
这样在峰值的访问时,也是CPU会先被耗尽(这个时候,大概率会TLS握手失败,但是是另一个故事了)。
结果
运行2周之后,服务器内存占用如图所示:
当前的配置距离完美还有很多的距离了。现在的配置内存反而没有完全利用起来。接下来可能要提高一些MariaDB可分配的资源(比如开启query cache)来提高性能,减少CPU的占用。
amazing!