1。前言三年的PHP开发工作经历,诚然至今一直沉浸在PHP作为WEB服务器开发的乐趣中。由于最近在工作上遇到了瓶颈,想尽快走出这个舒适区。本文适合想要摆脱只有CURD的php程序员,但文中所讨论的“高并发”、“异步”、“协程”等概念,或者计算机抽象设计的艺术,不受限于programminglanguages是的,其他编程语言的程序员想了解这些概念也是合适的。文章分析的是PHP语言,demo都是用PHP来描述的。高并发异步协程实例将使用SWOOLE进行描述,使用SWOOLE进行简单分析,但绝不是SWOOLE的广告,只是给大家一个具体的实例体验。相信当你足够强大的时候,你也可以自己开发出这样的工具。这也是我这2年的目标,当然要学习很多知识。php的优点:1.简单,PHP比其他语言更容易上手和掌握。内置了PHP常用的数据结构,使用起来方便简单,一点也不复杂。2.功能非常强大。PHP官方的标准库和扩展库提供了99%的可以用于服务器端编程的东西。3、在Web编程领域,LNMP框架下服务的工业级稳定性和可靠性。4.可以快速开发。但是纵观PHP在编程语言领域的排名,却是逐年下滑。当然,它在传统Web开发领域依然占据着绝对的统治地位。而在移动互联网、云计算、大数据、人工智能等其他大领域,则没有生态。除了web生态,几乎没有其他生态。但是即使在web领域,高并发、高流量要求的web项目也逐渐被java等语言改写(可能用swoole会更好)。PHP程序员无法做复杂大型的服务器,陷入无休止的CURD业务开发。2、PHP的劣势分析PHP的劣势,什么问题导致PHP除了web领域没有生态。1、单进程单线程模型,后期代码层面提速空间有限。`产量?pthreads?`2.核心异步网络不支持。`libevent?`3.cli方式编程功能不够强大。对于问题1和2,PHP没有其他语言支持的线程或者协程调度模型。占用整个进程资源,一旦执行了一段I/O相关的阻塞代码,整个进程就会进入休眠状态,将CPU控制权让给其他进程。等到I/O返回,再等待分配cpu资源,执行后续代码。有一个误会,我想解释一下。事实上,同步阻塞程序的性能并不差。它非常高效,不会浪费资源。当一个进程被阻塞时,操作系统会将其挂起,不再分配CPU。并不是说进程阻塞时占用的cpu时间片就会出现cpuidling现象。只是QPS低。在高并发请求下,后续的接口响应会越来越慢。操作系统可以创建的进程数是有限的,大量的进程会带来额外的进程调度消耗。那么横向扩展进程数来解决并发问题呢?并发度取决于worker进程的数量,进程之间的切换成本很大。而且创建一个进程会占用大量的操作系统资源。这种进程资源的浪费可以举个例子。以印刷厂为例,我租了一间1000平米的。在经历了各种复杂的政府审批程序后,我也为这家印刷厂申请了各种资质认证。印刷厂只有一台普通能力的印刷机来处理订单。如果要提高产能,就得再开一家工厂,但每个工厂只有一台印刷机在工作。所以问题的本质是:一个进程只能并发处理一个请求,这实际上是对cpu和进程资源的浪费。这时候肯定有人会说php有协程,可以用yield来启动协程,但是php的yield是stackless协程,也就是没有新的协程栈,本质上和single是一样的-进程和单线程模型。Yield更像是语法糖,实现进程的多任务协同。有人肯定会说PHP也有pthreads扩展,提供单进程多线程模型。但是只能在CLI命令行环境下使用,线程间通信机制和锁机制并不完善,所以不能贸然应用到生产环境中(一旦线上出现奇怪问题无法及时解决)及时有效处理,否则会发生释放事故,严重影响性能)。对于异步,有人说可以通过PHP使用libevent扩展驱动,但是libevent已经7年没有更新了,支持的php最高版本是6.0。对于问题3,大家都知道PHP有fpm模式和cli模式。fpm比较简单,也是现阶段php开发的主流。在cli模式下,大多数phper用于脚本编写。HTTP/HTTPS协议的解析和实现,不需要PHP-FPM下的PHP程序关心单个请求中的脚本运行周期,也不用担心内存泄露,PHP-FPM自带一套进程的管理机制保证服务中一直有工作进程,服务基本不间断,开发者不需要考虑太多业务逻辑以外的问题。顺便说一句,大多数PHPer在php-fpm下生活得太舒服了。随着年龄的增长,日复一日地生活在CURD,你有没有担心过自己的核心竞争力在哪里?而如果基于cli写一个稍微复杂的server,需要关心的东西很多。1、自己实现一套进程管理机制,保证服务进程因为代码错误自动重启一个新进程。比如php语法错误会导致cli进程直接退出,而不会导致fpm进程退出。2、还需要自己实现多进程架构,才能发挥多核CPU的优势。3.为了超越fpm的阻塞架构,你不得不在你的cli服务中加入事件驱动的支持。PHP需要使用event(libevent)等事件通知库来反映单个进程维护C10K连接的事实。不具备的能力。4、自己实现网络协议的解析,对读取到的原始数据进行解析。在fpm中,我们可以使用fpm分析完成的全局变量$_SERVER,$_POST,$_GET,$_COOKIE,$_SERVER来获取这些数据和cli目前还比较粗糙,提供的很多api还是比较接近原来的底层接口,使用容易,容易出错,要求开发者有扎实的基础知识和linux编程能力,否则每一步都难。3.对PHP能力的渴望3.1进程管理我们需要一个完整的进程管理机制什么是进程?什么是流程管理?流程管理需要什么?维基百科:进程是资源分配的单位。每个进程在操作系统中都有一个“进程控制块PCB*”来描述一个进程,在linux中使用task_struct结构来描述一个进程/线程。“PCB”是描述控制过程的运行。系统中存储进程管理和控制信息的数据结构称为进程控制块(PCBProcessControlBlock),它是进程实体的一部分,是操作系统中最重要的记录。性数据结构。听起来有点懵,只要记住这是一个非常复杂的数据结构。说到进程管理,首先要知道进程是如何组织的:父子关系。每个进程都有一个父进程,所有进程都以init(内核号0)进程为根,形成树状结构。有保证的父子进程管理:当父进程退出时,必须通知所有子进程退出,避免孤儿进程;子进程退出。需要通知父进程回收资源,避免产生僵尸进程。进程管理需要进程之间最基本的通信能力。进程间通信的方式有:消息队列、信号量、共享存储、socket、管道。pcntl是原生php提供的多进程编程的扩展。可以以“pcntl”为关键字搜索fpm框架(如laravel、symfony)下的vender包。有用的话可以看看怎么用。pnctl存在的问题:1.没有提供进程间通信的功能,需要开发者自己实现。2、不支持标准输入输出的重定向。3、pcntl只提供fork等原有接口,编程难度大,容易误用。pcntl实现了两个进程的通信。此示例使用消息队列通信。这段代码的问题是难以阅读。两个进程的执行代码混合在一起,没有层级区分。在这个例子中,你要花点功夫才能看到producer()方法是被子进程调度的。start();//主进程监听socketCo\run(function()use($process){$socket=$process->exportSocket();echo"fromexec:".$socket->recv()."\n";});//子进程回收Process::wait();SWOOLE代码更加简洁,可读性更强,减少了手动维护消息队列的成本。3.2协程刚才说到了问题的本质。那么我们期望能够解决的衍生问题是:单个进程如何并发处理多个连接?协程使一个进程可以维护多个客户端连接。协程可以简单理解为线程,但是这个线程是用户态的,不需要操作系统的参与,创建和销毁的成本都很低。所谓用户态不需要操作系统控制,指的是协程的创建和销毁。协程之间的调度和切换由进程自己控制。线程是CPU调度的基本单位,线程中托管的协程本质上是一种内存数据结构。其实和进程一样,只是比进程轻很多,占用内存空间少。协程的上下文小,即占用空间小。一个进程很容易创建上千个协程,协程之前的切换成本也很小。可以类比:进程切换好比部门间的沟通,线程切换好比部门内部的沟通,协程切换就是团队内部的沟通。Stackless表示没有额外的栈分配,stackfull表示有栈空间分配。对于像SWOOLE这样的单进程单线程模型,由于一个线程在同一时刻只能被一个CPU调度,从执行时序来看,CPU在某一时刻只能执行一个协程(不是并行),但是有stack和没有stack的协程在挂起时的自由度是不同的。有栈的协程比没有栈的协程要高很多,没有栈的协程不能在任何嵌套函数中挂起,有栈的协程只能挂在yield入口(涉及到计算机底层细节,会在后续博文中详细分析)。并且在实际项目开发中,使用stackless协程,需要深入理解yield的语义,修改每一层yield的调用,这会极大的影响你的开发效率和代码的可读性。与同步阻塞模式不同的是,我们期望程序是并发执行的,即同时会有多个服务器请求,因此应用程序必须为每个客户端或请求创建不同的资源和上下文。否则,不同的客户端和请求之间可能会出现数据和逻辑的混乱。stackfull协程真正实现了一个进程可以处理多个连接,为每个请求分配一个寄存器和栈来保存请求状态。用一张图来说明,这张图想结合具体的业务和协程来做一个具体的表述。登录界面:php首先检查参数,然后从数据库中查询密码,php代码判断密码是否正确,然后从数据库中查询用户权限,php判断用户权限是否匹配,最后查询详细的用户信息。横轴代表单个进程的请求量,纵轴代表请求处理时间。图中的每一行代表一个要求做的事情。红色的代表io操作,绿色的代表非io操作。3.3异步刚才说了问题的本质。那么我们期望能够解决的衍生问题是:单个进程如何并发处理多个连接?而异步为并发提供了可能。编程语言层面的异步是指让CPU暂时搁置对当前请求的响应,处理下一个请求,通过轮询或其他方式得到回调通知后开始运行。异步实际上是一种协调机制。异步有两个主体,对于swoole来说:就是swoole进程+内核进程。通过epoll(linux系统调用)异步取回通知,并发处理请求。以这个登录界面为例,简单说明协程(在单进程、单线程、多协程模型下)和异步是如何协同工作的。其实swoole一直在消费epoll的就绪队列。当进程启动时,发现就绪队列上有2个请求进来了。Swoole启动了2个协程分别处理这2个请求。执行完第一个协程的定时校验输入参数后,协程有一个I/O事件去数据库查询密码,于是进程将cpu的控制权交给另一个协程来处理校验输入参数,并且还会发生I/O事件。此时CPU分配给进程的时间片被消耗掉,进程被迫放弃对CPU的控制。当CPU控制权交给进程时,发现就绪队列上有3个事件,其中2个是现有协程产生的I/O事件结果返回,1个是生成一个新的请求连接。进程会依次调度已有的两个协程判断进程密码是否正确,并开启一个新的协程处理新连接的定时校验输入参数。循环继续,直到执行协程并回收资源。**从宏观上看,请求都在往下走,从微观上看,进程调度协程是横向执行的。**协程+异步的组合,可以轻松处理上万个并发请求。对比协程+异步后的速度提升,同样的业务逻辑:一个任务执行3000次,每次休眠3000毫秒。PHP原生实现
