成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Swoft 源碼剖析 - 連接池

rozbo / 1553人閱讀

摘要:基于擴(kuò)展實(shí)現(xiàn)真正的數(shù)據(jù)庫(kù)連接池這種方案中,項(xiàng)目占用的連接數(shù)僅僅為。一種是連接暫時(shí)不再使用,其占用狀態(tài)解除,可以從使用者手中交回到空閑隊(duì)列中這種我們稱為連接的歸隊(duì)。源碼剖析系列目錄

作者:bromine
鏈接:https://www.jianshu.com/p/1a7...
來(lái)源:簡(jiǎn)書(shū)
著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。
Swoft Github: https://github.com/swoft-clou...

為什么需要引入連接池?

對(duì)于基于php-fpm的傳統(tǒng)php-web應(yīng)用,包括且不限于Mysql,Redis,RabbitMq,每次請(qǐng)求到來(lái)都需要為其新建一套獨(dú)享的的連接,這直接帶來(lái)了一些典型問(wèn)題:

連接開(kāi)銷:連接隨著http請(qǐng)求到來(lái)而新建,隨著請(qǐng)求返回而銷毀,大量連接新建銷毀是對(duì)系統(tǒng)資源的浪費(fèi)。

連接數(shù)量過(guò)高:每一個(gè)請(qǐng)求都需要一套自己的連接,系統(tǒng)連接數(shù)和并發(fā)數(shù)會(huì)成一個(gè)近線性的關(guān)系。如果系統(tǒng)并發(fā)量達(dá)到了1w,那么就需要建立1w個(gè)對(duì)應(yīng)的連接,這對(duì)于Mysql之類的后端服務(wù)而言,是一個(gè)大的負(fù)荷。

空閑連接:假設(shè)我們有一個(gè)接口使用了一個(gè)Mysql連接。該接口在一開(kāi)始進(jìn)行一次sql查詢后,后面的操作都是sql無(wú)關(guān)的,那么該請(qǐng)求占據(jù)的空閑連接完全就是一種資源的浪費(fèi)。

對(duì)于異步系統(tǒng)而言,這個(gè)問(wèn)題變得更加的嚴(yán)峻。一個(gè)請(qǐng)求處理進(jìn)程要對(duì)同一個(gè)服務(wù)進(jìn)行并發(fā)的操作,意味著這個(gè)請(qǐng)求要持有1個(gè)以上同類的連接,這對(duì)于系統(tǒng)壓力而言,無(wú)疑是雪上加霜了,所以連接池對(duì)于基于Swoole的Web框架而言已經(jīng)是一個(gè)必需實(shí)現(xiàn)的機(jī)制了。

Swoft連接池的生命周期與進(jìn)程模型

連接池作為一個(gè)SCOPESINGLETON的典型Bean,
其實(shí)例最早會(huì)在SwoftBeanBeanFactory::reload()階段被初始化。

Worker/Task進(jìn)程

對(duì)于RPC或者HTTP請(qǐng)求而言,關(guān)系最密切的進(jìn)程肯定是Worker和Task進(jìn)程了。
對(duì)于這兩者而言 SwoftBeanBeanFactory::reload()會(huì)在swoole的onWorkerStart事件的回調(diào)階段階段被調(diào)用。

//SwoftBootstrapServerServerTrait(HttpServer和RpcServer都使用了該性狀)
/**
 * OnWorkerStart event callback
 *
 * @param Server $server server
 * @param int $workerId workerId
 * @throws InvalidArgumentException
 */
public function onWorkerStart(Server $server, int $workerId)
{
    // Init Worker and TaskWorker
    $setting = $server->setting;
    $isWorker = false;

    if ($workerId >= $setting["worker_num"]) {
        // TaskWorker
        ApplicationContext::setContext(ApplicationContext::TASK);
        ProcessHelper::setProcessTitle($this->serverSetting["pname"] . " task process");
    } else {
        // Worker
        $isWorker = true;
        ApplicationContext::setContext(ApplicationContext::WORKER);
        ProcessHelper::setProcessTitle($this->serverSetting["pname"] . " worker process");
    }

    $this->fireServerEvent(SwooleEvent::ON_WORKER_START, [$server, $workerId, $isWorker]);
    //beforeWorkerStart()內(nèi)部會(huì)調(diào)用BeanFactory::reload();
    $this->beforeWorkerStart($server, $workerId, $isWorker);
}

這意味著此時(shí)的連接池對(duì)象的生命周期是 進(jìn)程全局期而不是程序全局期
將進(jìn)程池設(shè)計(jì)為進(jìn)程全局期,而不是共享程度最高的程序全局期原因,個(gè)人認(rèn)為主要有3個(gè)

多個(gè)進(jìn)程同時(shí)對(duì)一個(gè)連接進(jìn)行讀寫會(huì)導(dǎo)致數(shù)據(jù)傳輸錯(cuò)亂,需要保證連接不會(huì)被同時(shí)訪問(wèn)。

Worker進(jìn)程對(duì)程序全局期的對(duì)象進(jìn)行寫操作時(shí)會(huì)導(dǎo)致寫時(shí)復(fù)制,產(chǎn)生一個(gè)進(jìn)程全局期的副本,程序全局期較難維持。

使用進(jìn)程全局期的話可以利用現(xiàn)有的Bean機(jī)制管理對(duì)象,減少的特殊編碼。

Process中的連接池
//SwoftProcessProcessBuilder.php
/**
     * After process
     *
     * @param string $processName
     * @param bool   $boot 該參數(shù)即Process 注解的boot屬性
     */
    private static function beforeProcess(string $processName, $boot)
    {
        if ($boot) {
            BeanFactory::reload();
            $initApplicationContext = new InitApplicationContext();
            $initApplicationContext->init();
        }

        App::trigger(ProcessEvent::BEFORE_PROCESS, null, $processName);
    }
}

Swoft中的Process有兩種:一種是定義Process 注解的boot屬性為true的 前置進(jìn)程,這種進(jìn)程隨系統(tǒng)啟動(dòng)而啟動(dòng)的 ;另一種是定義Process 注解的boot屬性為false的 用戶自定義進(jìn)程 ,該類進(jìn)程需要用戶在需要的時(shí)候手動(dòng)調(diào)用ProcessBuilder::create()啟動(dòng) 。

但是無(wú)論是何者,最終都會(huì)在Process中調(diào)用beforeProcess()進(jìn)行子進(jìn)程的初始化。對(duì)于 boot為true的 前置進(jìn)程 ,由于其啟動(dòng)時(shí)父進(jìn)程還未初始化bean容器,所以會(huì)多帶帶進(jìn)行bean容器初始化,而對(duì)于boot為false的其他 用戶自定義進(jìn)程,其會(huì)直接繼承父進(jìn)程的Ioc容器。

Swoft基本上遵守著一個(gè)進(jìn)程擁有一個(gè)多帶帶連接池的規(guī)則,這樣所有進(jìn)程中的連接都是獨(dú)立的,保證了連接
不會(huì)被同時(shí)讀寫。唯獨(dú)在Process中有一個(gè)特例。如果對(duì)先使用依賴連接池的服務(wù),如對(duì)Mysql進(jìn)行CRUD,再調(diào)用ProcessBuilder::create()啟動(dòng) 用戶自定義進(jìn)程,由于用戶自定義進(jìn)程 會(huì)直接繼承父進(jìn)程的Bean容器而不重置,這時(shí)子進(jìn)程會(huì)獲得父進(jìn)程中的連接池和連接。

Command
/**
 * The adapter of command
 * @Bean()
 */
class HandlerAdapter
{
    /**
     * before command
     *
     * @param string $class
     * @param string $command
     * @param bool   $server
     */
    private function beforeCommand(string $class, string $command, bool $server)
    {
        if ($server) {
            return;
        }
        $this->bootstrap();
        BeanFactory::reload();

        // 初始化
        $spanId = 0;
        $logId = uniqid();

        $uri = $class . "->" . $command;
        $contextData = [
            "logid"       => $logId,
            "spanid"      => $spanId,
            "uri"         => $uri,
            "requestTime" => microtime(true),
        ];

        RequestContext::setContextData($contextData);
    }
}

命令行腳本擁有自己多帶帶的Bean容器,其情況和Process相似且更簡(jiǎn)單,嚴(yán)格遵循一個(gè)進(jìn)程一個(gè)連接池,這里不再累述。


假設(shè)Worker數(shù)目為j,Task數(shù)目為k,Process數(shù)為l,Command數(shù)為m,每個(gè)進(jìn)程池內(nèi)配置最大連接數(shù)為n,部署機(jī)器數(shù)為x,不難看出每個(gè)swoft項(xiàng)目占用的連接數(shù)為(j+k+l+m)*n*x。

天峰本人曾經(jīng)提過(guò)另一種基于Swoole的連接池模型。
Rango-<基于swoole擴(kuò)展實(shí)現(xiàn)真正的PHP數(shù)據(jù)庫(kù)連接池>


這種方案中,項(xiàng)目占用的連接數(shù)僅僅為k*x。
除了Task進(jìn)程各個(gè)進(jìn)程并不直接持有連接池,而是通過(guò)向Task進(jìn)程提交指令(task(),sendMessage())讓其代為進(jìn)行連接池相關(guān)服務(wù)的操作,至少需要額外的一次進(jìn)程間通信(默認(rèn)為Unix Socket)。
該方案雖然能夠更好的復(fù)用連接和節(jié)省連接數(shù),但機(jī)制實(shí)現(xiàn)并不方便。從另一個(gè)角度去看,Swoft的連接池方案是為了解決使用Swoole時(shí),單進(jìn)程并發(fā)執(zhí)行的連接數(shù)要求問(wèn)題;Range提出的連接池方案是為了解決超大流量系統(tǒng)下對(duì)Mysql等服務(wù)的壓力控制問(wèn)題。兩者適合不同的場(chǎng)景,其目的和意義在一定程度下是重合的,但并不是完全一樣的。

Swoft連接池的實(shí)現(xiàn) 池的容器

連接池根據(jù)當(dāng)前是否協(xié)程環(huán)境選擇一種合適的隊(duì)列結(jié)構(gòu)作為連接的容器。

SplQueue:SplQueue是PHP標(biāo)準(zhǔn)庫(kù)的數(shù)據(jù)結(jié)構(gòu),底層是一個(gè)雙向鏈表,在隊(duì)列操作這種特化場(chǎng)景下,性能遠(yuǎn)高于底層使用鏈表+哈希表實(shí)現(xiàn)的array()數(shù)據(jù)結(jié)構(gòu)。

SwooleCoroutineChannel是Swoole提供的協(xié)程相關(guān)的數(shù)據(jù)結(jié)構(gòu),不僅提供了常規(guī)的隊(duì)列操作。在協(xié)程環(huán)境下,當(dāng)其隊(duì)列長(zhǎng)度從0至1之間切換時(shí),會(huì)自動(dòng)讓出協(xié)程控制權(quán)并喚醒對(duì)應(yīng)的生產(chǎn)者或消費(fèi)者。

連接的獲取
SwoftPoolConnectionPool.php
abstract class ConnectionPool implements PoolInterface {
    /**
     * Get connection
     *
     * @throws ConnectionException;
     * @return ConnectionInterface
     */
    public function getConnection():ConnectionInterface
    {
        //根據(jù)執(zhí)行環(huán)境選擇容器
        if (App::isCoContext()) {
            $connection = $this->getConnectionByChannel();
        } else {
            $connection = $this->getConnectionByQueue();
        }

        //連接使用前的檢查和重新連接
        if ($connection->check() == false) {
            $connection->reconnect();
        }
        //加入到全局上下文中,事務(wù)處理和資源相關(guān)的監(jiān)聽(tīng)事件會(huì)用到
        $this->addContextConnection($connection);
        return $connection;
    }
}
SwoftPoolConnectionPool.php
/**
 * Get connection by queue
 *
 * @return ConnectionInterface
 * @throws ConnectionException
 */
private function getConnectionByQueue(): ConnectionInterface
{
    if($this->queue == null){
        $this->queue = new SplQueue();
    }
    
    if (!$this->queue->isEmpty()) {
        //隊(duì)列存在可用連接直接獲取
        return $this->getEffectiveConnection($this->queue->count(), false);
    }
    //超出隊(duì)列最大長(zhǎng)度
    if ($this->currentCount >= $this->poolConfig->getMaxActive()) {
        throw new ConnectionException("Connection pool queue is full");
    }
    //向隊(duì)列補(bǔ)充連接
    $connect = $this->createConnection();
    $this->currentCount++;

    return $connect;
}
SwoftPoolConnectionPool.php
/**
 * Get effective connection
 *
 * @param int  $queueNum
 * @param bool $isChannel
 *
 * @return ConnectionInterface
 */
private function getEffectiveConnection(int $queueNum, bool $isChannel = true): ConnectionInterface
{
    $minActive = $this->poolConfig->getMinActive();
    //連接池中連接少于數(shù)量下限時(shí)直接獲取
    if ($queueNum <= $minActive) {
        return $this->getOriginalConnection($isChannel);
    }

    $time        = time();
    $moreActive  = $queueNum - $minActive;
    $maxWaitTime = $this->poolConfig->getMaxWaitTime();
    //檢查多余的連接,如等待時(shí)間過(guò)長(zhǎng),表示當(dāng)前所持連接數(shù)暫時(shí)大于需求值,且易失效,直接釋放
    for ($i = 0; $i < $moreActive; $i++) {
        /* @var ConnectionInterface $connection */
        $connection = $this->getOriginalConnection($isChannel);;
        $lastTime = $connection->getLastTime();
        if ($time - $lastTime < $maxWaitTime) {
            return $connection;
        }
        $this->currentCount--;
    }

    return $this->getOriginalConnection($isChannel);
}

加點(diǎn)注釋就非常清晰了,此處不再贅述。

連接的釋放

連接的釋放有兩種不同的容易引起歧義的用法,為此我們做以下定義:
一種是連接已經(jīng)不再使用了,可以關(guān)閉了,這種我們稱為 連接的銷毀。
一種是連接暫時(shí)不再使用,其占用狀態(tài)解除,可以從使用者手中交回到空閑隊(duì)列中,這種我們稱為 連接的歸隊(duì)

鏈接的銷毀

一般通過(guò)unset變量,或者通過(guò)其他手段清除連接變量的所有引用,等待Zend引擎實(shí)現(xiàn)鏈接資源清理。
這一點(diǎn)在上文的getEffectiveConnection()中出現(xiàn)過(guò)。執(zhí)行到$this->currentCount--;的時(shí)候 ,連接已經(jīng)出隊(duì)了,而$connection變量會(huì)在下個(gè)循環(huán)時(shí)作為循環(huán)變量被替換或者方法返回時(shí)作為局部變量被清除,連接資源的引用清0.引用降到0的資源會(huì)在下次gc執(zhí)行時(shí)被回收,所以你沒(méi)看到主動(dòng)的連接釋放代碼也很正常。
如果你的代碼在其他地方引用了這連接而沒(méi)管理好,可能會(huì)導(dǎo)致資源泄露。

鏈接的歸隊(duì)
/**
 * Class AbstractConnect
 */
abstract class AbstractConnection implements ConnectionInterface
{
    //SwoftPoolAbstractConnection.php
    /**
     * @param bool $release
     */
    public function release($release = false)
    {
        if ($this->isAutoRelease() || $release) {
            $this->pool->release($this);
        }
    }
}
//SwoftPoolConnectionPool.php
/**
 * Class ConnectPool
 */
abstract class ConnectionPool implements PoolInterface
{
    /**
     * Release connection
     *
     * @param ConnectionInterface $connection
     */
    public function release(ConnectionInterface $connection)
    {
        $connectionId = $connection->getConnectionId();
        $connection->updateLastTime();
        $connection->setRecv(true);
        $connection->setAutoRelease(true);

        if (App::isCoContext()) {
            $this->releaseToChannel($connection);
        } else {
            $this->releaseToQueue($connection);
        }

        $this->removeContextConnection($connectionId);
    }
}

當(dāng)用戶使用完某個(gè)連接后,比如執(zhí)行了完了一條sql后,應(yīng)當(dāng)調(diào)用連接的release()方法。
連接本身是持有連接池的反向連接,在用戶調(diào)用ConnectionInterface->release()方法時(shí),并不會(huì)馬上銷毀自身,而是清理自身的標(biāo)記,調(diào)用PoolInterface->release()重新加入到連接池中。

//SwoftEventListenersResourceReleaseListener.php
/**
 * Resource release listener
 *
 * @Listener(AppEvent::RESOURCE_RELEASE)
 */
class ResourceReleaseListener implements EventHandlerInterface
{
    /**
     * @param SwoftEventEventInterface $event
     * @throws InvalidArgumentException
     */
    public function handle(EventInterface $event)
    {
        // Release system resources
        App::trigger(AppEvent::RESOURCE_RELEASE_BEFORE);

        $connectionKey = PoolHelper::getContextCntKey();
        $connections   = RequestContext::getContextDataByKey($connectionKey, []);
        if (empty($connections)) {
            return;
        }

        /* @var SwoftPoolConnectionInterface $connection */
        foreach ($connections as $connectionId => $connection) {
            if (!$connection->isRecv()) {
                Log::error(sprintf("%s connection is not received ,forget to getResult()", get_class($connection)));
                $connection->receive();
            }

            Log::error(sprintf("%s connection is not released ,forget to getResult()", get_class($connection)));
            $connection->release(true);
        }
    }
}

考慮到用戶可能會(huì)在使用完后沒(méi)有釋放連接造成連接泄露,Swoft會(huì)在Rpc/Http請(qǐng)求或者Task結(jié)束后觸發(fā)一個(gè)Swoft.resourceRelease事件(注:Swoft是筆者添加的前綴,方便讀者區(qū)分Swoole相關(guān)事件和Swoft相關(guān)事件),將連接強(qiáng)制收包并歸隊(duì)。

Swoft源碼剖析系列目錄:https://segmentfault.com/a/11...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/28976.html

相關(guān)文章

  • Swoft 源碼剖析 - 目錄

    摘要:作者鏈接來(lái)源簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。同時(shí)順手整理個(gè)人對(duì)源碼的相關(guān)理解,希望能夠稍微填補(bǔ)學(xué)習(xí)領(lǐng)域的空白。系列文章只會(huì)節(jié)選關(guān)鍵代碼輔以思路講解,請(qǐng)自行配合源碼閱讀。 作者:bromine鏈接:https://www.jianshu.com/p/2f6...來(lái)源:簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。Swoft...

    qpwoeiru96 評(píng)論0 收藏0
  • Swoft 源碼剖析 - Swoole和Swoft的那些事 (Http/Rpc服務(wù)篇)

    摘要:和服務(wù)關(guān)系最密切的進(jìn)程是中的進(jìn)程組,絕大部分業(yè)務(wù)處理都在該進(jìn)程中進(jìn)行。隨后觸發(fā)一個(gè)事件各組件通過(guò)該事件進(jìn)行配置文件加載路由注冊(cè)。事件每個(gè)請(qǐng)求到來(lái)時(shí)僅僅會(huì)觸發(fā)事件。服務(wù)器生命周期和服務(wù)基本一致,詳情參考源碼剖析功能實(shí)現(xiàn) 作者:bromine鏈接:https://www.jianshu.com/p/4c0...來(lái)源:簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。S...

    張漢慶 評(píng)論0 收藏0
  • Swoole 在 Swoft 中的應(yīng)用

    摘要:在中的應(yīng)用官網(wǎng)源碼解讀號(hào)外號(hào)外歡迎大家我們開(kāi)發(fā)組定了一個(gè)就線下聚一次的小目標(biāo)上一篇源碼解讀反響還不錯(cuò)不少同學(xué)推薦再加一篇講解一下中使用到的功能幫助大家開(kāi)啟的實(shí)戰(zhàn)之旅服務(wù)器開(kāi)發(fā)涉及到的相關(guān)技術(shù)領(lǐng)域的知識(shí)非常多不日積月累打好基礎(chǔ)是很難真正 date: 2017-12-14 21:34:51title: swoole 在 swoft 中的應(yīng)用 swoft 官網(wǎng): https://www.sw...

    EscapedDog 評(píng)論0 收藏0
  • Swoft 源碼剖析 - Swoft 中 AOP 的實(shí)現(xiàn)原理

    摘要:官方在文檔沒(méi)有提供完整的但我們還是可以在單元測(cè)試中找得到的用法。解決的問(wèn)題是分散在引用各處的橫切關(guān)注點(diǎn)。橫切關(guān)注點(diǎn)指的是分布于應(yīng)用中多處的功能,譬如日志,事務(wù)和安全。通過(guò)將真正執(zhí)行操作的對(duì)象委托給實(shí)現(xiàn)了能提供許多功能。源碼剖析系列目錄 作者:bromine鏈接:https://www.jianshu.com/p/e13...來(lái)源:簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)...

    chenjiang3 評(píng)論0 收藏0
  • Swoft 源碼剖析 - Swoft 中 IOC 容器的實(shí)現(xiàn)原理

    摘要:作者鏈接來(lái)源簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。前言為應(yīng)用提供一個(gè)完整的容器作為依賴管理方案,是功能,模塊等功能的實(shí)現(xiàn)基礎(chǔ)。的依賴注入管理方案基于服務(wù)定位器。源碼剖析系列目錄 作者:bromine鏈接:https://www.jianshu.com/p/a23...來(lái)源:簡(jiǎn)書(shū)著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。Swof...

    Astrian 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<