使用Zend_Db进行读/写拆分

时间:2011-05-30 11:23:56

标签: php mysql zend-framework replication

我有一个PHP应用程序,其规模已经扩大。数据库曾经在单个主服务器上,但我们打算通过相当标准的主/从复制来改变性能和HA。

由于这个应用程序读取很重,我希望将读取委托给从属副本并写入主服务器。

该应用程序基于Zend Framework 1.1.10并使用Zend_Db。

使用此应用程序将读取和写入分配到数据库而不重构代码的最佳策略是什么? (我意识到这里可能会涉及一些重构)。

P.S:

我查看了MySQL Proxy,它似乎可以通过坐在数据库服务器和应用程序之间透明地分割读写,但我不确定在生产环境中使用它的性能问题。有没有人有这方面的经验?

2 个答案:

答案 0 :(得分:3)

正如你所说,MySQlProxy可以解决方案,但我个人从未在生产中测试过它。

我在代码中使用2个Db连接来分割写入和读取请求。 80%的常规任务都是通过读连接完成的。您可以使用Zend_Application_Resource_Multidb来处理(对我而言,我很久以前就完成了这个部分,我只是在注册表中存储了第二个Db连接)。

  • 首先限制您的用户权限 读操作并创建另一个db 具有写入授权的用户。
  • 然后跟踪每个写请求 你的代码(“更新”,“插入”, “删除”是一个好的开始)并尝试 用专用的方式拨打所有这些电话 帮手。
  • 运行您的应用并观看它崩溃,然后解决问题: - )

一开始就想到这个问题会更容易。例如:

  • 我通常有一个Zend_Db_Table工厂,带一个'read'或'write'参数,给我一个正确的Zend_Db_Table的Singleton(一个双单例,我可以有一个读实例和一个写实例)。然后我只需要确保在使用写访问查询/操作时使用正确的初始化Zend_Db_Table。请注意,使用Zend_Db_Table作为单例时,内存使用情况要好得多。
  • 我尝试在TransactionHandler中获取所有写操作。我在那里我可以检查我只使用与正确连接链接的对象。然后在控制器上管理事务,我从不尝试管理数据库层中的事务,所有启动/提交/回滚思想都是在控制器(或其他概念层,但不是DAO层)上完成的。

最后一点,交易,很重要。如果您想要管理交易,请务必使用 WRITE启用的连接,使事务中的READ请求。因为在事务之前完成的所有读取都应该被认为是过时的,并且如果数据库后端正在执行implicits锁定,则必须发出读取请求以获取锁定。如果您的数据库后端没有进行隐式读取,那么您还必须在事务中执行行锁。 这意味着您不应该依赖SELECT关键字将该请求推送到只读连接。

如果你的应用程序中有一个很好的数据库层使用,那么改变并不是很难。如果你用数据库/ DAO层制造了混乱的东西,那么......可能会更难。

答案 1 :(得分:2)

H 2。 Zend的

我刚刚修补了Zend PDO_MYSQL以分离读写连接。 为此,您只需在应用程序配置中指定其他参数:

'databases' => array (
    'gtf' => array(
        'adapter' => 'PDO_MYSQL',
        'params' => array(
            'host' => 'read.com',
            'host_write' => 'write-database-host.com',
            'dbname' => 'database',
            'username' => 'reader',
            'password' => 'reader',
            'username_write' => 'writer',
            'password_write' => 'writer',
            'charset' => 'utf8'
        )
    ),

此处所有“SELECT ...”查询都将使用主机。 所有其他查询将使用* host_write *。 如果未指定 host_write ,则所有查询都使用主机

修补程序:

diff --git a/Modules/Tools/Externals/Zend/Db/Adapter/Abstract.php b/Modules/Tools/Externals/Zend/Db/Adapter/Abstract.php
index 5ed3283..d6fccd6 100644
--- a/Modules/Tools/Externals/Zend/Db/Adapter/Abstract.php
+++ b/Modules/Tools/Externals/Zend/Db/Adapter/Abstract.php
@@ -85,6 +85,14 @@ abstract class Zend_Db_Adapter_Abstract
      * @var object|resource|null
      */
     protected $_connection = null;
+    
+    
+    /**
+     * Database connection
+     *
+     * @var object|resource|null
+     */
+    protected $_connection_write = null;

     /**
      * Specifies the case of column names retrieved in queries
@@ -299,10 +307,13 @@ abstract class Zend_Db_Adapter_Abstract
      *
      * @return object|resource|null
      */
-    public function getConnection()
+    public function getConnection($read_only_connection = true)
     {
         $this->_connect();
-        return $this->_connection;
+        if (!$read_only_connection && $this->_connection_write)
+            return $this->_connection_write;
+        else
+            return $this->_connection;
     }

     /**
diff --git a/Modules/Tools/Externals/Zend/Db/Adapter/Pdo/Abstract.php b/Modules/Tools/Externals/Zend/Db/Adapter/Pdo/Abstract.php
index d7f6d8a..ee63c59 100644
--- a/Modules/Tools/Externals/Zend/Db/Adapter/Pdo/Abstract.php
+++ b/Modules/Tools/Externals/Zend/Db/Adapter/Pdo/Abstract.php
@@ -57,7 +57,7 @@ abstract class Zend_Db_Adapter_Pdo_Abstract extends Zend_Db_Adapter_Abstract
      *
      * @return string
      */
-    protected function _dsn()
+    protected function _dsn($write_mode = false)
     {
         // baseline of DSN parts
         $dsn = $this->_config;
@@ -65,10 +65,15 @@ abstract class Zend_Db_Adapter_Pdo_Abstract extends Zend_Db_Adapter_Abstract
         // don't pass the username, password, charset, persistent and driver_options in the DSN
         unset($dsn['username']);
         unset($dsn['password']);
+        unset($dsn['username_write']);
+        unset($dsn['password_write']);
         unset($dsn['options']);
         unset($dsn['charset']);
         unset($dsn['persistent']);
         unset($dsn['driver_options']);
+        
+        if ($write_mode) $dsn['host'] = $dsn['host_write'];
+        unset($dsn['host_write']);

         // use all remaining parts in the DSN
         foreach ($dsn as $key => $val) {
@@ -91,9 +96,6 @@ abstract class Zend_Db_Adapter_Pdo_Abstract extends Zend_Db_Adapter_Abstract
             return;
         }

         // get the dsn first, because some adapters alter the $_pdoType
         $dsn = $this->_dsn();
+        if ($this->_config['host_write'])
+            $dsn_write = $this->_dsn(true);

         // check for PDO extension
         if (!extension_loaded('pdo')) {
             /**
@@ -120,14 +122,28 @@ abstract class Zend_Db_Adapter_Pdo_Abstract extends Zend_Db_Adapter_Abstract
             $this->_config['driver_options'][PDO::ATTR_PERSISTENT] = true;
         }

         try {
             $this->_connection = new PDO(
-                $dsn,
+                $dsn_read,
                 $this->_config['username'],
                 $this->_config['password'],
                 $this->_config['driver_options']
             );

+            if ($this->_config['host_write']) {
+                $this->_connection_write = new PDO(
+                    $dsn_write,
+                    $this->_config['username_write'],
+                    $this->_config['password_write'],
+                    $this->_config['driver_options']
+                );
+            }
+            
             $this->_profiler->queryEnd($q);

             // set the PDO connection to perform case-folding on array keys, or not
diff --git a/Modules/Tools/Externals/Zend/Db/Statement/Pdo.php b/Modules/Tools/Externals/Zend/Db/Statement/Pdo.php
index 8bd9f98..4ab81bf 100644
--- a/Modules/Tools/Externals/Zend/Db/Statement/Pdo.php
+++ b/Modules/Tools/Externals/Zend/Db/Statement/Pdo.php
@@ -61,8 +61,11 @@ class Zend_Db_Statement_Pdo extends Zend_Db_Statement implements IteratorAggrega
      */
     protected function _prepare($sql)
     {
+        
+        $read_only_connection = preg_match("/^select/i", $sql);
+        
         try {
-            $this->_stmt = $this->_adapter->getConnection()->prepare($sql);
+            $this->_stmt = $this->_adapter->getConnection($read_only_connection)->prepare($sql);
         } catch (PDOException $e) {
             require_once 'Zend/Db/Statement/Exception.php';
             throw new Zend_Db_Statement_Exception($e->getMessage());