欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Swoft 源码剖析 - Swoole和Swoft的那些事 (Http/Rpc服务篇)

程序员文章站 2022-04-14 20:05:10
前言 Swoft在PHPer圈中是一个门槛较高的Web框架,不仅仅由于框架本身带来了很多新概念和前沿的设计,还在于Swoft是一个基于Swoole的框架。Swoole在PHPer圈内学习成本最高的工具没有之一,虽然Swoft的出现降低了Swoole的使用成本,但如果你对Swoole本身了解不够深入, ......

前言

swoftphper圈中是一个门槛较高的web框架,不仅仅由于框架本身带来了很多新概念和前沿的设计,还在于swoft是一个基于swoole的框架。swoolephper圈内学习成本最高的工具没有之一,虽然swoft的出现降低了swoole的使用成本,但如果你对swoole本身了解不够深入,仍然很难避免栽进种种"坑"中。

考虑到这个现状,也为降低阅读难度,后续几个和swoole联系较为密切的机制,笔者会调整写作思路,将文章的定位从 「帮助读者深入理解swoft」 调整为 「帮助读者理解swoft和swoole」,叙述节奏也会放慢。

三种php应用的web模型

Swoft 源码剖析 - Swoole和Swoft的那些事 (Http/Rpc服务篇)

 

lnmplamp是绝大多数phper最熟悉的基础web架构,这里以常见的lnmp作为例子描述一个常见 无swoole应用的构件组成:nginx充当web servicephp-fpm维护一个进程池去运行web项目。

对比更古老的cgi模型,php-fpm已经引入了进程常驻的概念,避免每次请求创建并销毁进程的开销以及拓展加载的开销,但是每个请求仍然要执行php rinit 与 rshutdown 之间的所有流程,包括重新加载一次框架源码以及项目代码,造成极大的性能浪费。

这种模型的优点是简单成熟和稳定,一次运行随后销毁 带来的开发便捷性是php能够流行起来的原因之一。市面上绝大多数php项目使用的都是基于该种架构的变体。

Swoft 源码剖析 - Swoole和Swoft的那些事 (Http/Rpc服务篇)

 

lnmp-with-swoole 是 lnmp的一种变体,其在lnmp的基础上引入了swoole组件。
php-fpm一样,swoole有一套自己的进程管理机制。但由于代码变得高度常驻和编程思维需要从同步到异步的转变,所以swoole和传统的基于php-fpmweb框架亲和度很低,即使是适配升级过的老式web框架,目前在swoole上运行的表现往往并不好。

因此出现了这在这种折中方案,并没有直接将原有php代码运行在swoole中,而是使用swoole搭建了一个服务,系统通过接口与swoole通信,从而为web项目补充了异步处理的能力。我称呼这种同时使用php-fpmswoole的系统为 半swoole应用。因为接入简单,所以是绝大多数现有项目优先考虑的swoole接入方案。

lnmp-with-swoole模型虽然引入了swoole和异步处理能力,但是核心还是php-fpm,实际上还远远没有发挥出swoole的真正优势。

Swoft 源码剖析 - Swoole和Swoft的那些事 (Http/Rpc服务篇)

 

 

swoole-http-serverlnmp-with-swoole相比有巨大的变化,这种模型中充当web server角色的构件不仅仅有nginx,应用本身也包含了一个内建web server,不过由于swoole http server不是专业的http server,对http的处理不完善 ,因此仍然需要使用nginx作为静态资源服务器以及反代,swoole http server仅仅处理php相关的http流量。

一方面由于swoole已经包含了webserver,不再需要实现cgi或者fast-cgi的通用协议去和web server通信,另一方面swoole有自己的进程管理,因此php-fpm可以直接被去除了。对于php资源而言,在这种模型中,swoole http server的地位相当于传统模型中的nginxphp-fpm之和。

一次加载常驻内存,不同的请求间基本上复用了onrequest以外的所有流程,使得每个请求的开销大大降低;异步io的特性使得这种模型吞吐量远远高于传统的lnmp模型。另外相对于独立的swoole服务,内嵌在web系统中的swoole使用更加的直接方便,支持更好。

swoft 和 swoole 的关系是什么 ?

  1. swoole是一个异步引擎,核心是为php提供异步io执行的能力,同时提供一套异步编程可能会用到的工具集。
  2. swoole-http-serverswoole的一个组件,是swooleserver中的一种,提供了一个适合swoole直接运行的httpserver环境。
  3. swoft一个现代的web框架,和swoole亲和性高,同时也是上面提到的swoole-http-server模型的一个实践。

swoft管理着该web模型中的swoole,以及swoole-http-server,对开发者屏蔽swoole的种种复杂操作细节,并作为一个web框架向开发者提供各种web开发需要用到的路由mvc数据库访问等功能组件等。

swoft 是如何使用 swoole 的 ?

最核心的就是httpserver以及rpcserver

http 服务器

swoft直接使用的是swoole内建的\swoole\http\server,它已经处理好所有http层面的所有东西,我们只需要关注应用本身,我们来看一下http服务几个重要生命周期点。

swoole 启动前

这个阶段进行的行为有几个特征

  1. 基础bootstrap行为:如必须的常量定义,composer加载器引入,配置读取等;
  2. 需要生成被所有worker/task进程共享的程序全局期的对象,如swoole\lock,swoft\memory\table的创建;
  3. 启动时,所有进程中合计只能执行一次的操作:如前置process的启动;
  4. bean容器基本初始化,以及项目启动流程需要的corebean的加载。

这块涉及东西比较杂,为控制篇幅后续用单独文章介绍。

http服务关系最密切的进程是swoole中的worker进程(组),绝大部分业务处理都在该进程中进行。
对于每个swoole事件swoft都提供了对应的swoole监听器(对应@swoolelistener注解)作为事件机制的封装。要理解swofthttpserver是如何在swoole下运行的我们重点需要关注下两个在两个swoole事件swoole.workerstartswoole.onrequest

swoole.workerstart 事件

workerstart事件在taskworker/worker进程启动时发生,每个taskworker/worker进程里都会执行一次。
这是个关键节点,因为swoole.workerstart回调之后新建的对象都是进程全局期的,使用的内存都属于特定的task/worker进程,相互独立。也只有在这个阶段或以后初始化的部分才是可以被热重载的。
事件底层关键代码如下:

// swoft\bootstrap\server\servertrait.php
/**
 * @param bool $isworker
 * @throws \invalidargumentexception
 * @throws \reflectionexception
 */
protected function reloadbean(bool $isworker)
{
    beanfactory::reload();
    $initapplicationcontext = new initapplicationcontext();
    $initapplicationcontext->init();

    if($isworker && $this->workerlock->trylock() && env('auto_register', false)){
        app::trigger(appevent::worker_start);
    }
}

这里做的事情有3点

  1. 初始化bean容器:
    上文中的beanfactory::reload();就是swoftbean容器初始化入口,注解的扫描也是在此处进行(实际上这个说法并不准确,bean容器真正的初始化阶段在swoole server启动前的bootstrap阶段就已经进行了,只不过那时进行的是少部分初始化,相对swoole.workerstart中的初始化的bean数量,比重很小)。在workerstart中初始化bean容器是swoft可以热更新代码的基础。
  2. 初始化的应用上下文
    initapplicationcontext->init()会注册swoft事件监听器(对应@listener),方便用户处理swoft应用本身的各种钩子。随后触发一个swoft.applicationloader事件,各组件通过该事件进行配置文件加载,http/rpc路由注册。
  3. 服务注册
    具体内容会在服务治理章节讲述。

swoole.onrequest 事件

每个http请求到来时仅仅会触发swoole.onrequest事件。
框架代码本身都是由大量进程全局期和少量程序全局期的对象构成,而onreceive中创建的对象譬如$request$response都是请求期的,随着http请求的结束而回收。

事件底层关键代码如下:

/**
 * @param array ...$params
 * @return \psr\http\message\responseinterface
 * @throws \invalidargumentexception
 */
public function dispatch(...$params): responseinterface
{
    /**
     * @var requestinterface $request
     * @var responseinterface $response
     */
    list($request, $response) = $params;

    try {
        // before dispatcher
        $this->beforedispatch($request, $response);

        // request middlewares
        $middlewares = $this->requestmiddleware();
        $request = requestcontext::getrequest();
        $requesthandler = new requesthandler($middlewares, $this->handleradapter);
        $response = $requesthandler->handle($request);

    } catch (\throwable $throwable) {
        /* @var errorhandler $errorhandler */
        $errorhandler = app::getbean(errorhandler::class);
        $response = $errorhandler->handle($throwable);
    }

    $this->afterdispatch($response);

    return $response;
}

  1. beforedispatch($request, $response):
    设置请求上下文,并触发一个swoft.beforerequest事件。
  2. requesthandler->handle($request):
    执行各个 中间件 和请求对应的 action。
  3. $afterdispatch($response):
    整理http响应报文发送客户端并触发swoft.resourcerelease事件和swoft.afterrequest事件

总的来说,纵观这几个生命周期点你需要搞清楚几件事:

    1. swooleworker进程是你绝大多数http服务代码的运行环境。
    2. 一部分初始化和加载操作在swooleserver启动前完成,一部分在swoole.workerstart事件回调中完成,前者无法热重载但可能被多个进程共享。
    3. 初始化代码只会在系统启动和worker/task进程启动时执行一次, 不像php-fpm每次请求都会执行一次,框架对象也不像php-fpm会随请求返回而销毁。
    4. 每次请求都会触发一次swoole.onrequest事件,里面就是我们的请求处理代码真正运行的地方,只有这事件内产生的对象才会在请求结束时被回收。

      rpc服务器

      生命周期和http服务基本一致

      以上是文章全部内容,有需要学习交流的友人请加入swoole交流群的咱们一起,有问题一起交流,一起进步!前提是你是学技术的。感谢阅读!

      点此加入该群