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

完整的请求模式如上图所示,用户从DNS服务器上通过域名获得服务器IP地址(中间可能产生的过程有DNS轮询和SLB负载均衡)。
并请求指定端口(http:80 https:443)
此时就由master-nginx获取到这个请求
然后将此请求下发给子进程work -nginx,只要有一个work-nginx获得到了这个请求的锁,那么其他的work-nginx将不能处理此请求,只有获得到这个请求的锁的work-nginx才能处理这个请求,获得锁的work-nginx会解析这个请求
如果是访问静态资源(css、html、js、媒体文件)就直接返回相应的内容给用户,如果解析出此请求是一个php请求,就将此请求转发给master-php-fpm处理。(注意,当请求转发后,获得锁的work-nginx接着又可以去获得下一个请求的锁,而不用等待当前请求处理完成后才去处理下一个,这是因为nginx采用I/O复用模型
当php-fpm处理完请求,响应报文的时候会触发事件,这个事件会通知获得该请求锁的work-nginx来处理,基于这样的原理,nginx可支撑的迸发量是很高的)。
当master-php-fpm 接收到来自nginx的转发请求时,会将此请求下发给子进程work-php-fpm,具体的程序运行也是在子进程 work-php-fpm上。
如果一个接口的运行如果比较耗时,那么说明这个接口做的工作越多,越复杂,吞吐量不高。那么此时需要评估这个接口一般由谁调用,如果是由用户使用,那么就需要考虑是否可以拆分这个接口所做的工作。解决方案:
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,并参考文献及做了测试。我使用的是异步风格的swoole http服务器,并在前端采用nginx代理,所有的php请求均由nginx代理转发至swoole来处理相应的php请求
首先我们要了解swoole是常驻内存的。因为常驻所以就没有一个请求创建一个进程的这种开销所以快。且一旦进程创建后再次修改代码将不会生效,因为程序代码已经载入到了内存中,执行的直接跑就是,减少了编译次数。
swoole与php-fpm也有相似的地方,相似的地方如下:
1、当某个进程处理了一定请求数后都会重启,目的是为了放置内存泄漏
2、都是采用 master-work模式:即是由master-swoole监听某个端口,监听到请求来了以后,将此请求下发给work-swoole处理。
3、每个work-swoole进程同一时刻只能处理一个请求
swoole和php-fpm不同之处:swoole在启动时,一旦定义了一个变量,那么这个变量在整个进程的生存周期中都存在,使用时直接从内存沿用这个对象。因为这个特性,所以每次请求来的时候,都不用再去重新定义框架的一些内容,如框架的启动组件这些统统不用,在work-swoole启动的时候这些工作已经做好了。
task任务进程,异步程序的实现,将一个复杂的任务拆分成多个小任务,并交给task任务去跑,极大提高了cpu的利用率。
基于以上几点,所以swoole比php-fpm快。但是在使用的时候也有要注意的点。
为什么说抢占模式的性能比固定模式的效率要高
先举一个假设:以单核CPU为例,我们设计的某个restful api 接口所做的工作比较复杂,一个请求需要耗时3s才能完成,那么此时采用固定模式或是抢占模式让CPU来调度处理的话,会发生什么情况呢?(采用php-fpm)
固定模式
CPU的时间片会一直在这个进程上,会等待这个请求处理完成后才去处理下一个请求耗时3s,如果单位时间内,并发起来了,那么只有第一个请求会受到cpu的处理,其它的请求只能等待前面的请求处理完成后才能轮到我处理。从设计逻辑层面看上去没什么问题,但是对并发的性能不好。
抢占模式
CPU会经过一定算法(队列优先级)为每个进程分配一定的运行时间片,当一个进程的运行时间片结束,不管这个进程是否执行完成,都会切换上下文至下一个进程处理,当下一个进程处理一定时间片后,又继续切换上下文至下一个。一直到所有进程的工作任务结束。(当进程产生阻塞时,也会切换CPU的上下文,如I/O(数据库连接,读写,http请求,RPC等),sleep)这种模式下一个CPU可以同时处理多个任务。并发性能一下子就起来了。
其实我个人挺喜欢用swoole的,运行速度快,提高机器性能的利用率,期待swoole可以给我们更好的程序开发体验。