深入理解Laravel如何管理和配置多数据库连接的

栏目: PHP · 发布时间: 5年前

内容简介:原文链接:在我们我们"邀请"了来自美国的大咖Tom给我们系统讲解了如何用Laravel来构建一个SAAS多租户平台

原文链接: www.pilishen.com/posts/under…

在我们 <<Laravel底层实战兼核心源码解析>> 这个课程的第九章:<Laravel 国际前沿实践探究>

深入理解 <a href='https://www.codercto.com/topics/5169.html'>Laravel</a> 如何管理和配置多数据库连接的
深入理解Laravel如何管理和配置多数据库连接的

我们"邀请"了来自美国的大咖Tom给我们系统讲解了如何用Laravel来构建一个SAAS多租户平台

深入理解Laravel如何管理和配置多数据库连接的
深入理解Laravel如何管理和配置多数据库连接的

这期间Tom系统讲解了什么是SAAS多租户平台,为什么你要使用这种架构,在构建SAAS平台时单数据库方案和多数据库方案之间有何优劣对比,该如何基于情况去选择,当然也探讨了SAAS平台下如何去处理队列\命令行\搜索,以及外部服务等,最后还介绍了几个构建SAAS时的优秀插件.

可以说Tom的专场,已经涵盖了用laravel搞SAAS平台的方方面面,相信看过的小伙伴对SAAS架构已经胸有成竹了.那么这篇文章呢,我们只是关注SAAS平台搭建中的一个方面,当然也是最令人困惑的一个方面,就是多个数据库之间的通信与切换,我们重点关注一下这一点,通过这一点来深入了解laravel背后处理数据库连接的方式和原理.至于SAAS架构其他的方面,如果你想搞,那么还是免不了要仔细研究Tom的专场.

大部分的应用,只有一个数据库,只需要跟单个数据库进行交互.但是啊,也有相当一部分laravel应用,需要处理多个数据库之间的交互.虽然这方面有一些不错的组件,但是深入理解一下laravel里数据库连接的原理,还是非常有帮助的.

创建数据库连接

{id="createConnection"}

当你在laravel里执行一个数据查询,是 Illuminate\Database\DatabaseManager 在具体负责设置好相应的数据库连接.在配置里,不同的数据库连接有不同的名字,你可以选一个作为默认的数据库连接.这样当你没有提供具体连接数据库的名字时,就可以用默认的那个.

// 这样用的是默认的连接
DB::table('users')->all();

// 这里声明了使用"tenant" 这个数据库(连接)
DB::connection('tenant')->table('users')->all();
复制代码

这个数据库连接在一个laravel生命或请求周期里,只会创建一次,也即是一个单例模式,这样整个期间,只用这一个数据库连接就可以了,既保证效率,又避免混乱.

PDO----PHP标准数据对象

{id="pdo"}

PDO是 PHP 里跟数据库进行交互时的一个标准接口,laravel也是使用了PDO来进行各种的数据查询.当然了,你也可以再配置个数据库连接,然后用它来进行独立的PDO读写逻辑,这样就相当于一个数据库是用来查询或读取的,而另一个数据库是专门用来执行写入\删除\更新等的逻辑.当然更多的,可以进一步查看 laravel读写分离的官方文档

大部分的"多租户"应用,都会给每个"租户"或机构单独设置一个数据库,然后再有一个总的\处于中央位置的数据库,这个数据库用来存储一些租户的整体细节信息.那么这样的话,在一个单一的应用里,你就会同时有一个"系统级"的数据库连接,然后还会有一个"租户"或机构级别的数据库连接.

'tenant' => [
  'driver' => 'mysql',
  'host' => env('DB_HOST', '127.0.0.1'),
  'port' => env('DB_PORT', '3306'),
  // ...
],

'system' => [
  'driver' => 'mysql',
  'host' => env('DB_HOST', '127.0.0.1'),
  'port' => env('DB_PORT', '3306'),
  // ...
],
复制代码

这个系统级别的\总的数据库连接,总是连到那同一个数据库,所以它在config文件里的具体配置是不变的,这个连接下的查询也很简单,都可以类似这样来进行:

DB::connection('system')->table('tenants')->all();
复制代码

但是当你要在一个租户的数据库上进行查询和连接的时候,就会有意思起来了.因为要具体连接到哪一个租户的数据库,取决于系统当前的租户是谁.因为没法提前知道这一点,所以我们也就不可能在 config/database.php 文件里具体设置好或者说"穷尽"租户的数据库连接.所以呢,租户或机构的数据库连接,就必须在运行中进行动态设置了.

config(['database.connections.tenant.database' => 'tenant1']);
复制代码

上面的这行代码,就会将 tenant 这个数据库连接的配置,具体指向到"tenant1" 这个数据库,用同样的方式,你也可以更改其他的数据库连接配置参数,比如username, password, read/write connections等等.

那么现在,当 DatabaseManager 想着创建tenant相应的连接时,就会用你刚才动态设置的配置项.但是呢,假设在这之前,这个tenant的数据库连接已经解析过一次了,也即里面具体的配置已经被laravel缓存(cache)了,那么这个时候新更改的设置就不会生效,也就不会创建一个新的数据库连接.

要解决这个问题啊,你得确保在设置新的数据库配置项之前,系统里没有其它的\已经解析过或生效了的数据库连接:

config(['database.connections.tenant.database' => 'tenant1']);

DB::purge('tenant');

DB::reconnect('tenant');
复制代码

使用purge()和reconnect()方法,可以确保在tenant这个连接通道上,接下来的任何新的数据查询,都会用上面这个最新设置的数据库信息.

当然了,这个地方我们只是关注数据库的重新连接,如果你看过我们的 <<Laravel底层实战兼核心源码解析>> 课程,看过Tom关于Laravel SAAS的专场,那么这个地方其实还可以搞一些其他必要的事情,比如设置 app.name ,设置 app.url ,同时触发一些有用的event什么的.虽然其他的细节不是我们这篇文章的关注点,但是也要提醒你不要限制想象力哦

这些代码具体要写在哪里呢?

{id="whereToPut"}

一个laravel程序,实际上有好几个"入口":

  1. http请求(HTTP Requests)
  2. 命令行(Console Commands)
  3. 队列任务(Queued Jobs)

我们可以创建一个 TenancyServiceProvider ,记得将其添加到 config/app.php 里,那么在其 register 方法里,我们就可以这样来动态设置当前tenant的数据库连接信息了:

public function register(){
    if($this->app->runningInConsole()){
        return;
    }

    if($request->getHttpHost() == 'tenant1.app.com'){
      config(['database.connections.tenant.database' => 'tenant1']);
    
      DB::purge('tenant');
    
      DB::reconnect('tenant');
     }
}
复制代码

检查当前http请求的host信息,然后基于此,来将数据库连接,设置成相应租户的.

至于队列job,我们可以把 tenant_id 信息存到所有job的相应payload里.这样当具体执行这个job时,就可以用之前的方式来动态修改数据库连接配置了.所以在service provider里,可以再添加这么一行:

$this->app['queue']->createPayloadUsing(function () {
      return Tenant::get() ? [
              'tenant_id' => Tenant::get()->id
             ] : [];
});
复制代码

这里的 Tenant::get() 是自己写的,用来判断或获取当前的tenant是哪个.这样的话,每一个job的payload里都会包含一个tenant_id的信息,这时我们就可以监听 JobProcessing 这个事件,然后来相应地配置数据库连接:

$this->app['events']->listen(\Illuminate\Queue\Events\JobProcessing::class, function($event){
    if (isset($event->job->payload()['tenant_id'])) {
        Tenant::set($event->job->payload()['tenant_id']);
    }
});
复制代码

至于命令行里,你得声明当前的tenant是谁,比如通过参数的形式.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Agile Web Development with Rails 4

Agile Web Development with Rails 4

Sam Ruby、Dave Thomas、David Heinemeier Hansson / Pragmatic Bookshelf / 2013-10-11 / USD 43.95

Ruby on Rails helps you produce high-quality, beautiful-looking web applications quickly. You concentrate on creating the application, and Rails takes care of the details. Tens of thousands of deve......一起来看看 《Agile Web Development with Rails 4》 这本书的介绍吧!

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试