Один работник общей очереди в приложении Laravel multi-tenant

Я создаю многопользовательское приложение Laravel (на Laravel 5.3), которое позволяет каждому арендатору иметь свой собственный набор конфигураций для любых поддерживаемых настроек Laravel. В настоящее время это достигается путем переопределения значения по умолчанию Laravel Application с моей собственной реализацией, которая предоставляет пользовательский загрузчик конфигурации (переопределяет значение по умолчанию IlluminateFoundationBootstrapLoadConfiguration). Приложение обнаруживает текущего арендатора из среды (либо PHP $_ENV или .env file) при загрузке, а затем загружает соответствующие файлы конфигурации для обнаруженного клиента.

вышеуказанный подход отлично работает как для HTTP, так и для консольных ядер, где каждый запрос/команда имеет ограниченный жизненный цикл, но я не уверен, как подойти к работнику очереди. Я хотел бы иметь одного работника очереди для всех арендаторов, и я уже реализовал пользовательский соединитель очереди для добавления дополнительных метаданных при планировании задания очереди, чтобы можно было идентифицировать арендатора, когда работник получает он.

часть, на которой я ищу вашу помощь, - это как запустить каждое задание очереди в изолированной среде, которую я могу загрузить с помощью своей пользовательской конфигурации.

несколько возможных решений, которые я вижу такой:

  • для запуска пользовательского работника очереди, который работает как демон и получает задание из очереди, но выполняет задание в отдельном процессе PHP (созданном через exec()); после выполнения задания работник собирает результаты (статус, исключения и т. д.) и завершает задание в Родительском процессе (например, удаляет задание и т. д.)

  • аналогично приведенному выше, но запустите задание в отдельном потоке PHP вместо отдельного процесса, используя RunKit Sandbox

  • реализовать решение, которое "перезагружает" приложение после получения задания очереди (например, перезагружает конфигурации для текущего арендатора, сбрасывает все разрешенные зависимости и т. д.)

что важно я бы хотел, чтобы это выполнение задания с несколькими арендаторами было прозрачным для самого задания, чтобы задания, которые не предназначены для работы в среде с несколькими арендаторами (например, задания из сторонних пакетов, таких как Laravel Scout), можно было обрабатывать без каких-либо изменений.

какие-либо предложения о том, как подойти к этому?

1 ответов


у нас почти такая же ситуация. Вот наш подход:

Провайдер

у нас есть ServiceProvider называется BootTenantServiceProvider это загружает арендатора в обычный запрос HTTP / Console. Он ожидает, что переменная среды будет существовать с именем 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

в SerializesModels черта, которую включает Laravel, обеспечивает свою собственную __sleep и __wakeup методы. Я не совсем понял, как сделать, как черт, работать вместе, или даже если это возможно.

пока я уверен, что никогда не предоставляю полную красноречивую модель в конструкторе. Вы можете видеть в моем примере задания выше я храню только идентификаторы как атрибуты класса, никогда не полные модели. У меня есть handle() метод выборки моделей во время выполнения очереди. Тогда мне не нужно SerializesModels черта в все.

использовать очередь: слушайте вместо --daemon

вам нужно запустить своих работников очереди, используя queue:listen вместо queue:work --daemon. Первый загружает фреймворк для каждого задания очереди, последний сохраняет загруженную фреймворк в памяти.

по крайней мере, вам нужно сделать это, предполагая, что ваш процесс загрузки клиента нуждается в новой загрузке фреймворка. Если вы можете загружать несколько арендаторов последовательно, чисто перезаписывая конфигурации для каждого, то вы можете уходи с queue:work --daemon просто отлично.