ThinkPHP读写分离的内部实现
分析的版本是TP3.2.3
此版本已经支持mysql数据库读写分离,配置比较简单,具体可看分布式数据库支持
我们尝试分析下源码实现:
不妨先yy一下,在写操作和读操作时区分开数据库连接分别连接master和slave。
我们可以看见Model.class.php的db初始化
public function db($linkNum = , $config = , $force = false) {
if ( === $linkNum && $this->db) {
return $this->db;
}
if (! isset ( $this->_db [$linkNum] ) || $force) {
// 创建一个新的实例
if (! empty ( $config ) && is_string ( $config ) && false === strpos ( $config, / )) { // 支持读取配置参数
$config = C ( $config );
}
$this->_db [$linkNum] = Db::getInstance ( $config );
} elseif (NULL === $config) {
$this->_db [$linkNum]->close (); // 关闭数据库连接
unset ( $this->_db [$linkNum] );
return;
}
// 切换数据库连接
$this->db = $this->_db [$linkNum];
$this->_after_db ();
// 字段检测
if (! empty ( $this->name ) && $this->autoCheckFields)
$this->_checkTableInfo ();
return $this;
}
接着看DB单例,
static public function getInstance($config = array()) {
$md5 = md5 ( serialize ( $config ) );
if (! isset ( self::$instance [$md5] )) {
// 解析连接参数 支持数组和字符串
$options = self::parseConfig ( $config );
// 兼容mysqli
if (mysqli == $options [type])
$options [type] = mysql;
// 如果采用lite方式 仅支持原生SQL 包括query和execute方法
$class = $options [lite] ? ThinkDbLite : Think\Db\Driver\ . ucwords ( strtolower ( $options [type] ) );
if (class_exists ( $class )) {
self::$instance [$md5] = new $class ( $options );
} else {
// 类没有定义
E ( L ( _NO_DB_DRIVER_ ) . : . $class );
}
}
self::$_instance = self::$instance [$md5];
return self::$_instance;
}
接着看见mysql驱动继承了抽象类Driver。
不妨找两个Model操作读(find)和写(add):
public function find($options = array()) {
...
$resultSet = $this->db->select ( $options );
...
}
public function add($data = , $options = array(), $replace = false) {
...
$result = $this->db->insert ( $data, $options, $replace );
...
}
我们只聚焦关键代码select 和insert 。
然后去查看数据库驱动类代码:
public function select($options = array()) {
...
$result = $this->query ( $sql, ! empty ( $options [fetch_sql] ) ? true : false );
...
}
public function insert($data, $options = array(), $replace = false) {
...
return $this->execute ( $sql, ! empty ( $options [fetch_sql] ) ? true : false );
}
继续寻根,我们看见
/**
* 执行查询 返回数据集
*/
public function query($str, $fetchSql = false) {
$this->initConnect ( false );
...
}
/**
* 执行语句
*/
public function execute($str, $fetchSql = false) {
$this->initConnect ( true );
...
}
我们终于找到了根本,
/**
* 初始化数据库连接
*
* @access protected
* @param boolean $master
* 主服务器
* @return void
*/
protected function initConnect($master = true) {
if (! empty ( $this->config [deploy] ))
// 采用分布式数据库
$this->_linkID = $this->multiConnect ( $master );
else
// 默认单数据库
if (! $this->_linkID)
$this->_linkID = $this->connect ();
}
/**
* 连接分布式服务器
*
* @access protected
* @param boolean $master
* 主服务器
* @return void
*/
protected function multiConnect($master = false) {
// 分布式数据库配置解析
$_config [username] = explode ( ,, $this->config [username] );
$_config [password] = explode ( ,, $this->config [password] );
$_config [hostname] = explode ( ,, $this->config [hostname] );
$_config [hostport] = explode ( ,, $this->config [hostport] );
$_config [database] = explode ( ,, $this->config [database] );
$_config [dsn] = explode ( ,, $this->config [dsn] );
$_config [charset] = explode ( ,, $this->config [charset] );
$m = floor ( mt_rand ( 0, $this->config [master_num] - 1 ) );
// 数据库读写是否分离
if ($this->config [rw_separate]) {
// 主从式采用读写分离
if ($master)
// 主服务器写入
$r = $m;
else {
if (is_numeric ( $this->config [slave_no] )) { // 指定服务器读
$r = $this->config [slave_no];
} else {
// 读操作连接从服务器
$r = floor ( mt_rand ( $this->config [master_num], count ( $_config [hostname] ) - 1 ) ); // 每次随机连接的数据库
}
}
} else {
// 读写操作不区分服务器
$r = floor ( mt_rand ( 0, count ( $_config [hostname] ) - 1 ) ); // 每次随机连接的数据库
}
if ($m != $r) {
$db_master = array (
username => isset ( $_config [username] [$m] ) ? $_config [username] [$m] : $_config [username] [0],
password => isset ( $_config [password] [$m] ) ? $_config [password] [$m] : $_config [password] [0],
hostname => isset ( $_config [hostname] [$m] ) ? $_config [hostname] [$m] : $_config [hostname] [0],
hostport => isset ( $_config [hostport] [$m] ) ? $_config [hostport] [$m] : $_config [hostport] [0],
database => isset ( $_config [database] [$m] ) ? $_config [database] [$m] : $_config [database] [0],
dsn => isset ( $_config [dsn] [$m] ) ? $_config [dsn] [$m] : $_config [dsn] [0],
charset => isset ( $_config [charset] [$m] ) ? $_config [charset] [$m] : $_config [charset] [0]
);
}
$db_config = array (
username => isset ( $_config [username] [$r] ) ? $_config [username] [$r] : $_config [username] [0],
password => isset ( $_config [password] [$r] ) ? $_config [password] [$r] : $_config [password] [0],
hostname => isset ( $_config [hostname] [$r] ) ? $_config [hostname] [$r] : $_config [hostname] [0],
hostport => isset ( $_config [hostport] [$r] ) ? $_config [hostport] [$r] : $_config [hostport] [0],
database => isset ( $_config [database] [$r] ) ? $_config [database] [$r] : $_config [database] [0],
dsn => isset ( $_config [dsn] [$r] ) ? $_config [dsn] [$r] : $_config [dsn] [0],
charset => isset ( $_config [charset] [$r] ) ? $_config [charset] [$r] : $_config [charset] [0]
);
return $this->connect ( $db_config, $r, $r == $m ? false : $db_master );
}
其实就是通过传入initConnect来区分读写操作,并根据配置去连接操作。
今天分析就到这里了,其实与我们yy的基本一致。
ThinkPHP读写分离的内部实现
https://blog.puresai.com/2018/12/17/174/