Swoft source code interpretation

Time:2021-6-15

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

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

Outside the nickname,Welcome to starOur development team set a small goal of star 1000 + gathering offline once

The Yii / laravel framework in PHP is very “heavy”heavyLet’s not be specificperformanceAt the same time, the framework can solve many problems quickly and expand easily

PHP framework, Yii / laravel in, should be the best

At the same time, swoft uses a large number of functions provided by swoole, which is also very suitable for reading its code to learn how to make wheels. In fact, after reading the framework like Yii / laravel, some problems are solvedcurrencyThe frame design idea of is not repeated, mainly explain and explainServer developmentRelated parts, ideas will follow the official website feature list

The first half focuses on the common functions of the frame

  • Global container injection & MVC layered design
  • Annotation mechanism(Highlights, it is highly recommended to learn about it)
  • High performance routing
  • alias mechanism $aliases
  • Restful style
  • Event mechanism
  • Powerful log system
  • Internationalization (I18N)
  • Database ORM

The second half focuses on server related functions

  • Basic concepts(Highlight: the first framework based on the native coroutine of swoole2.0)
  • Connection pool
  • Service governance, degradation, load, registration and discovery
  • Task delivery & crontab timing 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 layered design

The reason why we put these two together is that one isinOne issurfaceIt’s just that new people listen moreMVCThe idea of hierarchical design is proposed, and the knowledge of global container injection is relatively less

  • MVC layered design: more business oriented

MVC is a simple, universal and practical methodSplit the business and implement itThe essence of designLayered designThe more important thing is to masterLayered designThis idea is widely used in engineering practice, such as OSI 7-layer network model and TCP / IP 4-layer network model. My layered design can be effectively determinedSystem boundary and responsibility division.

If you want to cultivate the idea of layered design, you can start fromDismantlingStart with, in the process of dismantling and assembling the wheel, you will be surprised to find that art is in it

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

  • Global container injection

Before entering this concept, we need to recognize another conceptobject-oriented programming. more commonly used may beProcess oriented programming vs object oriented programmingI won’t make a long speech here, just compare the way of thinking:

  1. Process oriented programming: the execution of one instruction after another, which is the preferred way for computers
  2. Object oriented programming: using objects toabstractInside different things, through the connection between things, to solve the related business

From this point of view,object-orientedIt may be more in line with the human way of thinking, or more intelligent way of thinking:

Abstract good management object, so as to better complete the task

But in the process of using object-oriented programming, there will be a problemnew, need to manage the dependency relationship between objects, global container injection is to do such a thingnewIndicates that an object needs to depend on another object, but with a container, it is an object that tells the container what it needs

I don’t care how it works – that’s how it worksnewAnd the difference between container injection, learn nameControl reversal.

So, the container isinWhen dealing with specific business, the container provides corresponding MVC objects to deal with

Annotation system

In the implementation of the container, or the bottom layer of the framework, in fact, each framework has its own characteristicsbe the same in essentials while differing in minor pointsLet’s talk about the difference of swoft – introducing annotation system

Briefly explain the annotation system: by adding comments & parsing comments, the comments can be transformed into some specific meaningful code

A little simpler:Comment = = code

In fact, it’s very simple to realize, but it’s less likely to be contacted–reflex:

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

    /**
     *Analysis of inject annotation
     *
     * @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];
        }

        //Analysis of phpdoc
        $phpReader = new PhpDocReader(); //  Convert comments to classes
        $property = new \ReflectionProperty($className, $propertyName); //  Using reflection
        $propertyClass = $phpReader->getPropertyClass($property);

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

If you are familiar with Java, you will find that many places are used before methods@override, which is also used in symfony. The advantage is a certain degree of cohesion, simpler use, and less configuration

High performance routing

First of all, what is routing? From the point of view of objects, routing actually corresponds toURLWhat is the URL?

URL, uniform resource locator, uniform resource locator

Therefore, the abstraction of routing layer is to solve the problem of finding the corresponding logic of URL

Now let’s explain the high performance mentioned by swoft

//App / routes.php: routing profile
$router = \Swoft\App::getBean('httpRouter'); //  Take httprouter through container

//Config / beans / base.php: beans configuration file
'httpRouter'      => [
    'class' = > < swoft / router / HTTP / handlermapping:: class, // httprouter actually corresponds to this
    'ignoreLastSep'  => false,
    'tmpCacheNumber' => 1000,
    'matchAll'       => '',
],

// \Swoft\Router\Http\HandlerMapping
private $cacheCounter = 0;
private $staticRoutes = []; //  Static routing
private $regularRoutes = []; //  Dynamic routing
Protected function cachematched paramroute ($path, array $CONF) {} // will cache the matched route
//The route matching method is also very simple: check, process static route, process dynamic route
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 route matching logic + route caching

alias mechanism $aliases

Those who have used Yii are more familiar with this one. In fact, it’s this oneevolutionary process :

  • use__DIR__ / DIRECTORY_SEPARATORAnd so on
  • usedefine() / defined()Define global variables to use paths
  • use$aliasesVariable replaces global variable

Here we only show the configuration. The implementation is just a change in the class$aliasesJust store the attributes:

// config/define.php
//Basic root directory
!defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
//Register alias
$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

Restful’s idea is very simpleWith resources as the core, business is actually around the addition, deletion, modification and query of resourcesSpecific to http:

  • URL is only used as resource identification, which has two forms,itemanditem/idThe latter means to operate on a specific resource
  • HTTP method (get / post / put, etc.) is used to correspond to the crud of the resource
  • Using JSON format for data processingInput and output

The implementation is simple: route + return

Event mechanism

First, explain it with the idea of 3w1h (who what why how) analysis methodEvent mechanismWhat’s more, what’s the use of this

normalIn other words, people’s thinking trend is carried out in accordance with the procedureTime linear serialYes, keep it upContinuityHowever, there are various problems in realityinterruptThe program is not alwaysReady stateThen, we need a mechanism to deal with all kinds of interruptions, or switch between different program states

With the development of event mechanism up to now, sometimes it can be regarded as a reserved means, according to your experience in the place where you needBurying pointAfter conveniencepatch up.

Swoft event mechanism based onPSR-14Implementation, highly cohesive and concise

It consists of three parts

  • Eventmanager: Event Manager
  • Event: Event
  • EventHandler / listener: event handler / listener

Implementation process:

  • Mr. Cheng
  • Register event and EventHandler with eventmanager
  • When an event is triggered, the eventmanager will call the corresponding EventHandler

It’s easier to use

use Swoft\Event\EventManager;

$em = new EventManager;

//Register event monitoring
$em->attach('someEvent', 'callback_ handler'); //  Here you can also use annotation mechanism to implement event monitoring registration

//Trigger event
$em->trigger('someEvent', 'target', ['more params']);

//It's OK
$event = new Event('someEvent', ['more params']);
$em->trigger($event);

Let’s take a look at the highlights of swoft’s event mechanism to improve performance

namespace Swoft\Event;

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

    /**
     *Priority queue
     * @var \SplPriorityQueue
     */
    protected $queue;

    /**
     *Counter
     *Set the maximum value to PHP_ INT_ MAX == 300
     * @var int
     */
    private $counter = PHP_INT_MAX;

    public function __construct()
    {
        $this->store = new \SplObjectStorage(); //  Add the event object here first
        $this->queue = new \SplPriorityQueue(); //  Then join the priority queue, and then schedule
    }
    ...
}

People who have played ACM a little bit are rightPriority queueBut PHPer doesn’t need to worry too much about the bottom layer implementation. It can directly use SPL library

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

Powerful log system

usemonolog/monologTo achieve the basic log system has become a standard, of course, the underlying or implementationPSR-3Standard. However, this standard appeared earlier, developed to the present, hidden more deeply

This is also the reason for the establishment of technical standards / agreementsBest practicesAfter the efforts are toward more and more easy to use development

Swoft’s log system consists of two parts

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

As for another document,Swoft\Log\Log, just a layer of encapsulation for logger, which is more convenient to call

Of course, there are obvious similarities between swoft’s logging system and yii2 framework

//Quick read exposed log function in app
public static function info($message, array $context = array())
{
    self::getLogger()->info($message, $context); //  In fact, it's still handled by logger
}

//The profile function has been added
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 logging system of yii2 framework consists of three parts

  • Logger: log main function
  • Dispatch: log distribution, the same log can be distributed to different target processing
  • Target: log consumer

Such a design, in fact, willFileHandlerIt is more flexible and convenient to expand

Let’s take a look at the powerful side of the swoft log system

private function aysncWrite(string $logFile, string $messageText)
{
    while (true) {
        //Using spool asynchronous file IO
        $result = \Swoole\Async::writeFile($logFile, $messageText, null, FILE_APPEND);
        if ($result == true) {
            break;
        }
    }
}

Of course, you can also choose the synchronization mode:

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); //  Be careful to lock
    fwrite($fp, $messageText);
    flock($fp, LOCK_UN);
    fclose($fp);
}

PSLog statistical analysis function development team is developing, welcome to recommend the scheme~

Internationalization (I18N)

The realization of this function is relatively simple, but the word I18N can be said more, the original word isinternationalizationBut it’s too longi18nAnd so onkubernetes -> k8s.

Database ORM

The development of ORM is very mature. Just look at the following evolutionary history:

  • Statement: execute SQL statement directly
  • Querybuild: using chain call to realize splicing SQL statements
  • Activerecord: model, which is used to map tables in the database, is actually an encapsulated querybuild

Of course, the benefits of this layer of encapsulation are obvious, reducing the sense of existence of SQL

// 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, there will be some problems, such asCode tips, and some more advanced functions, such asAssociation query

Basic concepts

  • Concurrent vs Parallel

graspparallelThe concept of a smaller scope is easy to understand, and parallelism is importantSimultaneous execution, then only multiple CPU cores can operate at the same time; Concurrency is due to the CPU running and switching speed, time period to execute multiple programs, macroseemIt’s like executing at the same time

  • Process vs process

A simple way to put itA coroutine is a thread in user modeThreads are scheduled by the operating system and can be automatically scheduled to multiple CPUs for execution; At the same time, there is only one coroutine running on the same CPU core. When encountering the blocking IO in user code, the underlying scheduler will enter the event cycle and reach the goalThe cooperative process is scheduled by the userThe effect of

  • Spoole2.0 native

The specific implementation principle of you to the official website view, there will be more detailed wiki description, I here fromtoolUse the angle of view to illustrate

  1. Restriction 1: collaboration server + collaboration client of spoole2.0 is required
  2. Restriction 2: it can only be used in the onrequest, onReceive and onconnect event callback of the coroutine server
$server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);

//1: create a coroutine
$server->on('Request', function($request, $response) {
    $mysql = new Swoole\Coroutine\MySQL();
    //If there is a blocking IO operation in the coroutine client, the coroutine scheduling will be triggered
    $res = $mysql->connect([
        'host' => '127.0.0.1',
        'user' => 'root',
        'password' => 'root',
        'database' => 'test',
    ]);
    //The blocking IO event is ready and the coroutine resumes execution
    if ($res == false) {
        $response->end("MySQL connect fail!");
        return;
    }
    //Blocking IO, continue scheduling
    $ret = $mysql->query('show tables', 2);
    $response->end("swoole response is ok, result=".var_export($ret, true));
});

$server->start();

be careful: if a callback function is triggered, a coroutine will be generated at the beginning and destroyed at the end. The lifecycle of the coroutine is the lifecycle of the callback function

Connection pool

Swoft connection pool function, mainly in thesrc/PoolIt consists of three parts

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

PS: it’s very easy to switch the synchronous / asynchronous client freely, just switch the connection

Direct code:

//Using sqlqueue to manage connections
public function getConnect()
{
    if ($this->queue == null) {
        $this->queue = new \SplQueue(); //  See also SPL
    }

    $connect = null;
    if ($this->currentCounter > $this->maxActive) {
        return null;
    }
    if (!$this->queue->isEmpty()) {
        $connect = $this->queue->shift(); //  There are available connections, direct access
        return $connect;
    }

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

//If service governance is connected, the scheduler will be used
public function getConnectAddress()
{
    $serviceList = $this->getServiceList(); //  Get the service list from the serviceprovider
    return $this->balancer->select($serviceList);
}

Service governance, degradation, load, registration and discovery

Swoft’s service governance related functions mainly includesrc/ServiceNext:

  • Packer: the packer corresponds to the protocol. Students who have read the spool document will know the role of the protocol
  • Serviceprovider: a service provider, which is used to interface with the third-party service management scheme. At present, it has implemented consul
  • Service: RPC service call, including synchronous call and coroutine call(deferCall())At present, add callback to realize simpleDemotion
  • Serviceconnect: RPC service implementation of connect in connection pool,But I think it’s better to put it in the connection pool
  • Circuit: fuse, insrc/CircuitThere are three states, close / open / half open
  • Dispatcherservice: Service scheduler, encapsulating a layer before service, adding functions such as middleware / event

Let’s take a look at the code of the fusing part. The logic of the half open state is more complex, which is worth referring to

// Swoft\Circuit\CircuitBreaker
public function init()
{
    //State initialization
    $this->circuitState = new CloseState($this);
    $this->halfOpenLock = new \swoole_ lock(SWOOLE_ MUTEX); //  Using spool lock
}

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

Task delivery & crontab timing task

Of course, the implementation mechanism of swoft task delivery is inseparableSwoole\Timer::tick()(\Swoole\Server->task()The underlying execution mechanism is the same). Swoft addslove to see and hearThe crontab method is implemented in thesrc/CrontabNext:

  • Parsecrontab: analyzing crontab
  • Tablecrontab: UsingSwoole\TableImplementation, used to store crontab tasks
  • Crontab: connecting task and tablecrontab

Let’s take a look at tablecrontab

//Store original tasks
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]
];
//Store resolved tasks
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 pairs\Swoole\ProcessAfter swoft encapsulation, it is easier to use user-defined process

inheritAbstractProcessClass and implementrun()To execute business logic

The function realization in swoftsrc/ProcessIn this case, the framework comes with three custom processes:

  • Reload: Cooperationext-inotifyExtend the implementation of automatic reload, which will be explained in detail below
  • Crontimer: the task in crontab is triggered here\Swoole\Server->tick()
  • Cronexec: implementation of coroutine task, in implementation

The code will not be pasted. Here we extend a scenario that is more suitable for using custom processesSubscription Service

Inotify auto reload

Server programs are mostly resident processes, which can effectively reduce the generation and destruction of objects and provide performance. However, this also brings problems to the development of server programs. You need to reload to view the effective programsext-inotifyExtension can solve this problem

Go directly to the code and see the implementation in swoft

// 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'); //  Customize the process to start inotify
    $inotify->setServer($this->server);
    $inotify->run();
}

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

    $inotify = inotify_ init(); //  Extending with inotify

    //Set to non blocking
    stream_set_blocking($inotify, 0);

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

        //Listen to directory only
        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) {// use the
        //Read files with event changes
        $events = inotify_read($inotify);
        if ($events) {
            $this->reloadFiles($inotify, $events); //  Listen to file change to update
        }
    }, null, SWOOLE_EVENT_READ);
}

Write at the end

In addition, when implementing the service management (reload stop), theposix_kill(pid, sig);Not with\Swoole\ServerBrought inreload()Method, because the context of our current environment is not necessarily in the\Swoole\ServerIn the middle

If we want to make a good framework, especially an open source framework, it is actually better than what we usually writeBusiness codeIt’s a lot more difficult. On the one hand, it’s in the early stage of businessachieve greater , faster , better and more economical results, often on someCan runHere we introduce 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. In some places, we can take a shortcut by following the specification

In a word:

If you want to significantly improve the coding level or quickly accumulate relevant technical knowledge, participating in open source can be regarded as a shortcut