Swoole application in Swoft

  php, swoole

date: 2017-12-14 21:34:51
Title: application of swole in swoft

Swoft website:https://www.swoft.org/

Swoft source code interpretation:http://naotu.baidu.com/file/8 …

Extra, extra, extra,Welcome, star, our development team has set a small goal of “star 1000+ getting together offline”

Last articleBlog-swoft source code interpretationThe response is not bad. many students recommend adding another article to explain the swole function used in swoft to help everyone open swole.Actual combat tour.

The server development involves a lot of knowledge in related technical fields, and it is difficult to do it well without laying a solid foundation over time. Therefore, I suggest:

swoole wikiIt’s best to watch it three times, including comments. The first time, go through it quickly to form a general impression. The second time, while looking at the code; The third time can choose the derived open source framework for actual combat.swoftIt is a good choice.

Swoole wiki has reached 1,400+pages, which is really a bit difficult to read.Brave boyCome on.

Swoole application in swoft;

  • Swoole\Server: swoole2.0 coordinator Server
  • Swoole\HttpServer: swoole2.0 coordinated http Server, inherited fromSwoole\Server
  • Swoole\Coroutine\Client: coordination client, swoole encapsulates tcp/http/redis/mysql
  • Swoole\Coroutine: coordination tool set, acquiring current coordination id, reflection calling, etc
  • Swoole\Process: Process Management Module, available atSwoole\ServerExpand More Functions Beyond
  • Swoole\Async: asynchronous file IO
  • Swoole\Timer: based ontimerfd + epollThe asynchronous millisecond timer implemented can run perfectly in the EventLoop.
  • Swoole\Event: Directly Operate Bottom Layerepoll/kqueueInterface for EventLoop
  • Swoole\Lock: You can easily create a lock in PHP code to synchronize data
  • Swoole\Table: Ultra-high Performance Data Structure Based on Shared Memory

SwooleHttpServer

Http server using swoole is still simpler than tcp server, only need to care about:

  • Swoole\Http\Server
  • Swoole\Http\Request
  • Swoole\Http\Response

First look at http server:

// \Swoft\Server\HttpServer
public function start()
{
    // http server
    $this->server = new \Swoole\Http\Server($this->httpSetting['host'], $this->httpSetting['port'], $this->httpSetting['model'], $this->httpSetting['type']);

    // 设置事件监听
    $this->server->set($this->setting);
    $this->server->on('start', [$this, 'onStart']);
    $this->server->on('workerStart', [$this, 'onWorkerStart']);
    $this->server->on('managerStart', [$this, 'onManagerStart']);
    $this->server->on('request', [$this, 'onRequest']);
    $this->server->on('task', [$this, 'onTask']);
    $this->server->on('pipeMessage', [$this, 'onPipeMessage']);
    $this->server->on('finish', [$this, 'onFinish']);

    // 启动RPC服务
    if ((int)$this->serverSetting['tcpable'] === 1) {
        $this->listen = $this->server->listen($this->tcpSetting['host'], $this->tcpSetting['port'], $this->tcpSetting['type']);
        $tcpSetting = $this->getListenTcpSetting();
        $this->listen->set($tcpSetting);
        $this->listen->on('connect', [$this, 'onConnect']);
        $this->listen->on('receive', [$this, 'onReceive']);
        $this->listen->on('close', [$this, 'onClose']);
    }

    $this->beforeStart();
    $this->server->start();
}

Swoole server is very simple to use:

  • To set up event monitoring, this step requires everyone to be very familiar with the process model of swoole, and must understand the following 2 pictures.
  • Start server

进程流程图

进程/线程结构图

When swoft uses http server, it will also determine whether to create a new RPC server and use swoole’s according to the configuration information.Duo port monitorTo achieve.

Let’s look at Request and Response again. Remind us to remember when designing the framework.Norm first:

PSR-7: HTTP message interfaces

SwooleHttpRequest

Phper should be familiar with$_GET $_POST $_COOKIE $_FILES $_SERVERThese global variables, which are supported in swoole, provide more convenient functions:

// \Swoole\Http\Request $request
$request->get(); // -> $_GET
$request->post(); // -> $_POST
$request->cookie(); // -> $_COOKIE
$request->files(); // -> $_FILES
$request->server(); // -> $_SERVER

// 更方便的方法
$request->header(); // 原生 php 需要从 $_SERVER 中取
$request->rawContent(); // 获取原始的POST包体

Let me emphasize here.$request->rawContent()Phper may use$_POSTCompared with 6, some knowledge is not known: the format of post data. Because of this knowledge, therefore$_POSTNot all data can be obtained at all times. You can look up the data on the Internet or use tools such as postman to test and verify it yourself. In$_POSTIf no data is available, it will be handled as follows:

$post = file_get_content('php://input');

$request->rawContent()Equivalent to this.

Swoft encapsulates the Request object in the same way as the mainstream framework, taking laravel as an example (actually using symfony’s method):

// SymfonyRequest::createFromGlobals()
public static function createFromGlobals()
{
    // With the php's bug #66606, the php's built-in web server
    // stores the Content-Type and Content-Length header values in
    // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.
    $server = $_SERVER;
    if ('cli-server' === PHP_SAPI) {
        if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {
            $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH'];
        }
        if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
            $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE'];
        }
    }

    $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server); // xglobal,

    if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
        && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
    ) {
        parse_str($request->getContent(), $data);
        $request->request = new ParameterBag($data);
    }

    return $request;
}

SwooleHttpResponse

Swoole\Http\ResponseIt also supports common functions:

// Swoole\Http\Response $response
$response->header($key, $value); // -> header("$key: $valu", $httpCode)
$response->cookie(); // -> setcookie()
$response->status(); // http 状态码

Of course, swoole also provides common functions:

$response->sendfile(); // 给客户端发送文件
$response->gzip(); // nginx + fpm 的场景, nginx 处理掉了这个
$response->end(); // 返回数据给客户端
$response->write(); // 分段传输数据, 最后调用 end() 表示数据传输结束

Phper, watch out herewrite()Andend(), here is a knowledge point of http chunk. when a large amount of data needs to be returned to the client (> =2M), chunk is needed to send it. so use it firstwrite()Send data, and finally useend()When the amount of data is small, call directlyend($html)Just return.

As for the concrete implementation of the framework, just like above, laravel still uses it.SymfonyResponse, swoft is also the interface to implement PSR-7 definition, rightSwoole\Http\ResponseEncapsulate.

SwooleServer

Swoft usageSwoole\ServerTo implement RPC service, in fact, in the above multi-port monitor, it is also to open RPC service. Note the difference between callback functions in separate activation:

// \Swoft\Server\RpcServer
public function start()
{
    // rpc server
    $this->server = new Server($this->tcpSetting['host'], $this->tcpSetting['port'], $this->tcpSetting['model'], $this->tcpSetting['type']);

    // 设置回调函数
    $listenSetting = $this->getListenTcpSetting();
    $setting = array_merge($this->setting, $listenSetting);
    $this->server->set($setting);
    $this->server->on('start', [$this, 'onStart']);
    $this->server->on('workerStart', [$this, 'onWorkerStart']);
    $this->server->on('managerStart', [$this, 'onManagerStart']);
    $this->server->on('task', [$this, 'onTask']);
    $this->server->on('finish', [$this, 'onFinish']);
    $this->server->on('connect', [$this, 'onConnect']);
    $this->server->on('receive', [$this, 'onReceive']);
    $this->server->on('pipeMessage', [$this, 'onPipeMessage']); // 接收管道信息时触发的回调函数
    $this->server->on('close', [$this, 'onClose']);

    // before start
    $this->beforeStart();
    $this->server->start();
}

SwooleCoroutineClient

Swoft, swoole’s own coordinated client, is encapsulated in connection pool to improve performance. at the same time, for the convenience of service usage, there are both coordinated connection and synchronous connection to facilitate seamless switching during service usage.

Implementation code of synchronous/synergetic connection:

// RedisConnect -> 使用 swoole 协程客户端
public function createConnect()
{
    // 连接信息
    $timeout = $this->connectPool->getTimeout();
    $address = $this->connectPool->getConnectAddress();
    list($host, $port) = explode(":", $address);

    // 创建连接
    $redis = new \Swoole\Coroutine\Redis();
    $result = $redis->connect($host, $port, $timeout);
    if ($result == false) {
        App::error("redis连接失败,host=" . $host . " port=" . $port . " timeout=" . $timeout);
        return;
    }

    $this->connect = $redis;
}

// SyncRedisConnect -> 使用 \Redis 同步客户端
public function createConnect()
{
    // 连接信息
    $timeout = $this->connectPool->getTimeout();
    $address = $this->connectPool->getConnectAddress();
    list($host, $port) = explode(":", $address);

    // 初始化连接
    $redis = new \Redis();
    $redis->connect($host, $port, $timeout);
    $this->connect = $redis;
}

The code to implement connection pool in swoft is insrc/PoolThe following implementation consists of three parts:

  • Connect: The connection in the above code
  • Balancer: load balancer, which has realized random/polling two ways at present
  • Pool: connection pool, call Balancer, return Connect

For details, please refer to the previousBlog-swoft source code interpretation

SwooleCoroutine

As the first framework to use Swoole2.0 native synergetics, swoft hopes to extend the synergetic capability to the core design of the framework. UseSwoft\Base\CoroutineEncapsulate for easy use throughout the application:

public static function id()
{
    $cid = SwCoroutine::getuid(); // swoole 协程
    $context = ApplicationContext::getContext();

    if ($context == ApplicationContext::WORKER || $cid !== -1) {
        return $cid;
    }
    if ($context == ApplicationContext::TASK) {
        return Task::getId();
    }
    if($context == ApplicationContext::CONSOLE){
        return Console::id();
    }

    return Process::getId();
}

As this code shows, Swoft hopes to extend the capability of easy-to-use coordination to different application scenarios such as Console/Worker/Task/Process, etc.

protosomaticcall_user_func() / call_user_func_array()The coroutine client cannot be used in, so swoole has also packaged the corresponding implementation in coroutine components, and swoft has also used it. please read the source code yourself.

SwooleProcess

The process management module is suitable for processing resident process tasks that are relatively independent of the Server. In swoft, it is used in the following scenarios:

  • Synergetic timer CronTimerProcess
  • Coordinate execution command CronExecProcess
  • Hot update process ReloadProcess

Swoft usage\Swoft\ProcessYesSwoole\ProcessEncapsulated:

// \Swoft\Process
public static function create(
    AbstractServer $server,
    string $processName,
    string $processClassName
) {
    ...

    // 创建进程
    $process = new SwooleProcess(function (SwooleProcess $process) use ($processClass, $processName) {
        // reload
        BeanFactory::reload();
        $initApplicationContext = new InitApplicationContext();
        $initApplicationContext->init();

        App::trigger(AppEvent::BEFORE_PROCESS, null, $processName, $process, null);
        PhpHelper::call([$processClass, 'run'], [$process]);
        App::trigger(AppEvent::AFTER_PROCESS);
    }, $iout, $pipe); // 启动 \Swoole\Process 并绑定回调函数即可

    return $process;
}

SwooleAsync

Swoft is used in log scenarios.Swoole\AsyncTo improve performance while maintaining the original synchronization mode for convenient switching

// \Swoft\Log\FileHandler
private function aysncWrite(string $logFile, string $messageText)
{
    while (true) {
        $result = \Swoole\Async::writeFile($logFile, $messageText, null, FILE_APPEND); // 使用起来很简单
        if ($result == true) {
            break;
        }
    }
}

SwooleEvent

For performance reasons, servers are usuallymemory residentTraditionalphp-fpmAlso, the modified configuration requires a reload server to take effect. Also because of this, new requirements have emerged in the server field-Hot update. swoole has made many optimizations in process management. Here are excerpts from some wiki contents:

Swoole提供了柔性终止/重启的机制
SIGTERM: 向主进程/管理进程发送此信号服务器将安全终止
SIGUSR1: 向主进程/管理进程发送SIGUSR1信号,将平稳地restart所有worker进程

At present, the common scheme adopted by everyone is based on Linux Inotify feature, which triggers swoole server reload. PHP to have Inotify extension by monitoring file changes. It is convenient to use and is specifically implemented inSwoft\Base\InotifyMedium:

public function run()
{
    $inotify = inotify_init();

    // 设置为非阻塞
    stream_set_blocking($inotify, 0);

    $tempFiles = [];
    $iterator = new \RecursiveDirectoryIterator($this->watchDir);
    $files = new \RecursiveIteratorIterator($iterator);
    foreach ($files as $file) {
        $path = dirname($file);

        // 只监听目录
        if (!isset($tempFiles[$path])) {
            $wd = inotify_add_watch($inotify, $path, IN_MODIFY | IN_CREATE | IN_IGNORED | IN_DELETE);
            $tempFiles[$path] = $wd;
            $this->watchFiles[$wd] = $path;
        }
    }

    // swoole Event add
    $this->addSwooleEvent($inotify);
}

private function addSwooleEvent($inotify)
{
    // swoole Event add
    Event::add($inotify, function ($inotify) { // 使用 \Swoole\Event
        // 读取有事件变化的文件
        $events = inotify_read($inotify);
        if ($events) {
            $this->reloadFiles($inotify, $events);
        }
    }, null, SWOOLE_EVENT_READ);
}

SwooleLock

Swoft is used in HalfOpenState of CircuitBreaker (fuse), and the implementation of this block is relatively complicated. It is recommended to read the source code:

// CircuitBreaker
public function init()
{
    // 状态初始化
    $this->circuitState = new CloseState($this);
    $this->halfOpenLock = new \Swoole\Lock(SWOOLE_MUTEX); // 初始化互斥锁
}

// HalfOpenState
public function doCall($callback, $params = [], $fallback = null)
{
    // 加锁
    $lock = $this->circuitBreaker->getHalfOpenLock();
    $lock->lock();
    list($class ,$method) = $callback;

    ....

    // 释放锁
    $lock->unlock();

    ...
}

The difficulty in using locks is mainly to understand the use scenarios of different locks. Currently, swoole supports:

  • Document lock SWOOLE_FILELOCK
  • Read-write lock SWOOLE_RWLOCK
  • Semaphore SWOOLE_SEM
  • Mutex lock SWOOLE_MUTEX
  • Spin lock SWOOLE_SPINLOCK

SwooleTimer & SwooleTable

Timers are basically used, and most phper uses crontab. Based on this consideration, swoft encapsulates the Timer for phper’s convenience.Familiar postureContinue to use.

Swoft pairSwoole\TimerIt is simply packaged and the code is in\Base\TimerMedium:

// 设置定时器
public function addTickTimer(string $name, int $time, $callback, $params = [])
{
    array_unshift($params, $name, $callback);

    $tid = \Swoole\Timer::tick($time, [$this, 'timerCallback'], $params);

    $this->timers[$name][$tid] = $tid;

    return $tid;
}

// 清除定时器
public function clearTimerByName(string $name)
{
    if (!isset($this->timers[$name])) {
        return true;
    }
    foreach ($this->timers[$name] as $tid => $tidVal) {
        \Swoole\Timer::clear($tid);
    }
    unset($this->timers[$name]);

    return true;
}

Swoole\TableIs to open up a region in the memory, to achieve a similar relational database Table (table) such data structure, aboutSwoole\TableRango wrote a special article on the implementation principle ofAnalysis of the implementation principle of swoole_tableRecommended reading.

Swoole\TableThe following points should be paid attention to in use:

  • Similar to relational databases, they need to be defined in advance.list structure
  • The size of the data (number of rows) needs to be determined in advance.
  • Note the memory, swoole will be more according to the above 2 definitions, in the call\Swoole\Table->create()To allocate this memory

Swoft uses this function to implement crontab task scheduling:

private $originTable;
private $runTimeTable;

private $originStruct = [
    'rule'       => [\Swoole\Table::TYPE_STRING, 100],
    'taskClass'  => [\Swoole\Table::TYPE_STRING, 255],
    'taskMethod' => [\Swoole\Table::TYPE_STRING, 255],
    'add_time'   => [\Swoole\Table::TYPE_STRING, 11],
];

private $runTimeStruct = [
    'taskClass'  => [\Swoole\Table::TYPE_STRING, 255],
    'taskMethod' => [\Swoole\Table::TYPE_STRING, 255],
    'minte'      => [\Swoole\Table::TYPE_STRING, 20],
    'sec'        => [\Swoole\Table::TYPE_STRING, 20],
    'runStatus'  => [\Swoole\TABLE::TYPE_INT, 4],
];

// 使用 \Swoole\Table
private function createOriginTable(): bool
{
    $this->setOriginTable(new \Swoole\Table('origin', self::TABLE_SIZE, $this->originStruct));

    return $this->getOriginTable()->create();
}

Write at the end

It’s a cliche that many people complain about the swoole pit and the documents are not good. To be honest,We should dare to face the reality that our server development ability is insufficient.. I often mention a word:

I want to read the wiki of swoole three times.

The original intention of writing this blog is to introduce the application scenario of SWOLE in swoft and help everyone try to land SWOLE. I hope this blog can be helpful to you, and also hope you can pay more attention to SWOLE community, pay more attention to swoft framework, and feel the fun brought by server development.