php - recordatorios - Laravel: sistema de cola Synchronisch
queue php (0)
Estoy tratando de configurar una API que utiliza un sistema de cola en otro servidor para manejar las solicitudes. Permítanme comenzar lo que intento lograr sin el sistema de cola (no hay autorización para hacerlo simple): al usar Postman, por ejemplo, hacer una solicitud GET a la URL https://example.com/products devolvería una cadena JSON como
[
{
"id": 1,
"name": "some name",
...
},
{
"id": 2,
"name": "some other name",
...
}.
...
]
El código en routes / api.php sería algo así como:
<php
Route::get(''/products'', ProductController@index'');
Y el código en la aplicación / 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();
}
}
Lo que me gustaría lograr es que toda la lógica comercial se procese en otro servidor que ejecute múltiples trabajadores. Lo que sigue es mi razonamiento detrás de esto.
- Seguridad: en el caso de que nos pirateen, lo más probable es que sea el servidor del cliente y no el servidor del trabajador. Como el último tiene toda la lógica comercial, el pirata informático, en el peor de los casos, solo podrá obtener datos de solicitudes entrantes y salientes.
- Múltiples trabajadores: Obtener los productos probablemente no tome mucho tiempo, pero puede haber otras solicitudes que necesiten más tiempo para procesar. El usuario que realiza la solicitud deberá esperar el resultado en la mayoría de las situaciones. Sin embargo, otros usuarios que hacen una llamada no deberían tener que esperar por esto. Por lo tanto, otro trabajador puede tomar esta solicitud y manejar el trabajo.
Este sería el flujo de trabajo como lo veo:
Todos los trabajadores libres están constantemente sondeando para un trabajo en la cola
- El usuario hace la solicitud
- Client Server toma datos de solicitud y los coloca en la cola
- Un trabajador toma un trabajo de la cola y lo maneja
- El trabajador devuelve el resultado al servidor del cliente
- El servidor cliente devuelve el resultado al usuario
Debajo Un pequeño dibujo para aclarar las cosas.
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
En la documentación de Laravel encontré las colas, que pensé que fácilmente harían el truco. Empecé a experimentar con Beanstalkd, pero supongo que cualquier controlador de cola lo haría. El problema con el que tropecé es que el sistema de cola funciona de forma asíncrona. Como consecuencia, el servidor de cliente simplemente continúa sin esperar un resultado. A menos que me falta algo, parece que no hay ninguna opción para hacer que el sistema de cola funcione sincrónicamente.
Al profundizar en la documentación de Laravel encontré la radiodifusión. No estoy seguro si entiendo el concepto de radiodifusión al 100%, pero por lo que entiendo es que la recepción parece suceder en Javascript. Soy desarrollador de back-end y me gustaría mantenerme alejado de Javascript. Por alguna razón, me parece mal que use javascript aquí, sin embargo, no estoy seguro de si ese sentimiento es justificable.
Mirando más allá en la documentación encontré a Redis. Estaba intrigado principalmente por la funcionalidad Pub / Sub. Estaba pensando que el servidor de cliente podría generar un valor único, enviarlo con los datos de solicitud a la cola y suscribirse a él. Una vez que el trabajador termina, puede publicar el resultado con este valor único. Estaba pensando que esto podría cubrir la parte faltante para el paso 4. Todavía no estoy seguro de cómo funcionaría esto en el código, si esta lógica funcionara en primer lugar. Principalmente me quedo con la parte donde el cliente debe escuchar y recibir los datos de Redis.
Me podría estar perdiendo algo muy simple. Sepa que soy relativamente nuevo en programación con PHP y con el concepto de programación a través de la red mundial. Por lo tanto, si encuentra que la lógica es defectuosa, o que es demasiado descabellada, por favor bríndeme algunos consejos sobre otros / mejores métodos.
Un FYI extra, he oído hablar de Gearman, que parece ser capaz de trabajar de forma sincrónica y asíncrona. Sin embargo, me gustaría mantenerme al margen de eso, ya que mi objetivo es utilizar las herramientas proporcionadas por Laravel al máximo. Todavía estoy aprendiendo y no estoy lo suficientemente seguro como para utilizar demasiados complementos externos.
Editar : Esto es lo lejos que conseguí. ¿Qué sigo perdiendo? ¿O es lo que estoy preguntando (casi) imposible?
Llamadas del usuario http://my.domain.com/worker?message=whoop
El usuario debe recibir una respuesta JSON
{"message":"you said whoop"}
Tenga en cuenta que en el encabezado de la respuesta, el tipo de contenido debe ser "application/json"
y no "text/html; charset=UTF-8"
Esto es lo que tengo hasta ahora:
Dos servidores API-server y WORKER-server. El servidor API recibe las solicitudes y las envía a la cola (Redis local). Los trabajadores en un servidor WORKER procesan los trabajos en el servidor API. Una vez que un trabajador procesó un trabajo, el resultado de este trabajo se transmite al servidor API. El servidor API escucha la respuesta emitida y la envía al usuario. Esto se hace con Redis y socket.io. Mi problema es que en este punto, para enviar el resultado, envío un archivo blade con un Javascript que escucha la respuesta. Esto da como resultado un tipo de contenido "text / html; charset = UTF-8", con un elemento que se actualiza una vez que se emite el resultado del trabajador. ¿Hay alguna manera de, en lugar de devolver una vista, hacer que la declaración "espere" hasta que se emita el resultado?
Servidor API: routes / web.php:
<?php
Route::get(''/worker'', ''ApiController@workerHello'');
API-server: app / 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-server: aplicación / Trabajos / 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-server: aplicación / Trabajos / 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-server: aplicación / Eventos / 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);
}
}
Servidor API: socket.js (se ejecuta con el nodo)
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);
Servidor API: resources / 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>