PHP物聯網開發利器之Actor併發模型

PHP不適合作物聯網服務端嗎?

在傳統的思惟中,常常會有人告訴你,php不適合用來作物聯網服務端,讓你換java,node,go等其餘語言,是的,沒錯傳統意義上的php,確實很難作物聯網服務器,由於它實在太蹩腳了,固然,這也不是意味着完全就不能作。舉個例子,當你想實現一個TCP服務器的時候,你可能須要寫出原理大約以下的代碼:php

for ($i = 0;$i <= 1;$i++){
    $pid = pcntl_fork();
    if($pid){
        if($i == 0){
            $server = stream_socket_server("tcp://127.0.0.1:9501", $errno, $errstr, STREAM_SERVER_BIND);
        }else if($i == 1){
            $tickTime = time()+3600;
            while (1){
                usleep(1);
                if($tickTime == time()){
                    //do my tick func
                }
            }
        }
    }
}

以上代碼的意義等於在一個進程中建立一個TCP 服務端,另一個進程中死循環來作時間檢測,從而實現定時器邏輯。這樣看起來,確實很蹩腳,並且對於編程基礎廣泛比較薄弱的PHPer來講,這真的很難維護。固然這個時候,就會有人說,這不是還有Workerman嗎,是的,確實還有Workerman,Workerman就是高度封裝了上述代碼原理,幫助你專心於實現代碼邏輯的一個PHP多進程框架,所以說PHP不時候作物聯網,其實這是謬論。固然這個時候可能又會有人說,go語言有協程,你用Workerman當出現阻塞數據庫調用的時候,那效率就很是的差,很難出現高併發,這麼說沒錯,可是實際上,咱們能夠儘量的用多進程去彌補這個不足,也就是堆機器。固然,若是你真的想錙銖必較,不要緊,這個時候咱們就能夠拿出咱們的殺器,那就是Swoole4.x的協程。java

Swoole作TCP服務器

舉個例子,以下代碼:node

$server = new swoole_server("127.0.0.1", 9501);
$server->on('workerstart',function ($ser,$workerId){
    if($workerId == 0){
        swoole_timer_tick(1000,function (){
            
        });
    }
});
$server->on('connect', function ($server, $fd){
    echo "connection open: {$fd}\n";
});
$server->on('receive', function ($server, $fd, $reactor_id, $data) {
    $server->send($fd, "Swoole: {$data}");
    $server->close($fd);
});
$server->on('close', function ($server, $fd) {
    echo "connection close: {$fd}\n";
});
$server->start();

咱們就能夠很快的建立出一個多進程的協程TCP服務器,並且在各個回調函數內,均自動建立協程環境,咱們能夠在協程回調內,去調用協程的數據庫API,這樣就避免了由於阻塞數據庫調用而致使沒法處理其餘客戶端請求的問題。然而儘管如此,不少人可能都沒有思考過,如何優雅的寫出本身的物聯網服務器。舉個例子,咱們常見的互聯網設備管理服務中,大約可能出現以下代碼:react

swoole_timer_tick(5000,function (){
    $deviceList = $db->getAll();
    foreach ($deviceList as $device){
        //do your check
        /*
         * 例如設備狀態處於1,那麼須要處理流程1
         * 例如設備狀態處於2,那麼須要處理流程2
         * 例如設備狀態處於3,那麼須要處理流程3
         */
    }
});
定時遍歷檢查設備狀態以及廣播
這樣乍一看好像無傷大雅,可是當出現多種設備,且每種設備邏輯都不一致的時候,那麼這樣的編寫模式就很容易寫出一大坨代碼出來,並且在協程下,若是不注意變量訪問安全與協程上下文隔離,那麼就很容易出現bug,致使很難維護。

Actor模型

什麼是Actor,簡單來講,Actor就是一種高度抽象化的併發模型,每一個Actor實例的內存空間都是互相隔離的,用於下降用戶編程與維護難度。關於Swoole4.x如何實現協程版本的Actor,咱們以前已經在文章 https://segmentfault.com/a/11... 中講解了如何用Swoole實現協程的原理。git

Actor模型庫實戰

咱們依舊用easyswoole/actor庫來說解,例如,咱們有一種型號的設備,那麼咱們能夠定義一個設備Actor,並把該設備的所有邏輯,寫在該actor模型內,例子代碼以下:github

namespace App\Device;


use EasySwoole\Actor\AbstractActor;
use EasySwoole\Actor\ActorConfig;
use EasySwoole\EasySwoole\Logger;
use EasySwoole\EasySwoole\ServerManager;
use EasySwoole\EasySwoole\Trigger;

class DeviceActor extends AbstractActor
{
    private $fd;
    private $deviceId;
    private $lastHeartBeat;
    public static function configure(ActorConfig $actorConfig)
    {
       $actorConfig->setActorName('Device');
    }

    protected function onStart()
    {
        $this->lastHeartBeat = time();
        /*
         * 該參數是建立的時候傳遞的
         */
        $this->fd = $this->getArg()['fd'];
        $this->deviceId = $this->getArg()['deviceId'];
        //記錄到table manager中
        DeviceManager::addDevice(new DeviceBean([
            'deviceId'=>$this->deviceId,
            'actorId'=>$this->actorId(),
            'fd'=>$this->fd
        ]));
        //推送消息
        ServerManager::getInstance()->getSwooleServer()->push($this->fd,"connect to server success,your actorId is {$this->actorId()}");
        //建立一個定時器,若是一個設備20s沒有收到消息,自動下線
        $this->tick(20*2000,function (){
            if(time() - $this->lastHeartBeat > 20){
                $this->exit(-1);
            }
        });
    }

    protected function onMessage($msg)
    {
        if($msg instanceof Command){
            switch ($msg->getCommand()){
                case $msg::RECONNECT:{
                    DeviceManager::updateDeviceInfo($this->deviceId,[
                        'fd'=>$msg->getArg()
                    ]);
                    $this->fd = $msg->getArg();
                    Logger::getInstance()->console("deviceId {$this->deviceId}  at actorId {$this->actorId()} reconnect success");
                    ServerManager::getInstance()->getSwooleServer()->push($this->fd,"deviceId {$this->deviceId}  at actorId {$this->actorId()} reconnect success");
                    break;
                }
                case $msg::WS_MSG:{
                    $recv = $msg->getArg();
                    Logger::getInstance()->console("deviceId {$this->deviceId}  at actorId {$this->actorId()} recv ws msg: {$recv}");
                    ServerManager::getInstance()->getSwooleServer()->push($this->fd,'actor recv msg for hash '.md5($recv));
                    break;
                }
                case $msg::REPLY_MSG:{
                    $recv = $msg->getArg();
                    Logger::getInstance()->console("deviceId {$this->deviceId}  at actorId {$this->actorId()} recv reply msg: {$recv}");
                    ServerManager::getInstance()->getSwooleServer()->push($this->fd,'actor recv reply msg '.$recv);
                    //此處return 一個數據,會返回給客戶端
                    return "actorId {$this->actorId()} recv {$recv}";
                    break;
                }
            }
        }
    }

    protected function onExit($arg)
    {
        if($arg == -1){
            if(ServerManager::getInstance()->getSwooleServer()->exist($this->fd)){
                ServerManager::getInstance()->getSwooleServer()->push($this->fd,"heartbeat lost,actor exit");
                ServerManager::getInstance()->getSwooleServer()->close($this->fd);
            }
        }
        DeviceManager::deleteDevice($this->deviceId);
        Logger::getInstance()->console("deviceId {$this->deviceId} at actorId {$this->actorId()} exit");
    }

    protected function onException(\Throwable $throwable)
    {
        Trigger::getInstance()->throwable($throwable);
    }
}

在該Actor內,咱們定義了這個設備的生命週期行爲。數據庫

  • 設備上線,記錄設備id與fd信息,並建立心跳週期檢查
  • 收到消息,能夠對該Actor投遞數據,處理對應的消息行爲
  • 設備下線,當設備下線,能夠自動的清理定時器與其餘的一些通知與清理邏輯

咱們能夠很清楚的看到,Actor模型下,容許咱們對一種設備模型進行高度自治的管理。固然,咱們本章節主要在講解如何優雅的利用Swoole協程來實現Actor模型,從而更好的開發管理咱們的設備,所以我再也不貼過多的代碼,有興趣的同窗能夠在Easyswoole框架demo中查看完整的示例代碼https://github.com/easy-swool...編程

Easyswoole項目主頁:http://easyswoole.com/
Easyswoole github 主倉庫https://github.com/easy-swool... ,若是你以爲咱們的努力有對你起到幫助做用,記得給個starsegmentfault

相關文章
相關標籤/搜索