深入理解Laravel如何管理和配置多資料庫連線的

pilishen發表於2019-01-31

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

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

深入理解Laravel如何管理和配置多資料庫連線的

深入理解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是誰,比如通過引數的形式.

相關文章