基於 swoole 的連線池模型和非同步任務小框架

wilson_yang發表於2018-11-08

維護著一個很老的專案,基於CodeIgniter寫的,幾經易手之後,CI框架底層原始碼都被改得不能按照CI官方文件來操作了,非常頭疼,其中某些部分的程式碼慢的要命,靠優化SQL語句,根據慢查詢給舊錶加索引之類的,雖然有一些成效,但是不能解決所有問題。幾個月前就想自己擼一個非同步任務處理的框架練練手,同時也希望能解決一下工作中的當務之急,於是就陸陸續續的寫了一個,用來寫入一些不是很重要的資料和不是很需要及時反饋給客戶端的功能,類似日誌類的資料和粗略統計用的資料。專案地址在這裡

使用 Swoole Server 作為處理任務的服務端,它是一個TCP Server,因此任何TCP Client 都可以向它投遞任務,目前專案中只做了兩種 Client 的實現。
整個任務的處理流程比較簡單:Client--->send序列化之後的任務物件--->Server--->Receive任務--->反序列化--->物件執行任務

Client

一個 client 例項一次性可以 send 多個任務:

namespace TaskClient;
use Task\AbstractTask;
class CommonSender
{
    private $fp;
    public function __construct()
    {
        $config = require dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'swoole.php';
        $this->fp = stream_socket_client("tcp://" . $config['ip'] . ":" . $config['swoole_task_server_port'], $errno, $errstr);
        if (!$this->fp) {
            echo "ERROR: $errno - $errstr\n";
        }
    }
    public function sendTask(AbstractTask $task)
    {
        $serializedTask = serialize($task);
        //Server在接收的時候如果Client沒有釋放連線,接收到的的資料將會作為一整塊資料,這裡拼接PHP_EOL
        //是為了隔開一個client例項多次傳送的資料
        return fwrite($this->fp, $serializedTask . PHP_EOL, strlen($serializedTask) + 1);
    }
    public function sendCommand($command)
    {
        $serializedCommand = serialize($command);
        return fwrite($this->fp, $serializedCommand . PHP_EOL, strlen($serializedCommand) + 1);
    }
    public function __destruct()
    {
        fclose($this->fp);
    }
}

Task任務物件

所有的任務物件都繼承自一個抽象類,在需要資料庫連線資源時,由Server 注入即可(在序列化之前是不可以出現資源型別的,因為資源型別是不可以被序列化的),Server是常駐程式的所以其中的資料庫連線能夠做到有意義的“池”化,例程中使用了Medoo作為資料庫包裝類(本質上還是基於 PDO 的):

<?php

namespace Task;

use DB\MedooPDO;

abstract class AbstractTask
{
    protected $DB;//此處應該由server 來分配資源

    /**
     * 在server中可以注入資源
     */
    public function setDB(MedooPDO $db = null)
    {
        $this->DB = $db;
        return;
    }

    /**
     * 業務程式碼中應實現的具體方法
     * @return mixed
     */
    public abstract function handler();

    /**
     * DB 是否斷線
     * @return bool
     */
    public function isDBGone()
    {
        //這裡是基於Meddo框架的錯誤碼,如果有其他實現,需要重寫此方法
        return $this->DB->error()[1] == 2006;
    }
}

Server

程式碼較多就不貼了。
其中使用了一個靜態陣列作為“池”容器,在其中放入資料庫連線物件,如果有可用物件則取出(pop)物件使用,使用完畢之後歸還到“池”中(push);
Task 如果使用了資料庫連線物件,失敗時會先嚐試從池中繼續取其他可用物件,繼續注入執行,如果物件全部都取完了,池子空了還仍然沒有可用的資料庫連線物件,那麼就嘗試重新建立,然後注入池中使用。

資料庫連線池在每個程式都有一個,假如程式數是m,池容量為n,那麼將會建立m*n個mysql資料庫連線,因為MySQL連線資源非常有限,這就造成了極大的浪費,應考慮減少這種浪費,例如將使用資料庫放在某些程式中單獨執行。

每天進步一點點

相關文章