深刻理解Laravel如何管理和配置多數據庫鏈接的

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

在咱們<<Laravel底層實戰兼核心源碼解析>>這個課程的第九章:<Laravel 國際前沿實踐探究>mysql

咱們"邀請"了來自美國的大咖Tom給咱們系統講解了如何用Laravel來構建一個SAAS多租戶平臺laravel

這期間Tom系統講解了什麼是SAAS多租戶平臺,爲何你要使用這種架構,在構建SAAS平臺時單數據庫方案和多數據庫方案之間有何優劣對比,該如何基於狀況去選擇,固然也探討了SAAS平臺下如何去處理隊列\命令行\搜索,以及外部服務等,最後還介紹了幾個構建SAAS時的優秀插件.sql

能夠說Tom的專場,已經涵蓋了用laravel搞SAAS平臺的方方面面,相信看過的小夥伴對SAAS架構已經成竹在胸了.那麼這篇文章呢,咱們只是關注SAAS平臺搭建中的一個方面,固然也是最使人困惑的一個方面,就是多個數據庫之間的通訊與切換,咱們重點關注一下這一點,經過這一點來深刻了解laravel背後處理數據庫鏈接的方式和原理.至於SAAS架構其餘的方面,若是你想搞,那麼仍是免不了要仔細研究Tom的專場.數據庫

大部分的應用,只有一個數據庫,只須要跟單個數據庫進行交互.可是啊,也有至關一部分laravel應用,須要處理多個數據庫之間的交互.雖然這方面有一些不錯的組件,可是深刻理解一下laravel裏數據庫鏈接的原理,仍是很是有幫助的.緩存

建立數據庫鏈接

{id="createConnection"}bash

當你在laravel裏執行一個數據查詢,是Illuminate\Database\DatabaseManager在具體負責設置好相應的數據庫鏈接.在配置裏,不一樣的數據庫鏈接有不一樣的名字,你能夠選一個做爲默認的數據庫鏈接.這樣當你沒有提供具體鏈接數據庫的名字時,就能夠用默認的那個.架構

// 這樣用的是默認的鏈接
DB::table('users')->all();

// 這裏聲明瞭使用"tenant" 這個數據庫(鏈接)
DB::connection('tenant')->table('users')->all();
複製代碼

這個數據庫鏈接在一個laravel生命或請求週期裏,只會建立一次,也便是一個單例模式,這樣整個期間,只用這一個數據庫鏈接就能夠了,既保證效率,又避免混亂.app

PDO----PHP標準數據對象

{id="pdo"}ide

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是誰,好比經過參數的形式.

相關文章
相關標籤/搜索