我正在构建一个多租户Laravel应用程序(在Laravel 5.3上),它允许每个租户为任何支持的Laravel设置拥有自己的一组配置。这是通过使用我自己的实现覆盖默认的Laravel Application
来实现的,该实现提供了自定义配置加载器(覆盖了默认的Illuminate\Foundation\Bootstrap\LoadConfiguration
)。应用程序在引导程序中检测环境中的当前租户(PHP的$_ENV
或.env
文件),然后为检测到的租户加载相应的配置文件。
上述方法适用于HTTP和控制台内核,其中每个请求/命令的生命周期都有限,但我不确定如何接近队列工作者。我希望为所有租户都有一个队列工作器,并且我已经实现了一个自定义队列连接器,以便在安排队列作业时添加其他元数据,以便在工作人员收到它时识别租户。
我正在寻求帮助的部分是如何在隔离环境中运行每个队列作业,我可以使用自定义配置进行引导。
我看到的一些可能的解决方案是:
运行作为守护程序运行的自定义队列工作程序并从队列中获取作业,但在单独的PHP进程中执行作业(通过exec()
创建);一旦作业执行,工作人员收集结果(状态,例外等)并完成父进程中的作业(例如删除作业等)
与上述类似,但使用RunKit Sandbox
实施一个解决方案,一旦收到队列作业,“重新启动”应用程序(例如,重新加载当前租户的配置,重置任何已解析的依赖关系等)
重要的是,我希望这个多租户作业执行对于作业本身是透明的,以便那些不适合在多租户环境中运行的作业(例如来自Laravel Scout等第三方软件包的作业) )无需任何修改即可处理。
有关如何处理此事的任何建议?
答案 0 :(得分:10)
我们的情况几乎相同。这是我们的方法:
我们有一个名为BootTenantServiceProvider
的ServiceProvider,可以在正常的HTTP /控制台请求中引导租户。它期望存在一个名为TENANT_ID
的环境变量。有了它,它将加载所有适当的配置并设置一个特定的租户。
我们将在队列作业中使用BootsTenant
特征,它看起来像这样:
trait BootsTenant
{
protected $tenantId;
/**
* Prepare the instance for serialization.
*
* @return array
*/
public function __sleep()
{
$this->tenantId = env('TENANT_ID');
return array_keys(get_object_vars($this));
}
/**
* Restore the ENV, and run the service provider
*/
public function __wakeup()
{
// We need to set the TENANT_ID env, and also force the BootTenantServiceProvider again
\Dotenv::makeMutable();
\Dotenv::setEnvironmentVariable('TENANT_ID', this->tenantId);
app()->register(BootTenantServiceProvider::class, [], true);
}
}
现在我们可以编写一个使用此特征的队列作业。在队列上序列化作业时,__sleep()
方法将在本地存储tenantId。当它被反序列化时,__wakeup()
方法将恢复环境变量并运行服务提供者。
我们的队列作业只需要使用这个特性:
class MyJob implements SelfHandling, ShouldQueue {
use BootsTenant;
protected $userId;
public function __construct($userId)
{
$this->userId = $userId;
}
public function handle()
{
// At this point the job has been unserialized from the queue,
// the trait __wakeup() method has restored the TENANT_ID
// and the service provider has set us all up!
$user = User::find($this->userId);
// Do something with $user
}
}
Laravel包含的SerializesModels
特征提供了自己的__sleep
和__wakeup
方法。我还没有弄明白如何使这两种特性协同工作,或者即使它是可能的。
现在我确保我从未在构造函数中提供完整的Eloquent模型。您可以在上面的示例作业中看到我只将ID存储为类属性,而不是完整模型。我有handle()
方法在队列运行时期间获取模型。然后我根本不需要SerializesModels
特征。
您需要使用queue:listen
代替queue:work --daemon
来运行队列工作人员。前者为每个队列作业引导框架,后者将引导的框架保存在内存中。
至少,您需要这样做,假设您的租户启动过程需要全新的框架启动。如果你能够连续启动多个租户,干净地覆盖每个租户的配置,那么你可以轻而易举地使用queue:work --daemon
。
答案 1 :(得分:1)
要扩展@jszobody的答案,请参阅由多租户Laravel软件包构建的TenantAwareJob特性。
这完全可以满足您的需求,即在睡眠对租户进行编码之前,唤醒时会引导您的租户。
此特征也适用于SerializesModels,因此您可以传递模型。