php项目上线优化

PHP项目中,尤其是在高并发大流量的场景中,如何提升PHP的响应时间,合理利用服务器性能,是一项十分重要的工作。

优化前我们有必要先理解PHP-FPM + Nginx 的工作机制,以及PHP脚本解释执行的机制

LNMP(linux+nginx+mysql+php)优化

一、PHP-FPM + Nginx 的工作机制

请求从Web浏览器到Nginx,再到PHP处理完成,一共要经历如下五个步骤:

第一步:启动服务

启动PHP-FPM。

PHP-FPM 支持两种通信模式:TCP socket和Unix socket;
PHP-FPM 会启动两种类型的进程:Master 进程 和 Worker 进程,前者负责监控端口、分配任务、管理Worker进程;后者就是PHP的cgi程序,负责解释编译执行PHP脚本。


启动Nginx。

首先会载入 ngx_http_fastcgi_module 模块,初始化FastCGI执行环境,实现FastCGI协议请求代理
这里要注意:fastcgi的worker进程(cgi进程),是由PHP-FPM来管理,不是Nginx。Nginx只是代理

第二步:Request => Nginx

Nginx 接收请求,并基于location配置,选择一个合适handler
这里就是代理PHP的 handler

第三步:Nginx => PHP-FPM

Nginx 把请求翻译成fastcgi请求
通过TCP socket/Unix Socket 发送给PHP-FPM 的master进程

第四步:PHP-FPM Master => Worker

PHP-FPM master 进程接收到请求
分配Worker进程执行PHP脚本,如果没有空闲的Worker,返回502错误
Worker(php-cgi)进程执行PHP脚本,如果超时,返回504错误
处理结束,返回结果

第五步:PHP-FPM Worker => Master => Nginx

PHP-FPM Worker 进程返回处理结果,并关闭连接,等待下一个请求
PHP-FPM Master 进程通过Socket 返回处理结果
Nginx Handler顺序将每一个响应buffer发送给第一个filter → 第二个 → 以此类推 → 最终响应发送给客户端

二、PHP脚本解释执行的机制

了解了PHP + Nginx 整体的处理流程后,我们接下来看一下PHP脚本具体执行流程,首先我们看一个实例:

<?php   
    if (!empty($_POST)) {
        echo "Response Body POST: ", json_encode($_POST), "\n";
    }
    if (!empty($_GET)) {    
        echo "Response Body GET: ", json_encode($_GET), "\n";
    }

我们分析一下执行过程:
1.php初始化执行环节,启动Zend引擎,加载注册的扩展模块
2.初始化后读取脚本文件,Zend引擎对脚本文件进行词法分析(lex),语法分析(bison),生成语法树
3.Zend 引擎编译语法树,生成opcode,
4.Zend 引擎执行opcode,返回执行结果

在PHP cli模式下,每次执行PHP脚本,四个步骤都会依次执行一遍;
在PHP-FPM模式下,步骤1)在PHP-FPM启动时执行一次,后续的请求中不再执行;步骤2)~4)每个请求都要执行一遍;
其实步骤2)、3)生成的语法树和opcode,同一个PHP脚本每次运行的结果都是一样的,在PHP-FPM模式下,每次请求都要处理一遍,是对系统资源极大的浪费,那么有没有办法优化呢?

OPCache 是Zend官方出品的,开放自由的 opcode 缓存扩展,还具有代码优化功能,省去了每次加载和解析 PHP 脚本的开销。PHP 5.5.0 及后续版本中已经绑定了 OPcache 扩展。

优化方式

一、Linux内核优化

内核参数详细可见Linux系统常用内核网络参数介绍与常见问题处理

vim /etc/sysctl.conf
net.core.somaxconn = 20480
net.core.netdev_max_backlog = 20480
net.ipv4.tcp_max_syn_backlog = 20480
net.ipv4.tcp_max_tw_buckets = 800000
sysctl -p

其中net.core.somaxconn是Linux中的一个内核(kernel)参数,表示socket监听(listen)的backlog上限。

syns queue
用于保存半连接状态的请求,其大小通过/proc/sys/net/ipv4/tcp_max_syn_backlog指定,一般默认值是512,不过这个设置有效的前提是系统的syncookies功能被禁用。互联网常见的TCP SYN FLOOD恶意DOS攻击方式就是建立大量的半连接状态的请求,然后丢弃,导致syns queue不能保存其它正常的请求。
accept queue
用于保存全连接状态的请求,其大小通过/proc/sys/net/core/somaxconn指定,在使用listen函数时,内核会根据传入的backlog参数与系统参数somaxconn,取二者的较小值。
如果accpet queue队列满了,server将发送一个ECONNREFUSED错误信息Connection refused到client。

上述调整个人感觉效果不大,但看描述应该可以减少大并发报错,增加稳定性

【linux】cpu过高解决方法

1、使用top命令查看cpu的进程占用情况:

二、PHP-FPM优化

1.连接方式( UNIX Socket 和 TCP/IP Socket )

UNIX Socket是同一台服务器上不同进程间的通信机制。
TCP/IP Socket是网络上不同服务器之间进程的通信机制,也可以让同一服务器的不同进程通信。

Postgres的一位核心开发者曾经做过实验,证明UNIX Socket的方式比TCP/IP Socket方式要快31%,所以,在同一个服务器上应该优先选择UNIX Socket方式。

【外网访问】若需外网访问,连接方式为TCP套接字,并将[连接信息-绑定IP]改为0.0.0.0

【外网访问】配置正确的IP白名单,在防火墙/安全组放行监听端口,有安全风险,需谨慎

2.运行模式(静态模式、动态模式、按需模式)

修改www.conf设置

;进程数优化
pm = static
pm.max_children = 400
pm.max_requests = 500
pm.status_path = /status.php
;内存大于等于8G用
pm.start_servers = 20
pm.min_spare_servers = 15
pm.max_spare_servers = 35

【静态模式】始终维持设置的子进程数量,对内存开销较大,但并发能力较好

【动态模式】按设置最大空闲进程数来收回进程,内存开销小,建议小内存机器使用

【按需模式】根据访问需求自动创建进程,内存开销极小,但并发能力略差

服务器内存够时选择 【静态模式】 是最优解,【最大子进程数量】 max_children 越大,并发能力越强,但max_children最大不要超过5000,【内存】每个PHP子进程需要20~30MB左右内存,过大的max_children会导致服务器不稳定,如16GB内存电脑 max_children 应在500以下。

如果内存较小情况应选择 【动态模式】 ,与【起始进程数】start_servers(服务启动后初始进程数量)、【最大空闲进程数】min_spare_servers(清理空闲进程后的保留数量)和【最大空闲进程数】max_spare_servers(当空闲进程达到此值时清理)有关

修改www.conf设置

listen.backlog =20480

listacklog参数只表示全连接队列的大小(accept队列有一个上限值,由系统参数somaxconn指定,也就是全连接队列的大小等于min(somaxconn, backlog))由上方 net.core.somaxconn共同决定,谁小选谁

三、针对CPU的nginx配置优化

针对CPU的nginx配置优化
处理器已经进入了多核时代,多核的意思是一个处理器集成两个或者多个计算引擎。一枚多核处理器可以承载多枚内核。将每个内核作为分立的逻辑处理器,通过多喝个内核之间的划分,在特定的周期执行更多的任务,提高并行处理能力。

在Nginx配置中,有两个关于进程的指令,worker_processes 和worker_cpu_affinity,worker_processes指令是用来设计Nginx进程数,worker_cpu_affinity指令用来分配每个进程的CPU的工作内核。

规则设定
(1)cpu有多少个核,就有几位数,1代表内核开启,0代表内核关闭

(2)worker_processes最多开启8个,8个以上性能就不会再提升了,而且稳定性会变的更低,因此8个进程够用了

示例:linode VPS 最低配,8核CPU,nginx配置信息:

worker_processes 8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;

上面的配置表示:8核CPU,开启8个进程。00000001表示开启第一个cpu内核,00000010表示开启第二个cpu内核,依次类推;有多少个核,就有几位数,1表示该内核开启,0表示该内核关闭。

配置实例:

1、2核CPU,开启2个进程

worker_processes 2;
worker_cpu_affinity 01 10;
2、2核CPU,开启4进程

worker_processes 4;
worker_cpu_affinity 01 10 01 10;
3、2核CPU,开启8进程

worker_processes 8;
worker_cpu_affinity 01 10 01 10 01 10 01 10;
4、8核CPU,开启2进程

worker_processes 2;
worker_cpu_affinity 10101010 01010101;
说明:10101010表示开启了第2,4,6,8内核,01010101表示开始了1,3,5,7内核

如果多个CPU内核的利用率都相差不多,证明nginx己经成功的利用了多核CPU。
测试结束后,CPU内核的负载应该都同时降低。

四、PHP脚本解释执行优化

1.使用Opcache

什么是Opcache?

每一次执行 PHP 脚本的时候,该脚本都需要被编译成字节码,而 OPcache 可以对该字节码进行缓存,这样,下次请求同一个脚本的时候,该脚本就不需要重新编译,这极大节省了脚本的执行时间,从而让应用运行速度更快,同时也节省了服务器的开销。

如何使用OPcache?

首先,我们需要确保在服务器上安装了 OPcache,从 PHP 5.5 开始,OPcache 已经成为 PHP 核心的一部分,基本上不需要手动去安装这个扩展,如果用宝塔面板打开php安装对应扩展即可。
可以通过查看 phpinfo() 进行确认是否安装

<?php
phpinfo();

该脚本会显示所有 PHP 安装的扩展。在页面搜索 “OPcache”,如果找到,证明已经安装。如果没有,则需要自己去安装。

OPCache 的配置

1 内存配置
opcache.preferred_memory_model=“mmap” OPcache 首选的内存模块。如果留空,OPcache 会选择适用的模块, 通常情况下,自动选择就可以满足需求。可选值包括:mmap,shm, posix 以及 win32。
opcache.memory_consumption=64 OPcache 的共享内存大小,以兆字节为单位,默认64M
opcache.interned_strings_buffer=4 用来存储临时字符串的内存大小,以兆字节为单位,默认4M
opcache.max_wasted_percentage=5 浪费内存的上限,以百分比计。如果达到此上限,那么 OPcache 将产生重新启动续发事件。默认5
2 允许缓存的文件数量以及大小
opcache.max_accelerated_files=2000 OPcache 哈希表中可存储的脚本文件数量上限。真实的取值是在质数集合 { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987 } 中找到的第一个大于等于设置值的质数。设置值取值范围最小值是 200,最大值在 PHP 5.5.6 之前是 100000,PHP 5.5.6 及之后是 1000000。默认值2000
opcache.max_file_size=0 以字节为单位的缓存的文件大小上限。设置为 0 表示缓存全部文件。默认值0
3 注释相关的缓存
opcache.load_commentsboolean 如果禁用,则即使文件中包含注释,也不会加载这些注释内容。本选项可以和 opcache.save_comments 一起使用,以实现按需加载注释内容。
opcache.fast_shutdown boolean 如果启用,则会使用快速停止续发事件。所谓快速停止续发事件是指依赖 Zend 引擎的内存管理模块 一次释放全部请求变量的内存,而不是依次释放每一个已分配的内存块。
4 二级缓存的配置
opcache.file_cache 配置二级缓存目录并启用二级缓存。启用二级缓存可以在 SHM 内存满了、服务器重启或者重置 SHM 的时候提高性能。默认值为空字符串 “”,表示禁用基于文件的缓存。
opcache.file_cache_onlyboolean 启用或禁用在共享内存中的 opcode 缓存。
opcache.file_cache_consistency_checksboolean 当从文件缓存中加载脚本的时候,是否对文件的校验和进行验证。
opcache.file_cache_fallbackboolean 在 Windows 平台上,当一个进程无法附加到共享内存的时候, 使用基于文件的缓存,也即:opcache.file_cache_only=1。需要显示的启用文件缓存。

2.升级php8

如果项目减容情况下建议使用高版本php8,执行效率会快很多

升级到php8后,Laravel应用程序停止工作

更新为 php 8 laravel 应用程序后停止工作,这是我收到的错误:

Deprecated: Method ReflectionParameter::getClass() is deprecated in /Users/.../Sites/.../vendor/laravel/framework/src/Illuminate/Container/Container.php on line 871
Deprecated: Method ReflectionParameter::getClass() is deprecated in /Users/.../Sites/.../vendor/laravel/framework/src/Illuminate/Container/Container.php on line 945
Deprecated: Method ReflectionParameter::getClass() is deprecated in /Users/.../Sites/.../vendor/laravel/framework/src/Illuminate/Container/Container.php on line 871
Deprecated: Method ReflectionParameter::getClass() is deprecated in /Users/.../Sites/.../vendor/laravel/framework/src/Illuminate/Container/Container.php on line 945

解决方案

正如这里所解释的,laravel 6、7 和 8 的最新版本已经对 php 8 进行了所需的更改。您所要做的就是:

1-将 php 8 添加到您的composer.json(我保留了 v7.4,以防生产服务器尚不支持 php 8)

"php": "^7.4|^8.0",

2-运行composer update以将您的laravel更新到最新版本

composer update

注意:备份好项目在进行更新测试

五、mysql优化

1.Laravel使用Redis缓存

在现代的数据库应用中,Redis 已经占据了很重要的位置。关于 Redis 的优点相信也不用我多说了,快速的内容访问也能够充当缓存数据库来使用。

安装predis/predis

进入Laravel项目所在目录,使用composer安装

composer require predis/predis

首先在config/app.php中添加redis服务

//去掉下面这一句的注释
Illuminate\Redis\RedisServiceProvider::class,

打开.env文件配置下列项

CACHE_DRIVER=redis

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

config/cache.php缓存配置文件中有这样的信息

'redis' => [
   'driver' => 'redis',
   'connection' => 'cache',
   'lock_connection' => 'default',
],

同时,config/database.php配置文件中有这样的信息

'redis' => [

        'client' => env('REDIS_CLIENT', 'phpredis'),

        'options' => [
            'cluster' => env('REDIS_CLUSTER', 'redis'),
            'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
        ],

        'default' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_DB', '0'),
        ],

        'cache' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_CACHE_DB', '1'),
        ],

    ],

很明显,cache使用的redis连接为cache连接,该连接的配置在database.php文件中,即为本机redis上的数据库1

验证
我们先使用Laravel的方法添加缓存

Cache::put('mykey','1234');

然后到redis安装目录下查询

redis-cli
select 1
keys *

输入以上命令后,会列出目前数据库内所有的键
可以看到我们存入mykey,但实际上它的键并非mykey,而是类似于下面的结构:

laravel_database_laravel_cache:mykey

使用命令

get laravel_database_laravel_cache:mykey

即可取出数据。
至此,已验证相关配置成功。

如果是宝塔可以直接在数据库这里看

顺便说一下Redis持久化:RDB和AOF

一、为什么需要持久化?
Redis对数据的操作都是基于内存的,当遇到了进程退出、服务器宕机等意外情况,如果没有持久化机制,那么Redis中的数据将会丢失无法恢复。有了持久化机制,Redis在下次重启时可以利用之前持久化的文件进行数据恢复。Redis支持的两种持久化机制:

RDB:把当前数据生成快照保存在硬盘上。
AOF:记录每次对数据的操作到硬盘上。

rdb的优点

  1. 体积更小:相同的数据量rdb数据比aof的小,因为rdb是紧凑型文件。
  2. 恢复更快:因为rdb是数据的快照,基本上就是数据的复制,不用重新读取再写入内存。
  3. 性能更高:父进程在保存rdb时候只需要fork一个子进程,无需父进程的进行其他io操作,也保证了服务器的性能。

rdb的缺点

  1. 故障丢失:因为rdb是全量的,我们一般是使用shell脚本实现30分钟或者1小时或者每天对redis进行rdb备份,(注,也可以是用自带的策略),但是最少也要5分钟进行一次的备份,所以当服务死掉后,最少也要丢失5分钟的数据。
  2. 耐久性差:相对aof的异步策略来说,因为rdb的复制是全量的,即使是fork的子进程来进行备份,当数据量很大的时候对磁盘的消耗也是不可忽视的,尤其在访问量很高的时候,fork的时间也会延长,导致cpu吃紧,耐久性相对较差。

aof的优点

  1. 数据保证:我们可以设置fsync策略,一般默认是everysec,也可以设置每次写入追加,所以即使服务死掉了,也最多丢失一秒数据
  2. 自动缩小:当aof文件大小到达一定程度的时候,后台会自动的去执行aof重写,此过程不会影响主进程,重写完成后,新的写入将会写到新的aof中,旧的就会被删除掉。但是此条如果拿出来对比rdb的话还是没有必要算成优点,只是官网显示成优点而已。

aof的缺点

  1. 性能相对较差:它的操作模式决定了它会对redis的性能有所损耗。
  2. 体积相对更大:尽管是将aof文件重写了,但是毕竟是操作过程和操作结果仍然有很大的差别,体积也毋庸置疑的更大。
  3. 恢复速度更慢:AOF 在过去曾经发生过这样的 bug : 因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样。测试套件里为这种情况添加了测试: 它们会自动生成随机的、复杂的数据集, 并通过重新载入这些数据来确保一切正常。 虽然这种 bug 在 AOF 文件中并不常见, 但是对比来说, RDB 几乎是不可能出现这种 bug 的。

RDB和AOF

RDB是将支持当前数据的快照存成一个数据文件的持久化机制。

  1. 在生成快照时,将当前进程fork出一个子进程.
  2. 然后再子进程中循环所有的数据,将数据写入到二进制文件中。
  3. 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。

AOF:Redis 默认不开启。它的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。AOF的工作原理就是是将写操作追加到文件中,文件的冗余内容会越来越多。所以Redis 新增了重写机制。当AOF文件的大小超过所设定的最大值时,Redis就会对AOF文件的内容压缩。

从持久化中恢复数据

数据的备份、持久化做完了,我们如何从这些持久化文件中恢复数据呢?如果一台服务器上有既有RDB文件,又有AOF文件,该加载谁呢?

redis重启时判断是否开启aof,如果开启了aof,那么就优先加载aof文件;
如果aof存在,那么就去加载aof文件,加载成功的话redis重启成功,如果aof文件加载失败,那么会打印日志表示启动失败,此时可以去修复aof文件后重新启动;
若aof文件不存在,那么redis就会转而去加载rdb文件,如果rdb文件不存在,redis直接启动成功;
如果rdb文件存在就会去加载rdb文件恢复数据,如加载失败则打印日志提示启动失败,如加载成功,那么redis重启成功,且使用rdb文件恢复数据;

2.合理选择mysql引擎

MyISAM和InnoDB的对比

Mysql 数据库中,最常用的两种引擎是 innordb 和 myisam。InnoDB 是 Mysql 的默

认存储引擎。

事务处理上方面

MyISAM 强调的是性能,查询的速度比 InnoDB 类型更快,但是不提供事务支持。

InnoDB 提供事务支持事务。

外键

MyISAM 不支持外键,InnoDB 支持外键。

MyISAM 只支持表级锁,InnoDB 支持行级锁和表级锁,默认是行级锁,行锁大幅度提高了多用户并发操作的性能。innodb 比较适合于插入和更新操作比较多的情况,而 myisam 则适合用于频繁查询的情况。另外,InnoDB 表的行锁也不是绝对的,如果在执行一个 SQL 语句时,MySQL 不能确定要扫描的范围,InnoDB 表同样会锁全表,例如 update table set num=1 where name like “%aaa%”。

全文索引

MyISAM 支持全文索引, InnoDB 不支持全文索引。innodb 从 mysql5.6 版本开始提供对全文索引的支持。

表主键

MyISAM:允许没有主键的表存在。

InnoDB:如果没有设定主键,就会自动生成一个 6 字节的主键(用户不可见)。

注意:

1、MyISAM存储引擎的非主键索引与InnoDB存储引擎的非主键索引数据结构是一样的,但是最底层叶节点存储的数据和指针信息是不同的。

MyISAM存储引擎的非主键索引最底层叶节点存储的是索引信息数据指向文件的指针信息

InnoDB存储引擎的非主键索引最底层叶节点存储的是索引信息指向主键索引的指针信息

2、MyISAM存储引擎的数据文件跟索引文件是两个文件。InnoDB存储引擎的数据文件跟索引文件是一个文件。所以找到了主键索引位置也就找到了数据位置。所以InnoDB主键索引被称为聚合索引。

0 评论
内联反馈
查看所有评论