webman可以動(dòng)態(tài)的鏈接數(shù)據(jù)庫(kù)嗎,我們的業(yè)務(wù)是每個(gè)客戶都是一個(gè)單獨(dú)的數(shù)據(jù)庫(kù),我們要批量對(duì)每個(gè)企業(yè)做個(gè)數(shù)據(jù)庫(kù)業(yè)務(wù)處理,每個(gè)企業(yè)都需要單獨(dú)鏈接到自己的數(shù)據(jù)庫(kù)上。這個(gè)webman有什么好的解決方案嗎。能不能就是動(dòng)態(tài)的來(lái)鏈接數(shù)據(jù)庫(kù),進(jìn)行操作處理。
我看到webman的數(shù)據(jù)庫(kù)是讀取數(shù)據(jù)庫(kù)配置文件來(lái)鏈接不同的數(shù)據(jù)同。我們有好幾千的企業(yè),不能都寫到配置文件里面。所以有什么好的解決方案沒(méi)有。配置文件那塊也不支持動(dòng)態(tài)的設(shè)置。
我的動(dòng)態(tài)實(shí)現(xiàn)方案你可以參考一下:
1. 設(shè)置一個(gè)base庫(kù) ,database.php配置中只配置 base庫(kù)的鏈接,企業(yè)客戶的數(shù)據(jù)庫(kù)連接信息配置到base庫(kù)的租戶表中。
2. 根據(jù)base庫(kù)中的配置 動(dòng)態(tài)生成 webman用到的 database配置
3.根據(jù)租戶域自動(dòng)切換租戶鏈接
4.所有業(yè)務(wù)數(shù)據(jù)庫(kù)操作使用model進(jìn)行操作,model中使用動(dòng)態(tài)鏈接即可
好的,我試下,我原先用的tp6或swoole,那塊和差不多。我們是動(dòng)態(tài)的改變config,然后來(lái)達(dá)到不同的企業(yè)用不同的數(shù)據(jù)庫(kù),和你這個(gè)邏輯也很像
我試了其他方式動(dòng)態(tài),都不理想。因?yàn)榭蚣軉?dòng)的時(shí)候依賴 db config ,要?jiǎng)討B(tài)生成所以開(kāi)始直接用PDO進(jìn)行連接
是的,都是依賴db config,你使用webman自帶的orm還是think-orm, webman只能model上進(jìn)行操作數(shù)據(jù)庫(kù),不能用tp的數(shù)據(jù)庫(kù)鏈接操作,
“ webman只能model上進(jìn)行操作數(shù)據(jù)庫(kù),不能用tp的數(shù)據(jù)庫(kù)鏈接操作“
這是哪來(lái)的說(shuō)法,用的本來(lái)就是tp的orm,怎么可能用法不一樣
我這里使用的是自帶的orm(Eloquent ORM)和think-orm差不多,沒(méi)有webman只能model上操作數(shù)據(jù)庫(kù)的說(shuō)法,Db 類一樣的可以操作數(shù)據(jù)庫(kù),我這里業(yè)務(wù)數(shù)據(jù)庫(kù)操作統(tǒng)一用model,是因?yàn)閯?dòng)態(tài)租戶數(shù)據(jù)庫(kù)我只支持到model這一層和個(gè)人習(xí)慣
確實(shí)是存在啟動(dòng)后新增的數(shù)據(jù)庫(kù)無(wú)法使用問(wèn)題,使用Illuminate\Database\Capsule\Manager::addConnection 應(yīng)該可以動(dòng)態(tài)添加
如果是這樣的話,直接在thinkorm中添加配置循環(huán),每次更改配置文件即可,在model里添加trait設(shè)置connection沒(méi)多大區(qū)別了,反而會(huì)更簡(jiǎn)單一些吧?
確實(shí)這個(gè)方案是在webman啟動(dòng)時(shí),把存在租戶表中的配置信息讀出來(lái),作為數(shù)據(jù)庫(kù)配置文件加載,然后自動(dòng)啟動(dòng)里的support\bootstrap\LaravelDb::class會(huì)addConnection進(jìn)去,如果動(dòng)態(tài)添加怎么做呢,請(qǐng)教一下!
那addConnection時(shí)要不要$capsule->setEventDispatcher,$capsule->setAsGlobal()和$capsule->bootEloquent()重新執(zhí)行一次呢,要不要像LaravelDb類里把 Paginator相關(guān)的加上?
是都要執(zhí)行一次的,但是我這邊應(yīng)用下來(lái) 動(dòng)態(tài)addConnection 上線后事件有問(wèn)題,目前還沒(méi)有搞清楚是什么原因?qū)е率录?/p>
動(dòng)態(tài)添加時(shí)也參照support\bootstrap\LaravelDb::class一樣,加一個(gè)定時(shí)任務(wù)的心跳呢,這樣會(huì)復(fù)用這個(gè)mysql連接吧
webman在啟動(dòng)時(shí),support\bootstrap\LaravelDb::class已經(jīng)setEventDispatcher,setAsGlobal(),bootEloquent()添加Paginator相關(guān)了,動(dòng)態(tài)addConnection時(shí),你剛回復(fù)都需要執(zhí)行一次,我覺(jué)得前面已經(jīng)setEventDispatcher,setAsGlobal(),bootEloquent()和添加Paginator相關(guān),這些做過(guò)的動(dòng)作不需要再做了吧,只在addConnection后,再給這個(gè)新的connection加一個(gè)定時(shí)任務(wù)的心跳即可。
可以實(shí)驗(yàn)一下看看 我現(xiàn)在沒(méi)有找到有效的本地測(cè)試方式,沒(méi)法在本地復(fù)現(xiàn)上面提到的問(wèn)題
使用中間件結(jié)合模型連接器即可解決,線上穩(wěn)定運(yùn)行一年
謝謝,是不是https://github.com/Tinywan/webman-admin這個(gè),目前里面的sql好像只有兩個(gè)表的,我想了解的是webman已經(jīng)啟動(dòng)后,比如又來(lái)了一個(gè)新的租戶,這個(gè)新的租戶對(duì)應(yīng)一個(gè)新的數(shù)據(jù)庫(kù)鏈接,就會(huì)有啟動(dòng)后新加一租戶數(shù)據(jù)庫(kù)無(wú)法連接的問(wèn)題。
可以采用動(dòng)態(tài)加載config配置文件就可以了,例如:
namespace plugin\SHGminiApi\app\model;
use DateTimeInterface;
use support\Model;
class Base extends Model
{
/* 數(shù)據(jù)庫(kù)配置文件 /
protected $connection = '';
/**
* 初始化架構(gòu)造函數(shù)
*/
public function __construct()
{
$config = request()->config;
$sandbox = $config['sandbox'] ?? false;
if ($sandbox) {
$this->connection = 'plugin.SHGminiApi.sandbox';
} else {
$this->connection = 'plugin.SHGminiApi.mysql';
}
}
/**
* 格式化日期
* @param DateTimeInterface $date
* @return string
*/
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d H:i:s');
}
}
注意,我在中間件里面加了請(qǐng)求是否為sandbox模式,如果是sandbox模式,加載sandbox數(shù)庫(kù)config配置文件
我有同樣的需求,自己寫的下面的方法實(shí)現(xiàn)了,動(dòng)態(tài)數(shù)據(jù)庫(kù)鏈接
<?php
namespace Yesgooo\DynamicDatabaseManager\DataBaseManager;
use Illuminate\Container\Container as IlluminateContainer;
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Events\Dispatcher;
class DynamicDataBaseManager
{
protected $capsule;
public function __construct($config = [])
{
$method = config('plugin.yesgooo.dynamic-database-manager.conf.method') ?? 'header';
//從請(qǐng)求頭部獲取當(dāng)前租戶標(biāo)識(shí),默認(rèn)0
$flag = \request()->$method(config('plugin.yesgooo.dynamic-database-manager.conf.flag'), 0);
//當(dāng)前公司數(shù)據(jù)庫(kù)名稱,如未傳公司ID取默認(rèn)配置數(shù)據(jù)庫(kù)名
$database = $flag ? config('plugin.yesgooo.dynamic-database-manager.conf.database_prefix').$flag : config('plugin.yesgooo.dynamic-database-manager.conf.default_database');
//當(dāng)前的數(shù)據(jù)庫(kù)連接名稱
$curConnect = $flag ? config('plugin.yesgooo.dynamic-database-manager.conf.conn_prefix') . $flag : 'default';
$config = $config ?: [
'driver' => 'mysql',
'host' => config('plugin.yesgooo.dynamic-database-manager.conf.host'),
'port' => config('plugin.yesgooo.dynamic-database-manager.conf.port'),
'database' => $database,
'username' => config('plugin.yesgooo.dynamic-database-manager.conf.username'),
'password' => config('plugin.yesgooo.dynamic-database-manager.conf.password'),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
];
$this->capsule = new Capsule(IlluminateContainer::getInstance());
$defaultConnect = $this->capsule->getDatabaseManager()->getDefaultConnection();
//通過(guò)容器獲取全部數(shù)據(jù)庫(kù)連接
$allConnect = $this->capsule->getContainer()['config']['database.connections'];
//如果當(dāng)前連接還沒(méi)有,則新增連接
if(!in_array($curConnect, array_keys($allConnect))){
$this->capsule->addConnection($config, $curConnect); // TODO: Change the autogenerated stub
}
//如果默認(rèn)連接不是當(dāng)前連接,則設(shè)置成默認(rèn)連接
if($defaultConnect != $curConnect){
//設(shè)置默認(rèn)連接
$this->capsule->getDatabaseManager()->setDefaultConnection($curConnect);
}
if (class_exists(Dispatcher::class) && !$this->capsule->getEventDispatcher()) {
$this->capsule->setEventDispatcher(\support\Container::make(Dispatcher::class, [IlluminateContainer::getInstance()]));
}
$this->capsule->setAsGlobal();
$this->capsule->bootEloquent();
}
}
然后實(shí)現(xiàn)個(gè)中間件,最后請(qǐng)求都走中間件先生成數(shù)據(jù)庫(kù)連接
<?php
namespace Yesgooo\DynamicDatabaseManager\middleware;
use Yesgooo\DynamicDatabaseManager\DataBaseManager\DynamicDataBaseManager;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class DynamicDataBase implements MiddlewareInterface
{
public function process(Request $request, callable $handler) : Response
{
//執(zhí)行動(dòng)態(tài)數(shù)據(jù)庫(kù)管理
new DynamicDataBaseManager();
return $handler($request);
}
}
這樣基本實(shí)現(xiàn)了需求,但最近發(fā)現(xiàn)個(gè)問(wèn)題mysql連接好像不會(huì)釋放,會(huì)超限,問(wèn)題還沒(méi)找到
webman啟動(dòng)時(shí)support\bootstrap\LaravelDb::class會(huì)有一個(gè)啟動(dòng)配置文件database.php的myql,每次在中間件里add時(shí),先前默認(rèn)鏈接沒(méi)有關(guān)閉就被重置了,可以試試Db::connection('xxx')->disconnect();關(guān)閉之前的mysql鏈接,再重置當(dāng)前默認(rèn)的。我的理解是這樣,不知道對(duì)不對(duì)。
數(shù)據(jù)庫(kù)配置連接
$connections = [
'tenant_1' => [
],
'tenant_2' => [
]
];
開(kāi)啟多進(jìn)程后,每個(gè)進(jìn)程都有可能維護(hù)每個(gè)租戶的數(shù)據(jù)庫(kù)連接。理論上,數(shù)據(jù)庫(kù)連接數(shù) = 進(jìn)程數(shù) * 租戶數(shù)量;
這樣是不是就造成數(shù)據(jù)庫(kù)連接浪費(fèi)呢,可能的解決辦法:
這種方案是否可行?