Phalcon框架數據庫讀寫分離的實現方法

Phalcon框架和Yaf相似,是一款用C實現的拓展級別的框架,不過其功能實現更加豐富,設計思路基於依賴注入、容器等方式,更符合現代框架思想。本文主要針對Phalcon框架數據庫層的讀寫分離進行說明,權當記錄。php

前提準備

既然須要主從分離,那麼數據庫鏈接至少得有兩個,即將主庫和從庫分別做爲服務註冊到di容器,以下html

// app/config/services.php
use Phalcon\Db\Adapter\Pdo\Mysql;

// 設置主庫
$di->setShared('dbMaster', function () {
    $params = [
        'host'     => '127.0.0.1',
        'username' => 'root',
        'password' => 'root',
        'dbname'   => 'phalcon_test',
        'charset'  => 'utf8',
        'port'     => 3306,
    ];

    return new Mysql($params);
});

  

 // 設置從庫 本地測試 和主庫用一個mysql實例便可 $di->setShared('dbSlave', function () { $params = [ 'host' => '127.0.0.1', 'username' => 'root', 'password' => 'root', 'dbname' => 'phalcon_test', 'charset' => 'utf8', 'port' => 3306, ]; return new Mysql($params); }); 

實現方法一

基於Model初始化時的讀寫分離設置mysql

Model配置以下,例子是寫在每一個Model的initialize方法中,固然你也能夠統一寫到BaseModel中,而後每一個Model繼承便可。git

// app/models/Users.php
use Phalcon\Mvc\Model;

class Users extends Model
{
    /**
     * 設置Model主從切換
     */
    public function initialize()
    {
        // 寫操做 使用dbMaster鏈接
        $this->setWriteConnectionService('dbMaster');
        // 讀操做 使用dbSlave鏈接
        $this->setReadConnectionService('dbSlave');
    }

    /**
     * 表名
     */
    public function getSource()
    {
        return 'table_xxx';
    }
}

  

實現方法二

基於modelsManager實現的讀寫分離github

一、首先須要自定義modelsManager實現,用於替換框架自帶的Managersql

// app/library/ModelsManager.php
use Phalcon\Mvc\Model\Manager;

class ModelsManager extends Manager
{
    /**
     * 讀操做 返回dbSlave
     *
     * @param \Phalcon\Mvc\ModelInterface $model
     *
     * @return \Phalcon\Db\Adapter\Pdo\Mysql
     */
    public function getReadConnection(\Phalcon\Mvc\ModelInterface $model)
    {
        return $this->getDI()->get('dbSlave');
    }

    /**
     * 寫操做 返回dbMaster
     *
     * @param \Phalcon\Mvc\ModelInterface $model
     *
     * @return \Phalcon\Db\Adapter\Pdo\Mysql
     */
    public function getWriteConnection(\Phalcon\Mvc\ModelInterface $model)
    {
        return $this->getDI()->get('dbMaster');
    }
}

  

二、將自定義modelsManage註冊到di容器數據庫

// app/config/services.php

use app/library/ModelsManager;

/**
 * 設置自定義modelsManager
 */
$di->setShared(
    'modelsManager',
    new ModelsManager()
);

  

實現效果

按照上述方法實現後,你所用到的Phalcon實現的SQL查詢都可自動實現主從切換,包括QueryQueryBuilderARPHQL等。mvc

固然,若是你某些情境下須要強制指定主庫或者從庫操做怎麼辦,以下便可:app

// di容器
$di = \Phalcon\Di\FactoryDefault::getDefault();

// 獲取主庫連接直接操做
$di->get('dbMaster')->execute('update table_xxx set age = 100 where id = 1');

// 獲取從庫連接直接操做
$di->get('dbSlave')->query('select * from table_xxx');

  

如何驗證

空口無憑,我怎麼知道全部的讀操做都發生在了從庫上,全部的寫操做都發生在了主庫上;別擔憂,每次查詢的時候寫個日誌記錄一下不就完了。框架

須要更改最上面註冊dbMasterdbSlave時的邏輯,以下:

// app/config/services.php

use Phalcon\Db\Adapter\Pdo\Mysql;

// 設置主庫
$di->setShared('dbMaster', function () {
    $params = [
        'host'     => '127.0.0.1',
        'username' => 'root',
        'password' => 'root',
        'dbname'   => 'phalcon_test',
        'charset'  => 'utf8',
        'port'     => 3306,
    ];

    $connection = new Mysql($params);

    // 記錄每次sql查詢日誌
    $logger        = new \Phalcon\Logger\Adapter\File('/tmp/phalcon_sqls.log');
    $eventsManager = new \Phalcon\Events\Manager();

    // 註冊記錄sql的event
    $eventsManager->attach('db', function ($event, $connection) use ($logger) {
        ($event->getType() == 'beforeQuery') && $logger->log('Master: ' . $connection->getRealSQLStatement());
    });

    // 設置事件管理器
    $connection->setEventsManager($eventsManager);

    return $connection;
});

// 設置從庫 本地測試 和主庫用一個mysql實例便可
$di->setShared('dbSlave', function () {
    $params = [
            'host'     => '127.0.0.1',
            'username' => 'root',
            'password' => 'root',
            'dbname'   => 'phalcon_test',
            'charset'  => 'utf8',
            'port'     => 3306,
        ];

        $connection = new Mysql($params);

        // 記錄每次sql查詢日誌
        $logger        = new \Phalcon\Logger\Adapter\File('/tmp/phalcon_sqls.log');
        $eventsManager = new \Phalcon\Events\Manager();

        // 註冊記錄sql的event
        $eventsManager->attach('db', function ($event, $connection) use ($logger) {
            ($event->getType() == 'beforeQuery') && $logger->log('Slave: ' . $connection->getRealSQLStatement());
        });

        // 設置事件管理器
        $connection->setEventsManager($eventsManager);

        return $connection;
});

  

完成以後查看/tmp/phalcon_sqls.log日誌,以下,即表明成功進行了分離

[Wed, 16 May 18 11:18:43 +0800][DEBUG] Slave: SELECT `table_xxx`.`id` FROM `table_xxx`
[Wed, 16 May 18 11:18:43 +0800][DEBUG] Master: UPDATE `table_xxx` SET `name` = 'xxx'

  

優劣比較

我的以爲兩種方法實現各有優劣,須要結合本身的業務進行選取.

  • 使用方法一時,能夠單獨設置每一個Model使用的db連接,適用於多個庫中同名表操做的不一樣情境,更加靈活。
  • 使用方法二時比較固定,直接使用Manager返回固定db連接,再也不作其餘處理邏輯,比較死板,但性能會高於方法一。

建議若是隻是爲了使用主從切換功能的話,方法二會更適合,性能會更高,畢竟實際業務可能沒有那麼多切換庫的邏輯。

寫在最後

上述兩種方法能夠同時存在,但優先級是方法二 > 方法一,即modelsManager的優先級高於Model中定義。本質緣由是由於Model中執行SQL查詢時,也會向modelsManager索取db實例,框架源代碼文件以下

// phalcon/mvc/model.zep

/**
 * Gets the connection used to read data for the model
 */
public function getReadConnection() -> <AdapterInterface>
{
    var transaction;

    let transaction = <TransactionInterface> this->_transaction;
    if typeof transaction == "object" {
        return transaction->getConnection();
    }

    return (<ManagerInterface> this->_modelsManager)->getReadConnection(this);
}

/**
 * Gets the connection used to write data to the model
 */
public function getWriteConnection() -> <AdapterInterface>
{
    var transaction;

    let transaction = <TransactionInterface> this->_transaction;
    if typeof transaction == "object" {
        return transaction->getConnection();
    }

    return (<ManagerInterface> this->_modelsManager)->getWriteConnection(this);
}

  

能夠看出讀寫Model進行查詢時db實例的獲取會經過modelsManager的getReadConnectiongetWriteConnection方法實現,默認狀況下Manager會返回Model註冊時的實例,即方法一的實現,但若是同時使用了方法二,會直接覆蓋本來Manager的實現,不會再使用Model中註冊的邏輯。

 

來自:http://www.cnblogs.com/cheng6018/p/9341823.html

相關文章
相關標籤/搜索