Intereting Posts

Laravel: система очереди Synchronisch

Я пытаюсь настроить API, который использует систему очередей на другом сервере для обработки запросов. Позвольте мне начать то, что я пытаюсь выполнить без системы очередей (нет разрешения на ее простоту): используя Postman, например, делая запрос GET на URL-адрес https://example.com/products , возвращается строка JSON, например

[ { "id": 1, "name": "some name", ... }, { "id": 2, "name": "some other name", ... }. ... ] 

Код в маршрутах / api.php будет примерно таким:

 <php Route::get('/products', ProductController@index'); 

И код в приложении / Http / Controllers / ProductController.php:

 <?php namespace App\Http\Controllers; class ProductController extends Controller { /** * Return the products. * * @return \Illuminate\Http\Response */ public function index() { // Logic to get the products. return $products->toJson(); } } 

Я бы хотел, чтобы вся бизнес-логика обрабатывалась на другом сервере, на котором работает несколько сотрудников. Ниже следует мое рассуждение.

  • Безопасность. В случае, если мы будем взломаны, скорее всего это будет клиентский сервер, а не рабочий сервер. Поскольку последний имеет всю бизнес-логику, хакер, в худшем случае, сможет получать только входящие и исходящие запросы.
  • Множество рабочих: получение продуктов, вероятно, не займет много времени, но могут быть другие запросы, для которых требуется больше времени для обработки. Пользователь, выполняющий запрос, должен будет ожидать результата в большинстве ситуаций. Однако другим пользователям, вызывающим вызов, не стоит ждать этого. Поэтому другой рабочий может принять этот запрос и выполнить эту работу.

Это будет рабочий процесс, как я его вижу:

  • Все свободные работники постоянно проводят опрос на работу в очереди

    1. Пользователь делает запрос
    2. Клиентский сервер принимает данные запроса и помещает их в очередь
    3. Рабочий выполняет задание из очереди и обрабатывает его
    4. Рабочий возвращает результат на клиентский сервер
    5. Клиентский сервер возвращает результат пользователю

Ниже небольшой рисунок, чтобы прояснить ситуацию.

  User 1 _ \ | \ \ \ 1. \ \ request \ \ ------------- result \ \ / \ 5. \ \ | Worker | \ _| | Server | \ | --------- | ------------- | / \ | / \ | | Worker1 | | | Client | / | | | | \ | Server 2. | / | \ / | \ | --------- | / | --------- | \ | / \ | / | --------- | \ | | Queue | | / | / \ | \ --------- | | | | |_ | | Worker2 | | _| / \ | | Job A | | | | | | | DB | | | Job B | | 3. <----- | \ / | -----> | Server | | | | | _ | --------- | _ | | | \ / | | | ... | | \ / | --------- | \ | --------- | / --------- \ / \ | / \ | / --------- ------------- \ | | WorkerN | | / / \ _ 4. ? \ | | | | / | Other | | | \ / | | Servers | / / | --------- | | | 1. / / \ / \ / request / / ------------- --------- / / / / result / / 5. / / |_ User 2 

В документации Laravel я столкнулся с очередями, которые, как я думал, легко выполнили бы. Я начал экспериментировать с Beanstalkd, но я предполагаю, что любой драйвер очереди будет делать. Проблема, на которую я наткнулся, заключается в том, что система очереди работает асинхронно. Как следствие, клиентский сервер просто продолжает, не дожидаясь результата. Если я что-то не упускаю, похоже, нет возможности заставить синхронно работать в очереди.

Когда я смотрел дальше в документацию Laravel, я встретил трансляцию. Я не уверен, понимаю ли я концепцию вещания на 100%, но из того, что я понимаю, является то, что получение, похоже, происходит в Javascript. Я сторонний разработчик и хотел бы остаться в стороне от Javascript. По какой-то причине мне кажется неправильным использовать javascript здесь, однако я не уверен, что это чувство оправдано.

Глядя далее в документацию, я наткнулся на Редиса. Я был в основном заинтригован функциональностью Pub / Sub. Я думал, что клиентский сервер может генерировать уникальное значение, отправлять его с данными запроса в очередь и подписываться на него. Как только рабочий закончен, он может опубликовать результат с этим уникальным значением. Я думал, что это может покрыть недостающую часть для шага 4. Я все еще не уверен, как это будет работать в коде, если эта логика будет работать в первую очередь. Я в основном придерживаюсь той части, где клиент должен слушать и получать данные от Redis.

Возможно, я пропустил что-то очень простое. Знайте, что я относительно новичок в программировании с PHP и концепции программирования во всемирной паутине. Поэтому, если вы обнаружите, что логика ошибочна, или она слишком надуманна, пожалуйста, дайте мне несколько указателей на другие / лучшие методы.

Дополнительный FYI, я слышал о Gearman, который, похоже, способен работать синхронно и асинхронно. Однако я хотел бы остаться в стороне от этого, поскольку моя цель – максимально использовать инструменты, предоставляемые Laravel. Я все еще участвую и недостаточно уверен, чтобы использовать слишком много внешних плагинов.

Редактировать : Это как далеко я получил. Что мне еще не хватает? Или то, что я прошу (рядом), невозможно?

Пользовательские вызовы http://my.domain.com/worker?message=whoop

Пользователь должен получить ответ JSON

 {"message":"you said whoop"} 

Обратите внимание, что в заголовке ответа тип содержимого должен быть "application/json" а не "text/html; charset=UTF-8"

Это то, что я имею до сих пор:

Два сервера API-сервера и WORKER-сервер. API-сервер получает запросы и помещает их в очередь (локальный Redis). Рабочие на сервере WORKER обрабатывают задания на API-сервере. Как только рабочий обработал задание, результат этого задания транслируется на API-сервер. API-сервер прослушивает переданный ответ и отправляет его пользователю. Это делается с помощью Redis и socket.io. Моя проблема в том, что в этот момент, чтобы отправить результат, я отправляю файл клика с некоторым Javascript, который слушает ответ. Это приводит к типу контента «text / html; charset = UTF-8», с элементом, который обновляется после того, как будет передан результат от работника. Есть ли способ, а не возвращать представление, вернуть «wait» до тех пор, пока результат не будет передан?

API-сервер: routes \ web.php:

 <?php Route::get('/worker', 'ApiController@workerHello'); 

API-сервер: приложение \ Http \ Controllers \ ApiController.php

 <?php namespace App\Http\Controllers; use App\Jobs\WorkerHello; use Illuminate\Http\Request; class ApiController extends Controller { /** * Dispatch on queue and open the response page * * @return string */ public function workerHello(Request $request) { // Dispatch the request to the queue $jobId = $this->dispatch(new WorkerHello($request->message)); return view('response', compact('jobId')); } } 

API-сервер: приложение \ Работа \ WorkerHello.php

 <?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Support\Facades\Log; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; class WorkerHello implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $message; /** * Create a new job instance. * * @param string $message the message * @return void */ public function __construct($message = null) { $this->message = $message; } /** * Execute the job. * * @return void */ public function handle() { } } 

WORKER-сервер: приложение \ Работа \ WorkerHello.php

 <?php namespace App\Jobs; use Illuminate\Bus\Queueable; use App\Events\WorkerResponse; use Illuminate\Support\Facades\Log; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; class WorkerHello implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $message; /** * Create a new job instance. * * @param string $message the message * @return void */ public function __construct($message = null) { $this->message = $message; } /** * Execute the job. * * @return void */ public function handle() { // Do stuff $message = json_encode(['message' => 'you said ' . $this->message]); event(new WorkerResponse($this->job->getJobId(), $message)); } } 

WORKER-сервер: приложение \ Events \ WorkerResponse.php

 <?php namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Queue\SerializesModels; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; class WorkerResponse implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; protected $jobId; public $message; /** * Create a new event instance. * * @return void */ public function __construct($jobId, $message) { $this->jobId = $jobId; $this->message = $message; } /** * Get the channels the event should broadcast on. * * @return Channel|array */ public function broadcastOn() { return new Channel('worker-response.' . $this->jobId); } } 

API-сервер: socket.js (выполняется с узлом)

 var server = require('http').Server(); var io = require('socket.io')(server); var Redis = require('ioredis'); var redis = new Redis(); redis.psubscribe('worker-response.*'); redis.on('pmessage', function(pattern, channel, message) { message = JSON.parse(message); io.emit(channel + ':' + message.event, channel, message.data); }); server.listen(3000); 

API-сервер: ресурсы \ views \ response.blade.php

 <!doctype html> <html lang="{{ config('app.locale') }}"> <head> </head> <body> <div id="app"> <p>@{{ message }}</p> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script> <script> var socket = io('http://192.168.10.10:3000'); new Vue({ el: '#app', data: { message: '', }, mounted: function() { socket.on('worker-response.{{ $jobId }}:App\\Events\\WorkerResponse', function (channel, data) { this.message = data.message; }.bind(this)); } }); </script> </body> </html>