Swoft source code interpretation

  Back end, php, swoole

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

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”

The yii/laravel framework in PHP is very “heavy.”HeavyNot specific firstPerformanceAt the level, it is mainly the design idea of the framework and the service of framework integration, which enables the framework to solve many problems quickly and expand easily.

The framework in PHP, with yii/laravel, should be second to none.

This time, I read the source code of swoft-based on the framework of swoole2.0 native synergetic process. at the same time, swoft uses a large number of functions provided by SWOLE and is also very suitable for reading its code to learn how to build wheels. in fact, after reading the framework of yii/laravel, somecurrencyThe framework of the design idea will not repeat, mainly explain andServer developmentRelevant parts, ideas will also be developed according to the website’s feature list.

Common Functions of Focus Frame in Front Half:

  • Global Container Injection & MVC Hierarchical Design
  • Annotation mechanism (Highlights, it is highly recommended to understand)
  • High performance routing
  • Alias mechanism$aliases
  • RestFul style
  • Event mechanism
  • Powerful log system
  • Internationalization (i18n)
  • ORM database

The second half focuses on server-related functions:

  • Basic concepts (Highlight, the first framework based on swoole2.0 native synergetic process)
  • Connection pool
  • Service Governance Fusing, Degradation, Load, Registration and Discovery
  • Task Delivery & Crontab Scheduled Task
  • User defined process
  • Inotify auto Reload

For the design of PHP framework, please refer to [PSR (PHP Standards Recommendations
)](http://www.php-fig.org/psr/).

Global Container Injection & MVC Hierarchical Design

The reason why these two are put together is because one isInOne isTableOnly the newcomers heard moreMVCBased on the hierarchical design idea of, global container injection is relatively unknown.

  • MVC Hierarchical Design: More Business-Oriented

MVC is simple, universal and practicalSplit the business and implement it.The essence of the design isHierarchical designWhat is more important is to master it.Hierarchical designThis idea is widely used in engineering practice, such as OSI 7 layer network model and TCP/IP 4 layer network model. I can effectively determine the hierarchical designSystem boundary and division of responsibilities.

Want to cultivate the idea of hierarchical design, in fact, can fromRemoveStart with, in the process of dismantling the wheel and then assembling the wheel, you will be surprised to find that art is among them.

Mortise app:https://www.douban.com/note/3 …

  • Global container injection

Before entering this concept, we must first recognize another concept:Object-oriented programmingMore commonly used may beProcess-oriented programming vs object-oriented programmingHere, we will not go into a long talk, but only compare the ways of thinking:

  1. Process-Oriented Programming: Execution of one instruction after another, which is the computer’s preferred method.
  2. Object-Oriented Programming: Through ObjectsabstractDifferent things in it solve their related businesses through the connection between things.

From this perspective,Object orientedMay be more in line with the human way of thinking, or more intelligent way of thinking:

Those who are above work and those who are below work. abstract the management objects so as to accomplish the task better.

But in the process of using object-oriented programming, there will be a problem:new, need to manage the dependencies between objects, global container injection is to do such a thing.new, indicating that one object needs to depend on another, but using a container, it is an object that tells the container what object it needs.

I don’t care how it is realized-this is the use ofnewAnd the difference between container injection, namedinverse of control.

So, the container isInWhen dealing with specific business, the container will provide corresponding MVC objects as required.

Annotation system

In the implementation of the container, or the bottom layer of the frame, each frame actuallybe almost alikeLet’s talk about the difference between swoft-the introduction of annotation system.

Explain briefly the annotation system: by adding annotations & parsing annotations, the annotations can be converted into some specific meaningful code.

More simply:Comment = = Code

It’s actually very simple to implement, but it’s less likely to contact-Reflection:

// Bean\Parser\InjectParser
class InjectParser extends AbstractParser
{

    /**
     * Inject注解解析
     *
     * @param string $className
     * @param object $objectAnnotation
     * @param string $propertyName
     * @param string $methodName
     *
     * @return array
     */
    public function parser(string $className, $objectAnnotation = null, string $propertyName = "", string $methodName = "", $propertyValue = null)
    {
        $injectValue = $objectAnnotation->getName();
        if (!empty($injectValue)) {
            return [$injectValue, true];
        }

        // phpdoc解析
        $phpReader = new PhpDocReader(); // 将注释转化为类
        $property = new \ReflectionProperty($className, $propertyName); // 使用反射
        $propertyClass = $phpReader->getPropertyClass($property);

        $isRef = true;
        $injectProperty = $propertyClass;
        return [$injectProperty, $isRef];
    }
}

If you are familiar with java, you will find that there are many places that have been used before the method.@overrideThis method is also used in symfony. the advantages are a certain degree of cohesion, simpler use and less configuration.

High performance routing

First answer a question, what is the route? From the point of view of objects, routes actually correspond to each other.URLWhat is the URL?

URL, Uniform Resource Locator, uniform resource locator.

Therefore, this layer of abstraction of routing is to solve-find the logic that needs to be executed for URL correspondence.

Now let’s explain the high performance mentioned by swoft:

// app/routes.php: 路由配置文件
$router = \Swoft\App::getBean('httpRouter'); // 通过容器拿 httpRouter

// config/beans/base.php: beans 配置文件
'httpRouter'      => [
    'class'          => \Swoft\Router\Http\HandlerMapping::class, // httpRouter 其实对应这个
    'ignoreLastSep'  => false,
    'tmpCacheNumber' => 1000,
    'matchAll'       => '',
],

// \Swoft\Router\Http\HandlerMapping
private $cacheCounter = 0;
private $staticRoutes = []; // 静态路由
private $regularRoutes = []; // 动态路由
protected function cacheMatchedParamRoute($path, array $conf){} // 会缓存匹配到的路由
// 路由匹配的方法也很简单: 校验 -> 处理静态路由 -> 处理动态路由
public function map($methods, $route, $handler, array $opts = [])
{
    ...
    $methods = static::validateArguments($methods, $handler);
    ...
    if (self::isNoDynamicParam($route)) {
        ...
    }
    ...
    list($first, $conf) = static::parseParamRoute($route, $params, $conf);
}

High Performance = Simple Routing Matching Logic+Routing Cache

Alias mechanism$aliases

Those who have used yii are familiar with this one, but actually it is such a one.evolutionary process:

  • Use__DIR__/DIRECTORY_SEPARATORSuch as splicing absolute path
  • Usedefine() / defined()Define global variables to use paths
  • Use$aliasesVariables Replace Global Variables

Here only shows the place of configuration, and the implementation only opens a change in the class.$aliasesProperty to store it:

// config/define.php
// 基础根目录
!defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
// 注册别名
$aliases = [
    '@root'       => BASE_PATH,
    '@app'        => '@root/app',
    '@res'        => '@root/resources',
    '@runtime'    => '@root/runtime',
    '@configs'    => '@root/config',
    '@resources'  => '@root/resources',
    '@beans'      => '@configs/beans',
    '@properties' => '@configs/properties',
    '@commands'   => '@app/Commands'
];
App::setAliases($aliases);

RestFul style

The idea of restful is actually very simple:With resources as the core, the business is actually about adding, deleting, modifying and checking resources.. Specific to http:

  • Url is only used as resource identification and has two forms.itemAnditem/idA that represents the operation of a specific resource
  • Http method(get/post/put, etc.) is used to correspond to the CRUD of the resource.
  • The that uses json format for dataInput / output

The implementation is also simple: route+return

Event mechanism

First of all, let’s explain it with the thinking of the WHO WHY WHY HOW analysis method.Event mechanismMore importantly, what’s the use of this?

NormalThe implementation of the program, or people’s thinking trend, is in accordance with theTime linear serialYes, keepcontinuityBut in reality there will be variousInterrupt, the program is not alwaysready state, then there is a need for a mechanism to handle possible interruptions or switch between different states of the program.

The incident mechanism has developed to this day and is sometimes regarded as a kind of reservation, where needed according to your experience.Burial pointAfter convenienceon a garment.

Swoft’s event mechanism is based onPSR-14Implementation, highly cohesive and concise.

It consists of three parts:

  • EventManager: event manager
  • Event: event
  • EventHandler/Listener: Event Handler/Listener

Execution process:

  • Mr. cheng EventManager
  • Register Event EventHandler with EventManager
  • When Event is triggered, Event Manager will call the corresponding EventHandler.

It is easier to use:

use Swoft\Event\EventManager;

$em = new EventManager;

// 注册事件监听
$em->attach('someEvent', 'callback_handler'); // 这里也可以使用注解机制, 实现事件监听注册

// 触发事件
$em->trigger('someEvent', 'target', ['more params']);

// 也可以
$event = new Event('someEvent', ['more params']);
$em->trigger($event);

Let’s take a look at the highlights swoft uses to improve performance here in the event mechanism:

namespace Swoft\Event;

class ListenerQueue implements \IteratorAggregate, \Countable
{
    protected $store;

    /**
     * 优先级队列
     * @var \SplPriorityQueue
     */
    protected $queue;

    /**
     * 计数器
     * 设定最大值为 PHP_INT_MAX == 300
     * @var int
     */
    private $counter = PHP_INT_MAX;

    public function __construct()
    {
        $this->store = new \SplObjectStorage(); // Event 对象先添加都这里
        $this->queue = new \SplPriorityQueue(); // 然后加入优先级队列, 之后进行调度
    }
    ...
}

People who have played ACM a little bit are rightPriority queueIt will not be strange, basically all OJ have related question banks. However, PHPer does not have to worry too much about the underlying implementation, and can directly use SPL library.

SPL, Standard PHP Library, similar to C++ STL, PHPer must understand.

Powerful log system

Usemonolog/monologTo realize the log system has basically become standard, of course, the bottom layer is still implementedPSR-3Standard. However, this standard appeared earlier and has been hidden deeper since its development.

This is also the reason for the establishment of technical standards/agreements, which are well defined.Best practicesAfter that, all efforts have been made towards more and more ease of use.

Swoft’s log system consists of 2 parts:

  • Swoft\Log\Logger: log body function
  • Swoft\Log\FileHandler: output log

As for the other document,Swoft\Log\Log, just a layer of encapsulation of Logger, which is more convenient to call.

Of course, there are obvious similarities between the log system of swoft and the yii2 framework:

// 都在 App 中快读暴露日志功能
public static function info($message, array $context = array())
{
    self::getLogger()->info($message, $context); // 其实还是使用 Logger 来处理
}

// 都添加了 profile 功能
public static function profileStart(string $name)
{
    self::getLogger()->profileStart($name);
}
public static function profileEnd($name)
{
    self::getLogger()->profileEnd($name);
}

It is worth mentioning that the log system of the yii2 framework consists of three parts:

  • Logger: log body function
  • Dispatch: log distribution, the same log can be distributed to different Target processing
  • Target: Log Consumer

This kind of design, in fact, is toFileHandlerIt is more flexible and convenient to expand.

Take a look at the powerful side of the swoft logging system:

private function aysncWrite(string $logFile, string $messageText)
{
    while (true) {
        // 使用 swoole 异步文件 IO
        $result = \Swoole\Async::writeFile($logFile, $messageText, null, FILE_APPEND);
        if ($result == true) {
            break;
        }
    }
}

Of course, you can also choose the synchronization method:

private function syncWrite(string $logFile, string $messageText)
{
    $fp = fopen($logFile, 'a');
    if ($fp === false) {
        throw new \InvalidArgumentException("Unable to append to log file: {$this->logFile}");
    }
    flock($fp, LOCK_EX); // 注意要加锁
    fwrite($fp, $messageText);
    flock($fp, LOCK_UN);
    fclose($fp);
}

PS: The development team of log statistical analysis function is under development. Welcome to recommend the scheme ~

Internationalization (i18n)

The implementation of this function is relatively simple, but the word i18n can be added. the original word isinternationalization, but it is too long, so abbreviated asi18n, and similarkubernetes -> k8s.

ORM database

ORM this development is very mature, it is good to see the following evolutionary history:

  • Statement: Execute sql statement directly
  • QueryBuild: Use chain calls to splice sql statements
  • ActiveRecord: Model, used to map tables in a database, is actually a encapsulated QueryBuild.

Of course, the benefits of this layer of encapsulation are also obvious, reducing sql’s presence.

// insert
$post = new Post();
$post->title = 'daydaygo';
$post->save();

// query
$post = Post::find(1);

// update
$post->content = 'coder at work';
$post->save();

// delete
$post->del();

To achieve this effect, there is still a certain amount of code and some problems, such asCode hint, there are some more advanced functions, such asAssociated query

Basic concept

  • Concurrent vs parallel

captureParallelThis concept with a smaller scope is easy to understand. Parallelism is toSimultaneous execution, then only multiple cpu cores can operate at the same time. Concurrency is because cpu runs and switches quickly, executing multiple programs within a period of time, macroscopicallyLookLike at the same time

  • Synergy vs process

A simple statementConcorde is a thread in user stateThreads are scheduled by the operating system and can be automatically scheduled to multiple cpu for execution. At the same time, there is only one coprocessor running on the same cpu core. When encountering blocking IO in user code, the underlying scheduler will enter an event loop to reachThe coordination process is scheduled by the user.The effect of

  • Swoole2.0 native

The specific implementation principle will be explained in more detail in wiki when you check on the website. I’m here fromToolsUse the angle to explain

  1. Limiting condition 1: swoole2.0 coordination server+coordination client is required.
  2. Constraint 2: Can only be used in the onRequet, onReceive, onConnect event callback of the coordination server
$server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);

// 1: 创建一个协程
$server->on('Request', function($request, $response) {
    $mysql = new Swoole\Coroutine\MySQL();
    // 协程 client 有阻塞 IO 操作, 触发协程调度
    $res = $mysql->connect([
        'host' => '127.0.0.1',
        'user' => 'root',
        'password' => 'root',
        'database' => 'test',
    ]);
    // 阻塞 IO 事件就绪, 协程恢复执行
    if ($res == false) {
        $response->end("MySQL connect fail!");
        return;
    }
    // 出现阻塞 IO, 继续协程调度
    $ret = $mysql->query('show tables', 2);
    $response->end("swoole response is ok, result=".var_export($ret, true));
});

$server->start();

pay attention to: Triggering a callback function will generate a coordination process at the beginning and destroy the coordination process at the end. The life cycle of the coordination process and the life cycle accompanying the execution of the callback function here

Connection pool

Swoft’s connection pool function is mainly implemented insrc/PoolUnder, mainly consists of three parts:

  • Connect: Connection. It is worth mentioning that synchronous connection+asynchronous connection are configured here for convenience of subsequent use.
  • Balancer: load balancer, currently provides 2 strategies, random number+polling
  • Pool: connection pool, core part, responsible for connection management and scheduling

PS: Switching synchronous/asynchronous clients freely is very simple, just switch the connection

Code directly:

// 使用 SqlQueue 来管理连接
public function getConnect()
{
    if ($this->queue == null) {
        $this->queue = new \SplQueue(); // 又见 Spl
    }

    $connect = null;
    if ($this->currentCounter > $this->maxActive) {
        return null;
    }
    if (!$this->queue->isEmpty()) {
        $connect = $this->queue->shift(); // 有可用连接, 直接取
        return $connect;
    }

    $connect = $this->createConnect();
    if ($connect !== null) {
        $this->currentCounter++;
    }
    return $connect;
}

// 如果接入了服务治理, 将使用调度器
public function getConnectAddress()
{
    $serviceList = $this->getServiceList(); // 从 serviceProvider 那里获取到服务列表
    return $this->balancer->select($serviceList);
}

Service Governance Fusing, Degradation, Load, Registration and Discovery

Swoft’s service governance-related functions mainly includesrc/ServiceBelow:

  • Packer: Packer, which corresponds to the protocol, students who have read swoole documents can know the role of the protocol.
  • ServiceProvider: a service provider used to interface with third-party service management schemes. Consul has been implemented at present
  • Service: RPC service call, including synchronous call and coordinated call (deferCall()), currently adding callback to implement simpleDemote
  • ServiceConnect: RPC Service implementation of Connect in connection pool.However, I think it is better to put it into connection pool.
  • Circuit: fuse, insrc/CircuitIn the implementation, there are three states, closed/open/half open
  • DispatcherService: a service scheduler that encapsulates a layer before the service and adds functions such as Middleware/Event

Here, look at the code of fusing this part. The logic of the half-open state is more complicated, which is worth referring to:

// Swoft\Circuit\CircuitBreaker
public function init()
{
    // 状态初始化
    $this->circuitState = new CloseState($this);
    $this->halfOpenLock = new \swoole_lock(SWOOLE_MUTEX); // 使用 swoole lock
}

// Swoft\Circuit\HalfOpenState
public function doCall($callback, $params = [], $fallback = null)
{
    // 加锁
    $lock = $this->circuitBreaker->getHalfOpenLock();
    $lock->lock();
    ...
    // 释放锁
    $lock->unlock();
}

Task Delivery & Crontab Scheduled Task

Of course, the implementation mechanism of swoft task delivery cannot be separated from it.Swoole\Timer::tick()(\Swoole\Server->task()The underlying execution mechanism is the same). swoft addedlove to see and hearThe crontab method, implemented insrc/CrontabBelow:

  • ParseCrontab: parsing crontab
  • TableCrontab: usingSwoole\TableImplemented to store crontab tasks
  • Crontab: connect Task with TableCrontab

Here’s a look at TableCrontab:

// 存储原始的任务
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]
];

User defined process

Custom process pair\Swoole\ProcessAfter swoft encapsulation, it is easier to use user-defined processes:

InheritanceAbstractProcessClass and implements therun()To execute business logic.

The functions in swoft are implemented insrc/Process, the framework comes with three custom processes:

  • Reload: cooperateext-inotifyThe extension realizes automatic reload, which will be explained in detail below.
  • The task in CronTimer: crontab is triggered here.\Swoole\Server->tick()
  • CronExec: implement collaborative task, in progress.

The code will not be pasted, so here is another scenario that is more suitable for using custom processes:Subscription service

Inotify auto Reload

Most server programs are resident processes, which effectively reduce the generation and destruction of objects and provide performance. However, this also brings problems to the development of server programs. reload is required to view the effective programs. Useext-inotifyExpansion can solve this problem.

Directly on the code, look at the swoft implementation:

// Swoft\Process\ReloadProcess
public function run(Process $process)
{
    $pname = $this->server->getPname();
    $processName = "$pname reload process";
    $process->name($processName);

    /* @var Inotify $inotify */
    $inotify = App::getBean('inotify'); // 自定义进程来启动 inotify
    $inotify->setServer($this->server);
    $inotify->run();
}

// Swoft\Base\Inotify
public function run()
{

    $inotify = inotify_init(); // 使用 inotify 扩展

    // 设置为非阻塞
    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
    swoole_event_add($inotify, function ($inotify) { // 使用 \Swoole\Event
        // 读取有事件变化的文件
        $events = inotify_read($inotify);
        if ($events) {
            $this->reloadFiles($inotify, $events); // 监听到文件变动进行更新
        }
    }, null, SWOOLE_EVENT_READ);
}

Write at the end

To add another point, when implementing the service management (reload stop), theposix_kill(pid, sig);, is not to use\Swoole\ServerIt comes fromreload()Method, because the context of our current environment is not necessarily in the\Swoole\ServerChina.

To make a good framework, especially an open source framework, is actually better than we usually writeBusiness codeIt is much more difficult. On the one hand, it is the initial stage of the business.faster, often to someCan runHere are some ideas about code:

  • Code Quality: bug Rate+Performance
  • Code Specification: Forming a specification can improve the experience of code development/use
  • Code reuse: This is a difficult problem in software engineering, which needs to be accumulated slowly. Some places can take shortcuts by following the specifications.

To sum up, one sentence:

To significantly improve the coding level or quickly accumulate relevant technical knowledge, participating in open source can be regarded as a shortcut.