这次我们来讲讲对象池、连接池的意义,在此之前我们先了解学习一些其他的基础知识,以便我们结合理解池的意义。
#nginx与php-fpm的进程模型
nginx采用多进程模型,启动之后的进程将包含一个master和多个worker进程。 master是worker的父进程,主要职责是用来管理worker进程的。
- 向worker进程发送信号,如通知退出
- 监控worker状态,当worker退出后(无论正常异常),可以重新启动新的worker。
可以实现从容重启:master进程在接收到信号后,会先重新加载配置,然后再启动新进程开始接收新请求,并向所有老进程发送信号告知不再接收新请求并在处理完所有未处理完的请求后自动退出。
worker进程负责处理请求,如果是静态文件则可以直接处理完,如果是php程序还需要调用php来处理,当php处理完成时获取php的返回,并返回给客户端。 采用的是异步非堵塞,当调用php的时候不会堵塞等待,会抽空处理下一个请求,当php处理完成时恢复之前的请求并返回给客户端。 php-fpm是php-cgi的管理器,在php >= 5.3.3
就已经集成在php中了。 它的出现提供了更好的php管理方式
- 可以平滑停止/启动php进程(重载配置生效)
- 可以配置监控多个端口和使用不同的配置
php脚本的解释器是php-cgi php-fpm是一个管理器,管理对象是php-cgi php-fpm实现了fastcgi协议,当php-fpm启动时,会启动多个cgi解释器进程。 web服务器可以发送数据给php-fpm,php-fpm再把数据发给php-cgi处理。(跟nginx发送数据给php-fpm类似)
#常驻内存下程序的对象回收
常驻内存程序是指把自己装入内存后将控制返回给操作系统,直到运行结束、异常、用户手动退出才会中断运行的程序。 当程序运行时,对象和变量将会一直存在。除非在程序中释放销毁。
#高并发下频繁new对象的资源占用
当我们new一个对象的时候,需要先经过这几个步骤:类加载检查、分配内存空间、设置类的基本信息、调用初始化构造函数。
首先我们看看构造函数这一块,这是在代码中按我们的需求和意愿编写的。 在这一块中我们经常会做一些配置检测、数据初始化、数据库连接(网络io)等。
接下来是分配内存空间 OS的内存分配器一般是预先向OS申请一大段内存。然后每次分配时,再将里面的一小段标记为已分配
,释放的时候再标记成未分配。 由于是有很多程序在运行,所以分配和释放会交替存在,得到的结果可能是 分配1段-未分配1段-分配2段-未分配2段 一个一个的未分配
就是内存碎片,会占用额外的内存,碎片不一定可以马上被重复使用(当分配不出连续内存时,需要向OS申请更多的内存)
同时,创建和销毁对象时,OS都需要做一些处理工作,也会产生资源占用。
new太多对象,然后导致cpu负载上线让全站死机的概念
若程序未产生IO(网络请求、读写文件等),执行时间等于cpu的占用时间。 频繁地创建销毁对象将会占用更多cpu资源,高并发时容易导致cpu长期处于高负载运行状态。
什么是对象池
对象池就是一个在程序启动的时候先创建好若干个可以重复使用的对象。 当程序其他地方需要使用该类型对象时,不再是向系统申请创建,而是向池发出请求。 池将会从池内发配出一个对象提供使用,当程序使用完毕后,需要将对象归还给对象池做管理。
对象池服务可以减少从头创建每个对象的系统开销。
大并发下多个mysql连接导致mysql繁忙全站崩溃
<?php function db(){ return mysqli_connect("localhost","root","root"); } for ($i=0; $i < 10000; $i++) { $name = "db{$i}"; $$name = db(); }
这一个demo将会产生报错:Warning: mysqli_connect(): (08004/1040): Too many connections
我们习惯性地在PHP脚本中不会主动关闭mysql连接,而是等到脚本运行完毕之后再由gc自动回收。在这个期间将会继续占用连接资源,而连接资源的数量又是有限制的,所以会更快出现连接不够用的情况。 处理会影响程序的运行,同时还将可能导致全站崩溃
。
- mysql是一个连接创建一个线程处理。
- 创建销毁mysql线程需要的内存等性能消耗、线程缓存命中率下降
- mysql底层几乎在同时需要处理几百个线程提交的查询请求,而cpu一次只能处理一条指令,并且数据库查询需要产生IO,在IO期间cpu将会切换上下文处理其他的请求,当cpu频繁切换上下文,性能抖动,发生性能下降甚至宕机的情况。
连接池 保护mysql不崩溃
连接池是将已经创建好的连接保存在池中,当有请求来时,直接使用已经创建好的连接对数据库进行访问。
<?php class Pool{ private $pool = []; private $min = 5; private $max = 100; private $now; public function __construct() { // 在池创建的时候就先创建好一些连接 for ($i = 0 ; $i < $this->min; $i++){ $this->pool[] = mysqli_connect("localhost","root","root"); $this->now++; } } public function get() { // 这里要判断当前池还有没有空闲的 // 若没有,则判断当前已经提供的服务数量大不大于最大数量 如果还没有达到最大数量 可以向系统再申请一个资源到池中 // 如果已经达到最大数量,并且池内没有服务了,则进行短暂等等看看有没有 // 需要销毁避免同一个连接多处使用,会冲突 $connect = array_shift($this->pool); return $connect;//伪代码 } public function recovery($connect) { $this->pool[] = $connect; } }
因为连接池需要长期保持在线,在传统的php脚本中不支持,在swoole
中可以常驻内存运行,即可使用连接池
这样省略了创建连接和销毁连接的过程。这样性能上得到了提高。 然而除了性能上的提高外,还有一个意义也很重要:保护服务稳定运行,不发生全站崩溃。 在上面一点我们已经提到,更多的链接将会导致cpu频繁切换上下文,性能抖动,严重情况时将会全站崩溃。 假设本来我们的服务器配置是可以保证1000个连接同时稳定运行,突然某一时刻有3000个人并发,导致连接不够用,那么是保证原有1000人都正常运行好,还是让这3000人争抢资源最终导致机器响应不了全站崩溃好呢?
#连接池的意义此时才得以体现,我们设置连接池的最大数量为机器能承受并且稳定运行的最大数量。
当已经有这么多的数量在服务的时候,后面的请求申请连接资源时需要进行短暂的等待,若时间到了还是没有空余连接提供,则需要熔断服务,返回给客户端失败。 这样子可以保证机器长期稳定服务。若是越来越多的客户端申请不到资源,则需要提高机器配置。(因为我们的连接池最大数量已经是机器的瓶颈,只能通过硬件配置来提升能服务的数量)
nginx - php fpm在大并发下504
在最开始的时候已经介绍过nginx和php的运行进程模型,php-fpm就是一个池管理器,内部装了若干个php-cgi程序,当nginx申请解析php脚本时,php-fpm则分配一个php-cgi出去处理,处理完则收回管理。 在高并发下,nginx会产生504错误,这就是我们上面介绍到的,客户端进行了短暂的 等待
后,仍然申请不到资源,则只能告诉客户端失败。 (在京东、淘宝的大活动期间很有机会碰到504错误哦! 这种情况下我们一般只需要刷新页面即可。 因为再刷新时大几率已经有连接资源空闲了!)
- Nginx 504 Gateway Time-out的含义是没有请求到可以执行的PHP-CGI。
总结
连接池、对象池的意义不仅仅是可以减少频繁创建销毁对象连接的性能开销 更大的意义是可以保证应有服务客户端的稳定运行。