nginx swoole php-fpm工作原理及比较。

最近仔细翻阅了网上的相关文章,了解nginx、php-fpm的工作模式,cpu 进程及线程的切换(抢占模式、固定模式)。本文为自己学习理解

CPU 进程与线程

  • CPU:一台服务器可以有1个或多个CPU、每个CPU可以有多个CPU核心(参见买电脑时,给你介绍这是一台4核8线程的电脑,意思是这个电脑有4个CPU,但每个CPU有两个运算核心,也就是一个CPU可以同时运行两个线程)。
  • 进程:程序运行的一个实体的运行过程,是系统进行资源分配和调配的一个独立单位。
  • 线程:线程是进程运行和执行的最小调度单位。

1个CPU同时只有1个运算核心时:串行执行
多个CPU或有多个运算核心时:并行执行

关于进程与线程
一个进程至少包含一个线程。而一个线程只能属于一个进程。一个进程中的多个线程可以共享进程的内存资源。(这里需要注意,因为共享内存资源,所以在做技术开发的时候我们要考虑到不同线程运行时对共有资源的操作)

进程的几种状态

  • 创建态:这个时候进程刚刚被创建,CPU会分配内存地址,内存空间(用户空间和内核空间。用户空间是给进程用的,内核空间是给CPU操作系统用的)

  • 就绪态:在这个状态下,进程已做好了运行前的一切工作,只需要等待调度程序的调度交给CPU去运行。(调度程序根据一系列的算法,将就绪态的进程归类成了各个队列,优先级越高的队列被分配到CPU运行时间片的概率越大,这里是以抢占模式为例)

  • 运行态:进程真正在执行的状态,这个时候进程已被调度程序调度,CPU在进程上执行了。

    1、如果CPU在执行过程中,因进程产生了阻塞(I/O阻塞:常见操作如数据库连接,文件操作(打开,读写,关闭等),数据库CURD操作,网络请求(restful接口调用),RPC。sleep)就会将此进程状态变更为阻塞态,并调度至下一个进程运行。

    2、如果是因进程CPU运行时间片结束但进程任务尚未结束就会保存进程上下文信息,统计计数等信息,将进程加入到就绪态,等待调度程序的再次调度,并调度至下一个进程运行。

    3、 如果在CPU运行时间片内,进程任务结束了,那么就将进程的状态改为结束态,并调度至下一个进程运行。

  • 阻塞态:进程因产生了阻塞被加入到阻塞态,等待阻塞结束(数据库连接成功,CURD返回相应操作,网络请求响应,文件操作(打开,读写,关闭等))就会将这个进程重新加入到就绪态,等待调度程序的调度交给CPU执行

  • 结束态:在这个状态下的进程,等待释放内存空间,并销毁相关进程信息(进程上下文,统计计数等),归还资源给系统。

nginx

一个请求的流程逻辑大图
在这里插入图片描述

完整的请求模式如上图所示,用户从DNS服务器上通过域名获得服务器IP地址(中间可能产生的过程有DNS轮询和SLB负载均衡)。

并请求指定端口(http:80 https:443)

此时就由work-nginx获取到这个请求(惊群现象,多个work-nginx 抢占这个请求,只有一个能成功)

只要有一个work-nginx获得到了这个请求的锁,那么其他的work-nginx将不能处理此请求,只有获得到这个请求的锁的work-nginx才能处理这个请求,获得锁的work-nginx会解析这个请求

如果是访问静态资源(css、html、js、媒体文件)就直接返回相应的内容给用户,如果解析出此请求是一个php请求,就将此请求转发给work-php-fpm处理(惊群现象,多个work-php抢占这个请求,只有一个能成功)。(注意,当请求转发后,获得锁的work-nginx接着又可以去获得下一个请求的锁,而不用等待当前请求处理完成后才去处理下一个,这是因为nginx采用I/O复用模型

特点:异步,非阻塞

每进来一个request,会有一个worker进程去处理。但不是全程的处理,处理到什么程度呢?处理到可能发生阻塞的地方,比如向上游(后端php-fpm主进程)服务器转发request,并等待请求返回。那么,这个处理的worker不会这么一直等着,他会在发送完请求后,注册一个事件:“如果upstream返回了,告诉我一声,我再接着干”。于是他就休息去了。这就是异步。此时,如果再有request 进来,他就可以很快再按这种方式处理。这就是非阻塞和IO多路复用。而一旦上游服务器返回了,就会触发这个事件,worker才会来接手,这个request才会接着往下走。这就是异步回调。

php-fpm

当work-php-fpm 接收到来自nginx的转发请求时,会解析这个请求并处理。但work-php-fpm受master-php-fpm管理。当一个work-php-fpm处理一定数量或是生存一定时间后,master-php-fpm会向该work-php-fpm发出信号,通知该关闭了(此时当work-php-fpm结束当前请求后会结束进程,且master-php-fpm会根据策略启动新的work-php-fpm进程(受当前环境影响,如果闲时根据配置可能不会再产生新的进程))

  • 每个work-php-fpm 都受master-php-fpm管理(管理内容有:创建、销毁、平滑重启等)。
  • 每个work-php-fpm 在处理一定数量后(由配置文件配置),都会重启,这是为了避免内存泄露
  • 同一时刻一个work-php-fpm只能处理一个请求,只有当当前的请求处理完毕后才可以去处理下一个请求
  • 当所有的work-php-fpm都处于工作状态时,如果此时还有新的请求加入,那么这些请求将会阻塞在master-php-fpm上,等待work-php-fpm进程处理完当前请求后,再将请求给此子进程。如果阻塞队列超出配置值,则会在master-php-fpm进程直接向nginx抛出异常。
  • 所有的work-php-fpm 均是使用抢占模式来处理请求,抢占模式:每个进程都可以得到一定CPU时间片的时间处理,时间片结束或是进程发生阻塞(数据库连接、网络请求I/O(如发送短信、邮件第三方接口)、读写文件、sleep等待)就会触发上下文切换,CPU会经由调度程序去处理下一个进程的内容。等待阻塞结束再将进程由阻塞态加入就绪态队列(此时的进程处于就绪队列高等级),等待cpu调度执行
  • php-fpm 配置建议采用动态配置,闲时多少个工作进程,忙时最多多少个工作进程,能达到更高的处理性能,但也不能盲目的增加工作进程,需要结合CPU核数配置合适值,因为CPU在进程上下文切换时也是需要消耗资源的,当进程过多,CPU就一直在上下文切换上消耗资源,且前面已执行完时间片的php-fpm也不能快速的得到CPU调度(因为进程过多CPU还在后面调度其它进程的执行呢,哪有时间管你)

我们平时在开发上需要注意的点

  • 如果一个接口的运行如果比较耗时,那么说明这个接口做的工作越多,越复杂,吞吐量不高。那么此时需要评估这个接口一般由谁调用,如果是由用户使用,那么就需要考虑是否可以拆分这个接口所做的工作。解决方案:
    1、队列执行:将这个接口的任务加入队列,等待队列执行成功后再通知用户。队列执行串行化,避免多个队列同时执行导致cpu性能开销大。
    2、协程:任务分段开多个线程执行。系统创建线程的开销比创建一个进程的开销小,因为同一个进程中线程间共享内存

  • 数据库死锁问题:我们在开发中应尽量避免在一个事务中锁住两行记录,举例,账户间的转账,需要同时锁住两个记录再进行资金转账。类似于这种的情况下我们要做好重试机制,当两个请求发生数据库事务死锁时,重试机制是一个补救的办法,如A锁住了记录a1,即将要去锁住记录b1,但B锁住了b1,即将要去锁住记录a1。此时A、B互斥了,所以产生了所等待,当A一旦事务超时,那么A就会发生回滚(回滚会释放a1的锁),此时B就拿到a1的锁继续执行后面的逻辑。当A回滚成功后,因重试机制又去重复执行,此时因B拿到了a1、b1的锁,只能等待B处理完成提交后(提交成功也会释放锁)才可以拿到锁跑逻辑

  • 进程阻塞:由前,我们知道php-fpm同一时刻只能处理一个请求,那么一旦这个请求发生了进程阻塞,CPU将会执行上下文切换去处理其他的进程。如果在高迸发时期,这个进程阻塞是要命的。原因:当所有的工作进程都处于阻塞态,那么新的请求进来只能在master-php-fpm的请求队列中等待,如果此时连master-php-fpm的队列都已经满了那么就只能给用户抛异常了。接口的吞吐量要高的话,一定要
    1、接口工作内容简单
    2、不易发生阻塞(一般最常见的就是I/O阻塞)

为什么说swoole比php-fpm快

异步风格

针对这个问题我曽学习了swoole,并参考文献及做了测试。我使用的是异步风格的swoole http服务器,并在前端采用nginx代理,所有的php请求均由nginx代理转发至swoole来处理相应的php请求

  • 首先我们要了解swoole是常驻内存的。因为常驻所以就没有一个请求创建一个进程的这种开销所以快。且一旦进程创建后再次修改代码将不会生效,因为程序代码已经载入到了内存中,执行的直接跑就是,减少了编译次数。

  • swoole与php-fpm也有相似的地方(swoole 采用I/O同步阻塞模式),相似的地方如下:
    1、当某个进程处理了一定请求数后都会重启,目的是为了放置内存泄漏
    2、都是采用 master-work模式:即是由master-swoole监听子进程的状态并根据策略来实现销毁还是创建。work-swoole负责监听请求并accept处理。
    3、每个work-swoole进程同一时刻只能处理一个请求
    4、swoole下的所有work-swoole均处于工作状态(I/O阻塞、sleep、task active),那么新进来的请求会加入请求队列,等待当中的某个work-swoole结束工作态后,将从请求队列中将这个请求分派给此work-swoole去执行

  • swoole和php-fpm不同之处:swoole在启动时,一旦定义了一个变量,那么这个变量在整个进程的生存周期中都存在,使用时直接从内存沿用这个对象。因为这个特性,所以每次请求来的时候,都不用再去重新定义框架的一些内容,如框架的启动组件这些统统不用,在work-swoole启动的时候这些工作已经做好了。

  • task任务进程,异步程序的实现,将一个复杂的任务拆分成多个小任务,并交给task任务去跑,极大提高了cpu的利用率。

基于以上几点,所以swoole比php-fpm快。但是在使用的时候也有要注意的点。

  • 如果没有一定的把握,一定不要去操作全局静态变量(新增、删除、修改),这样可能会导致内存溢出
  • 数据库连接的问题,数据库连接是有超时时间的,当连接超时后就没有办法再操作数据库了,所以使用要考虑是否做一个数据库连接池实现,redis也同理。一般第三方服务如阿里云RDS、polarDB mysql 都有连接池的实现方案,我们php直接用就行了,如果不喜欢,我们直接用swoole做一个mysql连接池也可以,网上目前已有成熟的解决方案了。我们直接git下来直接跑就行。

协程风格

协程即是用户线程,是由用户自己创建的线程,因此无法利用多核CPU的特性。如需使用多核CPU,就需要采用多进程模型。
协程的出现极大提高了php的执行效率。但随之也带来了一系列的问题

举例: 1W个请求同时打上来,采用协程模式,需要对这1W的请求处理。

  • 1、执行速度太快(全程无阻塞),对数据库的压力很大
    数据库连接池或是数据库本身是否支持这么多数据库连接的建立?如果不支持这么多连接的建立,那么当超出连接数后再次连接是哪种情况(阻塞模式或是抛出连接数耗尽异常)。数据库IOPS指数是否能撑住1W个请求的处理。

  • 2、 1W个请求过来,每个请求以协程的方式运行,每个请求被分配了多少内存?操作系统最多支持一个进程创建多少个协程?如果超出阀值,再次进来新的请求策略是怎样的?(阻塞还是抛出异常)

但不能说协程不好,相反,协程极大利用了cpu的性能,在某些需要密集运算的场景或是对并发处理有较高要求的场景,协程就是很适合的一种方案。任何方案总是要采用熟悉,把握的住的方案才是最好的方案。

抢占模式、固定模式

为什么说抢占模式的性能比固定模式的效率要高
先举一个假设:以单核CPU为例,我们设计的某个restful api 接口所做的工作比较复杂,一个请求需要耗时3s才能完成,那么此时采用固定模式或是抢占模式让CPU来调度处理的话,会发生什么情况呢?(采用php-fpm)

固定模式
CPU的时间片会一直在这个进程上,会等待这个请求处理完成后才去处理下一个请求耗时3s,如果单位时间内,并发起来了,那么只有第一个请求会受到cpu的处理,其它的请求只能等待前面的请求处理完成后才能轮到我处理。从设计逻辑层面看上去没什么问题,但是对并发的性能不好。

抢占模式
CPU会经过一定算法(队列优先级)为每个进程分配一定的运行时间片,当一个进程的运行时间片结束,不管这个进程是否执行完成,都会切换上下文至下一个进程处理,当下一个进程处理一定时间片后,又继续切换上下文至下一个。一直到所有进程的工作任务结束。(当进程产生阻塞时,也会切换CPU的上下文,如I/O(数据库连接,读写,http请求,RPC等),sleep)这种模式下一个CPU可以同时处理多个任务。并发性能一下子就起来了。

其实我个人挺喜欢用swoole的,运行速度快,提高机器性能的利用率,期待swoole可以给我们更好的程序开发体验。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值