数据库读写分离(Master/Slave)的解决方法

icyleaf 2010-03-22 20:02:57

数据库复制后,如何操作读写分离(Master/Slave)的数据库?查找了一些关于 Kohana 的资料,发现官网在去年 10 月有人提到过这个问题和自己的解决方案,不过到现在还没有什么有用的答复....

难不成需要更山寨些,分别实例化两个 DB 类来负责读和写两部分么?下面是官方自己的山寨解决方案,但是由于 Database 类会先判断是否实例化了 connection 所以基本上还是没效果的,不过还是粘贴出来吧:

//classes/database/mysql.php
class Database_MySQL extends Kohana_Database_MySQL {
    public function query($type, $sql, $as_object)
    {
        $this->_instance = ($type === self::SELECT)?'default_readonly':'default';
        $this->_cdatabase')->{$this->_instance};


        return parent::query($type,$sql,$as_object);
    }


}

总结了很久发现上面的仁兄给出的解答是最佳答案,从基础上面解决问题。

首先需要对 Database class 进行改造,注意我是直接改的 Kohana 核心文件,如果大家不方便或谨慎起见请覆写它们的子类:

1. 添加一个类的属性: Database::$slaves
2. 对 instance() 改写,加载 Database 中的配置项
3. 增加一个随机选择 slave 的配置项

abstract class Kohana_Database {


	/**
	 * @var  array  slaves name
	 */
	public static $slaves = array();


	public static function instance($name = NULL, array $config = NULL)
	{
		if ($name === NULL)
		{
			// Use the default instance name
			$name = Database::$default;
		}


		if ( ! isset(Database::$instances[$name]))
		{
			if ($config === NULL)
			{
				// Load the configurations for all databases
				$config = Kohana::config('database');


				// Load the configuration for this database
				$default = $config->$name;
			}


			if ( ! Database::$slaves)
			{
				foreach($config as $conf_name => $conf_database)
				{
					if (strpos($conf_name, 'slave') !== FALSE)
					{
						Database::$slaves[] = $conf_name;
					}
				}
			}


			if ( ! isset($default['type']))
			{
				throw new Exception('Database type not defined in [ '.$name.' ] configuration');
			}


			// Set the driver class name
			$driver = 'Database_'.ucfirst($default['type']);


			// Create the database connection instance
			new $driver($name, $default);
		}


		return Database::$instances[$name];
	}


	public function select_slave()
	{
		// Get slave total
		$slaves_total = count(Database::$slaves);
		if ($slaves_total > 0)
		{
			// Get a random time
			$time = round(microtime(true), 2) * 100;
			$time = substr($time, strlen($time) - 2);


			// Generate slave index
			$handle = 100 / $slaves_total;
			$choosen_index = floor($time/$handle);


			// Get slave name
			return Database::$slaves[$choosen_index];
		}


		return Database::$default;
	}
}

4. 在 Kohana_Database_Mysql class 的 query 中判断执行 sql 的类型从而选择数据库源:

class Kohana_Database_MySQL extends Kohana_Database {


	...


	public function query($type, $sql, $as_object)
	{
		if ($type === Database::SELECT)
		{
			$name = $this->select_slave();
		}
		else
		{
			$name = Database::$default;
		}


		if ($this->_instance != $name)
		{
			$this->_connection = NULL;
			$this->_instance = $name;
			$this->_config = Kohana::config('database')->$name;
		}


		// 下面是原来的内容...
	}

5. Database 配置文件,对于 slave 的数据源都要以 'slave‘ 为开头,后面可以随意,比如: slave, slave1, slave2...

return array
(
	'default' => array
	(
		'type'       => 'mysql',
		'connection' => array(
			'hostname'   => 'localhost',
			'database'   => 'master',
			'username'   => 'root',
			'password'   => '',
			'persistent' => FALSE,
		),
		'table_prefix' => '',
		'charset'      => 'utf8',
		'caching'      => FALSE,
		'profiling'    => TRUE,
	),
	'slave' => array
	(
		'type'       => 'mysql',
		'connection' => array(
			'hostname'   => 'localhost',
			'database'   => 'slave01',
			'username'   => 'root',
			'password'   => '',
			'persistent' => FALSE,
		),
		'table_prefix' => '',
		'charset'      => 'utf8',
		'caching'      => FALSE,
		'profiling'    => TRUE,
	),
);

完工!

BWT, 这仅是对 Mysql 型驱动的 replication 支持,对于使用 PDO 的用户,只需要把上面 Kohana_Database_Mysql 中 query() 方法的代码照搬过去就成。

Post new reply

Post new reply after log in
Remember me (Lost Password)