Laravel 5 配置数据库主从读写分离和源码分析

转载 2017年02月05日 14:51:33

原文地址:Laravel5配置读写分离和源码分析


一,配置过程:

Laravel5读写分离配置比较简单,只需修改config/database.php,下面以MySQL数据库为例 内容如下 'mysql' => [

'read' => [
    'host' => '192.168.1.1'
],
'write' => [
    'host' => '196.168.1.2'
],
'driver'    => 'mysql',
'database'  => 'database',
'username'  => 'root',
'password'  => '',
'charset'   => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix'    => '',

]

设置完毕之后,Laravel5默认将select的语句让read指定的数据库执行,insert/update/delete则交给write指定的数据库,达到读写分离的作用。 这些设置对原始查询raw queries,查询生成器query builder,以及对象映射 Eloquent 都生效。 官网解释如下: Sometimes you may wish to use one database connection for SELECT statements, and another for INSERT, UPDATE, and DELETE statements. Laravel makes this a breeze, and the proper connections will always be used whether you are using raw queries, the query builder, or the Eloquent ORM

二,实现原理

Laravel5读写分离主要有两个过程: 第一步,根据database.php配置,创建写库和读库的链接connection 第二步,调用select时先判断使用读库还是写库,而insert/update/delete统一使用写库

三,源码分析:根据database.php配置,创建写库和读库的链接connection

主要文件:Illuminate/Database/Connectors/ConnectionFactory.php 来看看几个重要的函数:

1,判断database.php是否配置了读写分离数据库

/**
 * Establish a PDO connection based on the configuration.
 *
 * @param  array   $config
 * @param  string  $name
 * @return \Illuminate\Database\Connection
 */
public function make(array $config, $name = null)
{
    $config = $this->parseConfig($config, $name);
    // 如果配置了读写分离,则同时创建读库和写库的链接
    if (isset($config['read'])) {
        return $this->createReadWriteConnection($config);
    }
    // 如果没有配置,默认创建单个数据库链接
    return $this->createSingleConnection($config);
}

2,看看如何创建读库和写库的链接

/**
 * Create a single database connection instance.
 *
 * @param  array  $config
 * @return \Illuminate\Database\Connection
 */
protected function createReadWriteConnection(array $config)
{
    // 获取写库的配置信息,并创建链接
    $connection = $this->createSingleConnection($this->getWriteConfig($config));
    // 创建读库的链接
    return $connection->setReadPdo($this->createReadPdo($config));
}

3,多个读库会选择哪个呢

/**
 * Get the read configuration for a read / write connection.
 *
 * @param  array  $config
 * @return array
 */
protected function getReadConfig(array $config)
{
    $readConfig = $this->getReadWriteConfig($config, 'read');

    // 如果数组即多个读库,那么通过随机函数array_rand()挑一个,默认取第一个
    if (isset($readConfig['host']) && is_array($readConfig['host'])) {
        $readConfig['host'] = count($readConfig['host']) > 1
            ? $readConfig['host'][array_rand($readConfig['host'])]
            : $readConfig['host'][0];
    }
    return $this->mergeReadWriteConfig($config, $readConfig);
}

4,写库也是随机选择的

/**
 * Get a read / write level configuration.
 *
 * @param  array   $config
 * @param  string  $type
 * @return array
 */
protected function getReadWriteConfig(array $config, $type)
{

    // 如果多个,那么通过随机函数array_rand()挑一个
    if (isset($config[$type][0])) {
        return $config[$type][array_rand($config[$type])];
    }
    return $config[$type];
}

总结:

1,可以设置多个读库和多个写库,或者不同组合,比如一个写库两个读库

2,每次只创建一个读库链接和一个写库链接,从多个库中随机选择一个;

四,源码分析:调用select时先判断使用读库还是写库,而insert/update/delete统一使用写库

主要文件:Illuminate/Database/Connection.php 看看几个重要的函数

1,select函数根据第三个输入参数判断使用读库还是写库

 /**
 * Run a select statement against the database.
 *
 * @param  string  $query
 * @param  array  $bindings
 * @param  bool  $useReadPdo
 * @return array
 */
public function select($query, $bindings = [], $useReadPdo = true)
{
    return $this->run($query, $bindings, function ($me, $query, $bindings) use ($useReadPdo) {
        if ($me->pretending()) {
            return [];
        }

        // For select statements, we'll simply execute the query and return an array
        // of the database result set. Each element in the array will be a single
        // row from the database table, and will either be an array or objects.
        // 根据$useReadPdo参数,判断使用读库还是写库;
        // true使用读库,false使用写库;默认使用读库
        $statement = $this->getPdoForSelect($useReadPdo)->prepare($query);

        $statement->execute($me->prepareBindings($bindings));

        $fetchArgument = $me->getFetchArgument();

        return isset($fetchArgument) ?
            $statement->fetchAll($me->getFetchMode(), $fetchArgument, $me->getFetchConstructorArgument()) :
            $statement->fetchAll($me->getFetchMode());
    });
}

/**
 * Get the PDO connection to use for a select query.
 *
 * @param  bool  $useReadPdo
 * @return \PDO
 */
protected function getPdoForSelect($useReadPdo = true)
{
    // 根据$useReadPdo参数,选择PDO即判断使用读库还是写库;
    // true使用读库getReadPdo,false使用写库getPdo; 
   return $useReadPdo ? $this->getReadPdo() : $this->getPdo();
}

2, insert/update/delete统一使用写库

/**
 * Run an insert statement against the database.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return bool
 */
public function insert($query, $bindings = [])
{
    return $this->statement($query, $bindings);
}

/**
 * Run an update statement against the database.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return int
 */
public function update($query, $bindings = [])
{
    return $this->affectingStatement($query, $bindings);
}

/**
 * Run a delete statement against the database.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return int
 */
public function delete($query, $bindings = [])
{
    return $this->affectingStatement($query, $bindings);
}

/**
 * Execute an SQL statement and return the boolean result.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return bool
 */
public function statement($query, $bindings = [])
{
    return $this->run($query, $bindings, function ($me, $query, $bindings) {
        if ($me->pretending()) {
            return true;
        }

        $bindings = $me->prepareBindings($bindings);
        // 直接调用写库
        return $me->getPdo()->prepare($query)->execute($bindings);
    });
}

/**
 * Run an SQL statement and get the number of rows affected.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return int
 */
public function affectingStatement($query, $bindings = [])
{
    return $this->run($query, $bindings, function ($me, $query, $bindings) {
        if ($me->pretending()) {
            return 0;
        }

        // For update or delete statements, we want to get the number of rows affected
        // by the statement and return that back to the developer. We'll first need
        // to execute the statement and then we'll use PDO to fetch the affected.
        // 直接调用写库
        $statement = $me->getPdo()->prepare($query);

        $statement->execute($me->prepareBindings($bindings));

        return $statement->rowCount();
    });
}

总结:

1,getReadPdo()获得读库链接,getPdo()获得写库链接;

2,select()函数根据第三个参数判断使用读库还是写库;

五,强制使用写库

有时候,我们需要读写实时一致,写完数据库后,想马上读出来,那么读写都指定一个数据库即可。 虽然Laravel5配置了读写分离,但也提供了另外的方法强制读写使用同一个数据库。

实现原理:上面$this->select()时指定使用写库的链接,即第三个参数useReadPdo设置为false即可

有几个方法可实现 1,调用方法 DB::table('posts')->selectFromWriteConnection('*')->where('id', $id);

源码解释:通过selectFromWriteConnection()函数 主要文件:Illuminate/Database/Connection.php

/**
 * Run a select statement against the database.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return array
 */
public function selectFromWriteConnection($query, $bindings = [])
{
    ,
    // 上面有解释$this->select()函数的第三个参数useReadPdod的意义
    // 第三个参数是 false,所以 select 时会使用写库,而不是读库
    return $this->select($query, $bindings, false);
}

2,调用方法

User::onWriteConnection()->find($id);

源码解释:通过onWriteConnection()函数 主要文件:Illuminate/Database/Eloquent/Model

/**
 * Begin querying the model on the write connection.
 *
 * @return \Illuminate\Database\Query\Builder
 */
public static function onWriteConnection()
{
    $instance = new static;
    // query builder 指定使用写库
    return $instance->newQuery()->useWritePdo();
}

看看query builder如何指定使用写库 主要文件:Illuminate/Database/Query/Builder

/**
 * Use the write pdo for query.
 *
 * @return $this
 */
public function useWritePdo()
{
    // 指定使用写库,useWritePdo 为true
    $this->useWritePdo = true;

    return $this;
}

/**
 * Run the query as a "select" statement against the connection.
 *
 * @return array
 */
protected function runSelect()
{
    // 执行select时,useWritePdo原值为true,这里取反,被改成false;
    // 即$this->select()函数第三个参数为false,所以使用写库;
    return $this->connection->select($this->toSql(), $this->getBindings(), ! $this->useWritePdo);
}

相关文章推荐

【Laravel】一主多从读写分离配置

错误范例 最近要配置laravel的读写分离,找了半天资料都是类似下面的这种形式。 'mysql' => array( 'read' => array( 'host' => '...

Laravel数据库操作的三种方式

Laravel提供了3种操作数据库方式:DB facade(原始方式)、查询构造器和Eloquent ORM。下面我将逐一讲解。 数据库的配置文件在config目录下的database.php里。打...

Laravel数据库读写分离配置

Laravel数据库读写分离配置原文链接:http://blog.onlywan.cc/14847498744910.html配置范例'mysql' => [ 'driver' => 'm...

thinkphp框架实现mysql读写分离

基于thinkphp3.2.3,官方手册:http://www.kancloud.cn/manual/thinkphp/1733 注意事项: 1、首先得配置好mysql主从,可以多主多从 2、在读写分...
  • nuli888
  • nuli888
  • 2016年08月03日 20:33
  • 2871

辛星解读laravel源代码第一篇即Database的实现流程

经过一天左右的努力,终于把laravel的源代码整合到了yaf框架上面,下面说一下laravel的db层的一些处理吧,我使用的是laravel5.1.4,下面是database所在的位置:larave...

Laravel Eloquent ORM 一种高级用法

自己写得一种laravel Eloqument ORM 的组合查询方法,仅供参考,欢迎拍砖!

laravel数据库读写分离配置

转载地址:https://docs.golaravel.com/docs/5.4/database/
  • p_ython
  • p_ython
  • 2017年08月04日 19:41
  • 117

PHP 开发学习[7] —— 代码实现主从数据库 读写分离

背景:项目规划中,要求进行主从数据库的读写分离,其中对于主从数据库的创建和配置,网上有很充实的代码,这里仅在配置成功的基础上进行代码端的判断实现。 1.ThinkPHP框架中,已经集成了主从数据库...

nginx负载均衡的一些问题和具体配置

对于服务器集群的搭建,nginx负载均衡是很好的方式:思路如下图 这里的session采用了session共享的方式,这样来避免一些情况下的session丢失。下面我会具体说到。 sessio...

浏览器资源加载过程以及优化

当收到一个资源请求的时候将根据URL创建一个对应于要获得资源的加载器,然后开始对缓存资源进行响应。如果客户端对cached进行了禁用的话,那么将先清空这个资源在缓存中对应的数据,然后进行资源请求(re...
  • wmaoshu
  • wmaoshu
  • 2016年12月23日 20:16
  • 631
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Laravel 5 配置数据库主从读写分离和源码分析
举报原因:
原因补充:

(最多只允许输入30个字)