原文連結:www.pilishen.com/posts/under…
在我們<<Laravel底層實戰兼核心原始碼解析>>這個課程的第九章:<Laravel 國際前沿實踐探究>
我們"邀請"了來自美國的大咖Tom給我們系統講解了如何用Laravel來構建一個SAAS多租戶平臺
這期間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程式,實際上有好幾個"入口":
- http請求(HTTP Requests)
- 命令列(Console Commands)
- 佇列任務(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是誰,比如通過引數的形式.