Laravel多租户应用中的单个共享队列工作者

时间:2016-10-07 12:49:44

标签: php laravel laravel-5 architecture queue

我正在构建一个多租户Laravel应用程序(在Laravel 5.3上),它允许每个租户为任何支持的Laravel设置拥有自己的一组配置。这是通过使用我自己的实现覆盖默认的Laravel Application来实现的,该实现提供了自定义配置加载器(覆盖了默认的Illuminate\Foundation\Bootstrap\LoadConfiguration)。应用程序在引导程序中检测环境中的当前租户(PHP的$_ENV.env文件),然后为检测到的租户加载相应的配置文件。

上述方法适用于HTTP和控制台内核,其中每个请求/命令的生命周期都有限,但我不确定如何接近队列工作者。我希望为所有租户都有一个队列工作器,并且我已经实现了一个自定义队列连接器,以便在安排队列作业时添加其他元数据,以便在工作人员收到它时识别租户。

我正在寻求帮助的部分是如何在隔离环境中运行每个队列作业,我可以使用自定义配置进行引导。

我看到的一些可能的解决方案是:

  • 运行作为守护程序运行的自定义队列工作程序并从队列中获取作业,但在单独的PHP进程中执行作业(通过exec()创建);一旦作业执行,工作人员收集结果(状态,例外等)并完成父进程中的作业(例如删除作业等)

  • 与上述类似,但使用RunKit Sandbox

  • 在单独的PHP线程中运行作业而不是单独的进程
  • 实施一个解决方案,一旦收到队列作业,“重新启动”应用程序(例如,重新加载当前租户的配置,重置任何已解析的依赖关系等)

重要的是,我希望这个多租户作业执行对于作业本身是透明的,以便那些不适合在多租户环境中运行的作业(例如来自Laravel Scout等第三方软件包的作业) )无需任何修改即可处理。

有关如何处理此事的任何建议?

2 个答案:

答案 0 :(得分:10)

我们的情况几乎相同。这是我们的方法:

服务提供商

我们有一个名为BootTenantServiceProvider的ServiceProvider,可以在正常的HTTP /控制台请求中引导租户。它期望存在一个名为TENANT_ID的环境变量。有了它,它将加载所有适当的配置并设置一个特定的租户。

使用__sleep()和__wakeup()

的特征

我们将在队列作业中使用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
    }
}

与SerializesModels冲突

Laravel包含的SerializesModels特征提供了自己的__sleep__wakeup方法。我还没有弄明白如何使这两种特性协同工作,或者即使它是可能的。

现在我确保我从未在构造函数中提供完整的Eloquent模型。您可以在上面的示例作业中看到我只将ID存储为类属性,而不是完整模型。我有handle()方法在队列运行时期间获取模型。然后我根本不需要SerializesModels特征。

使用queue:listen而不是--daemon

您需要使用queue:listen代替queue:work --daemon来运行队列工作人员。前者为每个队列作业引导框架,后者将引导的框架保存在内存中。

至少,您需要这样做,假设您的租户启动过程需要全新的框架启动。如果你能够连续启动多个租户,干净地覆盖每个租户的配置,那么你可以轻而易举地使用queue:work --daemon

答案 1 :(得分:1)

要扩展@jszobody的答案,请参阅由多租户Laravel软件包构建的TenantAwareJob特性。

这完全可以满足您的需求,即在睡眠对租户进行编码之前,唤醒时会引导您的租户。

此特征也适用于SerializesModels,因此您可以传递模型。