Advertisement
  1. Code
  2. Laravel

Posponer Tareas en Laravel Utilizando Colas

Scroll to top
Read Time: 17 min

Spanish (Español) translation by Jean Perez (you can also view the original English article)

En este artículo vamos a explorar la API de cola en el marco de la web de Laravel. Le permite aplazar las tareas de uso intensivo de recursos durante la ejecución del script para mejorar la experiencia general de usuario final. Después de introducir la terminología básica, podrá demostrarlo mediante la aplicación de un ejemplo real.

Tiempo de carga de página es un aspecto importante de cualquier sitio web de éxito, y uno debería no pasar por alto la importancia de como afecta el SEO del sitio y la experiencia de usuario general. Más a menudo que no, terminas que necesitan depurar páginas web con tiempos de carga de página larga. Por supuesto, hay diferentes enfoques que puede usar para corregir este problema.

Sobre investigación, usted a menudo se dan cuenta que hay ciertos bloques de código causando un retardo en la ejecución de la página. La siguiente cosa que usted podría intentar es identificación de bloques que puede postergarse para el proceso y que no tienen real impacto en el resultado final de la página actual. Realmente deben mejorar la velocidad general de la página web que hemos eliminado los bloques de código que estaban causando un retraso.

Hoy, vamos a explorar un concepto similar en el contexto del marco Laravel web. De hecho, Laravel ya proporciona una útil API incorporada que nos permite aplazar el procesamiento de tareas, la API de cola. Sin perder mucho de su tiempo, yo adelante y discutir los elementos básicos de la API de cola.

Conductores, conexiones, colas y puestos de trabajo

El objetivo básico de la API de cola es ejecutar trabajos que se agregan en una cola. A continuación, la cola podría pertenecer a una conexión específica, y esa conexión puede pertenecer a un controlador de cola específico configurado con esa conexión sí mismo. Vamos a intentar brevemente entender lo que he de decir.

Controladores de Cola

De la misma manera habría utilizado un controlador diferente para su conexión a la base de datos, usted podría también elegir de una variedad de controladores de cola diferentes. La API de cola soporta diversos adaptadores como base de datos, beanstalkd, sqs y redis.

El controlador de cola es un lugar que se utiliza para almacenar información relacionada con la cola. Así que si estás utilizando un controlador de base de datos cola, por ejemplo, el nuevo trabajo se añadirá en la tabla de puestos de trabajo en la base de datos. Por otra parte, si ha configurado redis como el controlador predeterminado de cola, el trabajo se añadirá al servidor de redis.

La API de cola también ofrece dos motores de cola especial para propósitos de prueba, sincronización y null. El conductor de la cola de sincronización se utiliza para ejecutar un trabajo en cola inmediatamente, mientras que el controlador de cola null se utiliza para saltar de un trabajo para que no se ejecuta en todos.

Conexiones

Al configurar la API de cola por primera vez, usted necesita especificar una conexión predeterminada que debe utilizarse para el procesamiento de colas por defecto. Por lo menos, la conexión se espera que proporcione la siguiente información:

  • el controlador de cola que se utilizará
  • valores de configuración específica del controlador de cola
  • el nombre predeterminado de cola en la que se añadirá el trabajo

Colas

Cuando se añade cualquier tipo de trabajo en una cola, lo añadió en la cola predeterminada. De hecho, debe ser muy bien en la mayoría de los casos, a menos que tenga puestos de trabajo que deben recibir prioridad sobre otros trabajos. En ese caso, podría crear una cola llamada high y colocar los puestos de trabajo de prioridad más alta en esa cola en particular.

Cuando se ejecuta un trabajador de cola que procesa la cola de trabajos, usted puede opcionalmente pasar el parámetro --queue, que permite nombres de cola lista en el orden en el que deben procesarse. Por ejemplo, si especifica --queue=high,default, por defecto, procesará primero trabajos en la cola high, y al finalizar se obtiene trabajos en la cola por defecto.

Trabajo

Un trabajo en la API de cola es una tarea que es diferida por el flujo de ejecución principal. Por ejemplo, si desea crear una imagen cuando el usuario sube una imagen desde el front-end, puede crear un nuevo trabajo que maneja el procesamiento miniatura. De esta manera, podría aplazar la tarea de procesamiento miniatura por el flujo de ejecución principal.

Fue una introducción básica a la terminología de la API de cola. En la sección siguiente, exploraremos cómo crear un trabajo en cola personalizada y ejecutar utilizando un trabajador de cola Laravel.

Crear su Primer Cola de Trabajo

Por ahora, debe sentirse seguro sobre los trabajos de la cola. Desde esta sección vamos a implementar un ejemplo real que demuestra el concepto de trabajos de la cola en Laravel.

Más a menudo que no, termina en la situación donde es necesario crear diferentes versiones miniatura de una imagen subido por un usuario. En la mayoría de los casos, el desarrollador intenta procesar en tiempo real para que diferentes versiones de las imágenes se crean inmediatamente cuando el usuario carga una imagen.

Parece un enfoque razonable si vas a crear un par de versiones y no toma demasiado tiempo en el primer lugar. Por otro lado, si usted está tratando con una aplicación que requiere de gran procesamiento y así se come más recursos, procesamiento en tiempo real podría terminar en una mala experiencia.

La opción obvia que aparece en tu mente en primer lugar es aplazar el proceso de la generación de miniaturas tan tarde como sea posible. El enfoque más sencillo que se podría implementar en este escenario específico es una tarea de cron que factores desencadenantes tratamiento en intervalos regulares y usted deben ser finas.

Un enfoque mucho mejor, por el contrario, es aplazar y empuje la tarea de una cola y que el trabajador de cola procesarlo cuando tiene la oportunidad de hacerlo. En un entorno de producción, el trabajador de la cola es un script de daemon que está siempre funcionando y tareas en una cola de procesamiento. La ventaja obvia de este enfoque es una mucho mejor experiencia de usuario final, y no tienes que esperar a que el cron como el trabajo se procesarán tan pronto como sea posible.

Supongo que es suficiente teoría para empezar con una aplicación real.

En nuestro caso, vamos a utilizar el controlador de database cola, y nos obliga a crear la tabla de puestos de jobs en la base de datos. La mesa jobs tiene todos los trabajos que deben ser procesados en el siguiente trabajador de cola ejecutar.

Antes de seguir adelante y crear la tabla de jobs, vamos a cambiar la configuración predeterminada de la cola de sync a database en el archivo config/queue.php.

1
...
2
...
3
/*

4
|--------------------------------------------------------------------------

5
| Default Queue Driver

6
|--------------------------------------------------------------------------

7
|

8
| Laravel's queue API supports an assortment of back-ends via a single

9
| API, giving you convenient access to each back-end using the same

10
| syntax for each one. Here you may set the default queue driver.

11
|

12
| Supported: "sync", "database", "beanstalkd", "sqs", "redis", "null"

13
|

14
*/
15
16
'default' => env('QUEUE_DRIVER', 'database'),
17
...
18
...

De hecho, Laravel ya proporciona un comando de artesano que nos ayuda a crear la tabla de jobs. Ejecute el siguiente comando en la raíz de su aplicación Laravel, y debe crear la migración de base de datos es necesario que crea la tabla de jobs.

1
$php artisan queue:table

El archivo de migración que es generado en el database/migrations/YYYY_MM_DD_HHMMSS_create_jobs_table.php debe tener este aspecto:

1
<?php
2
use Illuminate\Support\Facades\Schema;
3
use Illuminate\Database\Schema\Blueprint;
4
use Illuminate\Database\Migrations\Migration;
5
6
class CreateJobsTable extends Migration
7
{
8
    /**

9
     * Run the migrations.

10
     *

11
     * @return void

12
     */
13
    public function up()
14
    {
15
        Schema::create('jobs', function (Blueprint $table) {
16
            $table->bigIncrements('id');
17
            $table->string('queue');
18
            $table->longText('payload');
19
            $table->unsignedTinyInteger('attempts');
20
            $table->unsignedInteger('reserved_at')->nullable();
21
            $table->unsignedInteger('available_at');
22
            $table->unsignedInteger('created_at');
23
24
            $table->index(['queue', 'reserved_at']);
25
        });
26
    }
27
28
    /**

29
     * Reverse the migrations.

30
     *

31
     * @return void

32
     */
33
    public function down()
34
    {
35
        Schema::dropIfExists('jobs');
36
    }
37
}

A continuación, vamos a ejecutar el comando de migrate para que realmente crea la tabla de jobs en una base de datos.

1
php artisan migrate

Eso es todo en cuanto a la migración de jobs.

A continuación, vamos a crear el modelo de Image que se utilizará para administrar las imágenes subidas por el usuario final. El modelo de imagen también requiere una tabla de base de datos asociada, por lo que vamos a utilizar --migrate opción al crear el modelo de la Image.

1
php artisan make:model Image --migration

El comando anterior debe crear la clase de modelo de Image y así una migración de base de datos asociada.

La clase de modelo de la Image debe tener este aspecto:

1
<?php
2
// app/Image.php

3
namespace App;
4
5
use Illuminate\Database\Eloquent\Model;
6
7
class Image extends Model
8
{
9
    //

10
}

Y el archivo de migración de base de datos debe crearse en database/migrations/YYYY_MM_DD_HHMMSS_create_images_table.php. También queremos guardar la ruta original de la imagen cargada por el usuario final. Vamos a revisar el código del archivo de migración de base de datos de Image para parecerse a la siguiente.

1
<?php
2
// database/migrations/YYYY_MM_DD_HHMMSS_create_images_table.php

3
use Illuminate\Support\Facades\Schema;
4
use Illuminate\Database\Schema\Blueprint;
5
use Illuminate\Database\Migrations\Migration;
6
7
class CreateImagesTable extends Migration
8
{
9
    /**

10
     * Run the migrations.

11
     *

12
     * @return void

13
     */
14
    public function up()
15
    {
16
        Schema::create('images', function (Blueprint $table) {
17
            $table->increments('id');
18
            $table->timestamps();
19
            $table->string('org_path');
20
        });
21
    }
22
23
    /**

24
     * Reverse the migrations.

25
     *

26
     * @return void

27
     */
28
    public function down()
29
    {
30
        Schema::dropIfExists('images');
31
    }
32
}

Como se puede ver, hemos añadido la columna de $table->string('org_path') para almacenar la ruta de la imagen original. A continuación, basta con ejecutar el comando de migrate para crear esa tabla en la base de datos.

1
$php artisan migrate

Y eso es en cuanto al modelo de la Image.

A continuación, vamos a crear un trabajo en cola real que se encarga de procesar las miniaturas de la imagen. Para el procesamiento de miniatura, que vamos a utilizar una librería de procesamiento de imagen muy popular — imagen de la intervención.

Para instalar la biblioteca de imágenes de la intervención, seguir adelante y ejecutar el siguiente comando en la raíz de su aplicación.

1
$php composer.phar require intervention/image

Ahora, es el momento de crear la clase Job, y usaremos un comando artesanal para hacer eso.

1
$php artisan make:job ProcessImageThumbnails

Debe crear la plantilla de la clase Job en app/Jobs/ProcessImageThumbnails.php. Vamos a sustituir el contenido de ese archivo con el siguiente.

1
<?php
2
// app/Jobs/ProcessImageThumbnails.php

3
namespace App\Jobs;
4
5
use App\Image as ImageModel;
6
use Illuminate\Bus\Queueable;
7
use Illuminate\Queue\SerializesModels;
8
use Illuminate\Queue\InteractsWithQueue;
9
use Illuminate\Contracts\Queue\ShouldQueue;
10
use Illuminate\Foundation\Bus\Dispatchable;
11
use Illuminate\Support\Facades\DB;
12
13
class ProcessImageThumbnails implements ShouldQueue
14
{
15
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
16
17
    protected $image;
18
19
    /**

20
     * Create a new job instance.

21
     *

22
     * @return void

23
     */
24
    public function __construct(ImageModel $image)
25
    {
26
        $this->image = $image;
27
    }
28
29
    /**

30
     * Execute the job.

31
     *

32
     * @return void

33
     */
34
    public function handle()
35
    {
36
        // access the model in the queue for processing

37
        $image = $this->image;
38
        $full_image_path = public_path($image->org_path);
39
        $resized_image_path = public_path('thumbs' . DIRECTORY_SEPARATOR .  $image->org_path);
40
41
        // create image thumbs from the original image

42
        $img = \Image::make($full_image_path)->resize(300, 200);
43
        $img->save($resized_image_path);
44
    }
45
}

Cuando el trabajador de la cola empieza a procesar cualquier tipo de trabajo, busca el método de handle. Así es el método de handle que sostiene la lógica principal de su trabajo.

En nuestro caso, necesitamos crear una miniatura de una imagen subida por el usuario. El código del método de handle es bastante sencillo — que recuperar una imagen del modelo ImageModel y crear una imagen en miniatura usando la biblioteca de imágenes de la intervención. Por supuesto, tenemos que pasar el modelo de Image al que enviemos nuestro trabajo, y vamos a ver en un momento.

Para probar nuestro trabajo recién creado, vamos a crear un formulario de carga simple que permite al usuario subir una imagen. Por supuesto, no creará las miniaturas de imagen a aplazar la tarea por lo que podría ser procesado por el trabajador de la cola.

Vamos a crear un archivo de controlador en app/Http/Controllers/ImageController.php como se muestra a continuación.

1
<?php
2
namespace App\Http\Controllers;
3
4
use App\Image;
5
use App\Jobs\ProcessImageThumbnails;
6
use Illuminate\Http\Request;
7
use Illuminate\Support\Facades\Redirect;
8
use App\Http\Controllers\Controller;
9
use Validator;
10
11
class ImageController extends Controller
12
{
13
    /**

14
     * Show Upload Form

15
     *

16
     * @param  Request  $request

17
     * @return Response

18
     */
19
    public function index(Request $request)
20
    {
21
        return view('upload_form');
22
    }
23
24
    /**

25
     * Upload Image

26
     *

27
     * @param  Request  $request

28
     * @return Response

29
     */
30
    public function upload(Request $request)
31
    {
32
        // upload image

33
        $this->validate($request, [
34
          'demo_image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
35
        ]);
36
        $image = $request->file('demo_image');
37
        $input['demo_image'] = time().'.'.$image->getClientOriginalExtension();
38
        $destinationPath = public_path('/images');
39
        $image->move($destinationPath, $input['demo_image']);
40
41
        // make db entry of that image

42
        $image = new Image;
43
        $image->org_path = 'images' . DIRECTORY_SEPARATOR . $input['demo_image'];
44
        $image->save();
45
46
        // defer the processing of the image thumbnails

47
        ProcessImageThumbnails::dispatch($image);
48
49
        return Redirect::to('image/index')->with('message', 'Image uploaded successfully!');
50
    }
51
}

Vamos a crear un archivo de vista asociada a resources/views/upload_form.blade.php.

1
<!DOCTYPE html>
2
<html lang="{{ config('app.locale') }}">
3
    <head>
4
        <meta charset="utf-8">
5
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
        <meta name="viewport" content="width=device-width, initial-scale=1">
7
        <meta name="csrf-token" content="{{ csrf_token() }}" />
8
        <title>Laravel</title>
9
10
        <!-- Fonts -->
11
        <link href="https://fonts.googleapis.com/css?family=Raleway:100,600" rel="stylesheet" type="text/css">
12
13
        <!-- Styles -->
14
        <style>
15
            html, body {
16
                background-color: #fff;
17
                color: #636b6f;
18
                font-family: 'Raleway', sans-serif;
19
                font-weight: 100;
20
                height: 100vh;
21
                margin: 0;
22
            }
23
24
            .full-height {
25
                height: 100vh;
26
            }
27
28
            .flex-center {
29
                align-items: center;
30
                display: flex;
31
                justify-content: center;
32
            }
33
34
            .position-ref {
35
                position: relative;
36
            }
37
38
            .top-right {
39
                position: absolute;
40
                right: 10px;
41
                top: 18px;
42
            }
43
44
            .content {
45
                text-align: center;
46
            }
47
48
            .title {
49
                font-size: 84px;
50
            }
51
52
            .links > a {
53
                color: #636b6f;
54
                padding: 0 25px;
55
                font-size: 12px;
56
                font-weight: 600;
57
                letter-spacing: .1rem;
58
                text-decoration: none;
59
                text-transform: uppercase;
60
            }
61
62
            .m-b-md {
63
                margin-bottom: 30px;
64
            }
65
            
66
            .alert {
67
                color: red;
68
                font-weight: bold;
69
                margin: 10px;
70
            }
71
            .success {
72
                color: blue;
73
                font-weight: bold;
74
                margin: 10px;
75
            }
76
        </style>
77
    </head>
78
    <body>
79
        <div class="flex-center position-ref full-height">
80
            @if (Route::has('login'))
81
                <div class="top-right links">
82
                    @if (Auth::check())
83
                        <a href="{{ url('/home') }}">Home</a>
84
                    @else
85
                        <a href="{{ url('/login') }}">Login</a>
86
                        <a href="{{ url('/register') }}">Register</a>
87
                    @endif
88
                </div>
89
            @endif
90
91
            <div class="content">
92
                <div class="m-b-md">
93
                    <h1 class="title">Demo Upload Form</h1>
94
                    
95
                    @if ($errors->any())
96
                        <div class="alert alert-danger">
97
                            <ul>
98
                                @foreach ($errors->all() as $error)
99
                                    <li>{{ $error }}</li>
100
                                @endforeach
101
                            </ul>
102
                        </div>
103
                    @endif
104
                    
105
                    @if (session('message'))
106
                        <div class="success">
107
                            {{ session('message') }}
108
                        </div>
109
                    @endif
110
                    
111
                    <form method="post" action="{{ url('/image/upload') }}" enctype="multipart/form-data">
112
                      <div>
113
                        <input type="file" name="demo_image" />
114
                      </div>
115
                      <br/>
116
                      <div>
117
                        <input type="hidden" name="_token" value="{{ csrf_token() }}">
118
                        <input type="submit" value="Upload Image"/>
119
                      </div>
120
                    </form>
121
                </div>
122
            </div>
123
        </div>
124
    </body>
125
</html>

Finalmente, vamos a añadir las rutas para el index y upload acciones en el archivo routes/web.php.

1
Route::get('image/index', 'ImageController@index');
2
Route::post('image/upload', 'ImageController@upload');

En el controlador de ImageController, el método de index se utiliza para representar un formulario de subida.

1
public function index(Request $request)
2
{
3
    return view('upload_form');
4
}

Cuando el usuario envía un formulario, se invoca el método de upload.

1
public function upload(Request $request)
2
{
3
    // upload image

4
    $this->validate($request, [
5
        'demo_image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
6
    ]);
7
    $image = $request->file('demo_image');
8
    $input['demo_image'] = time().'.'.$image->getClientOriginalExtension();
9
    $destinationPath = public_path('/images');
10
    $image->move($destinationPath, $input['demo_image']);
11
12
    // make db entry of that image

13
    $image = new Image;
14
    $image->org_path = 'images' . DIRECTORY_SEPARATOR . $input['demo_image'];
15
    $image->save();
16
17
    // defer the processing of the image thumbnails

18
    ProcessImageThumbnails::dispatch($image);
19
20
    return Redirect::to('image/index')->with('message', 'Image uploaded successfully!');
21
}

A principios del método de upload, usted notará el archivo usual Añadir código que mueve el archivo subido al directorio public/images. A continuación, insertamos un registro de base de datos mediante el modelo de App/Image.

Finalmente, utilizamos el trabajo de ProcessImageThumbnails para aplazar la tarea procesamiento de miniatura. Es importante tener en cuenta que es el método de dispatch que se usa para diferir una tarea. Al final, se redirige al usuario a la página de carga con un mensaje de éxito.

En este momento, el trabajo se agrega a la tabla de jobs para el proceso. Vamos a confirmarlo mediante la emisión de la siguiente consulta.

1
mysql> select * FROM lvl_jobs;
2
|  1 | default | {"displayName":"App\\Jobs\\ProcessImageThumbnails","job":"Illuminate\\Queue\\CallQueuedHandler@call","maxTries":null,"timeout":null,"data":{"commandName":"App\\Jobs\\ProcessImageThumbnails","command":"O:31:\"App\\Jobs\\ProcessImageThumbnails\":5:{s:8:\"\u0000*\u0000image\";O:45:\"Illuminate\\Contracts\\Database\\ModelIdentifier\":2:{s:5:\"class\";s:9:\"App\\Image\";s:2:\"id\";i:2;}s:6:\"\u0000*\u0000job\";N;s:10:\"connection\";N;s:5:\"queue\";N;s:5:\"delay\";N;}"}} |        0 |        NULL |   1510219099 | 1510219099 |

Usted debe estarse preguntando, ¿qué necesita para procesar un trabajo entonces? No te preocupes, eso es lo que vamos a discutir en la siguiente sección.

Trabajador de Cola

La tarea del trabajador Laravel cola es procesar los trabajos que están en cola para su procesamiento. De hecho, hay un comando de artesano que nos ayuda a iniciar el proceso de trabajo de la cola.

1
$php artisan queue:work

Tan pronto como se ejecuta ese comando, procesos pendientes puestos de trabajo. En nuestro caso, que debe procesar el trabajo de ProcessImageThumbnails que fue cola cuando el usuario carga una imagen anterior.

1
$php artisan queue:work
2
[YYYY-MM-DD HHMMSS] Processing: App\Jobs\ProcessImageThumbnails
3
[YYYY-MM-DD HHMMSS] Processed:  App\Jobs\ProcessImageThumbnails

Habría notado que cuando empiezas un trabajador de la cola, sigue sonando hasta que mata manualmente o cerrar la terminal. De hecho, está esperando el siguiente trabajo a procesar. Tan pronto como hay un nuevo trabajo en la cola, va ser procesado inmediatamente si el trabajador de la cola se está ejecutando.

Por supuesto, no podemos continuar funcionando de esa manera, así que tenemos que encontrar una manera para que el trabajador cola ejecuta permanentemente en segundo plano.

A nuestro rescate, hay varias proceso herramientas administrativas ahi que podía elegir. Para nombrar unos pocos, aquí está una lista:

  • Circus
  • daemontools
  • Monit
  • Supervisor
  • Upstart

Debe elegir la herramienta que usted está cómodo con gestionar el trabajador de cola Laravel. Básicamente, queremos para asegurarse de que el trabajador de la cola debe ejecutar indefinidamente para que procesa trabajos cola enseguida.

Es que la API de cola a su disposición. Se puede utilizar en su desarrollo día a día aplazar tareas desperdiciadoras de tiempo para el mejoramiento de la experiencia del usuario final.

Conclusión

En este artículo, discutimos la API de cola en Laravel, que es muy útil si desea aplazar el procesamiento de tareas consumidoras de recursos.

Comenzamos con una introducción básica a la API de cola, que una discusión de las conexiones, colas y puestos de trabajo. En la segunda mitad del artículo, hemos creado un trabajo en cola personalizada que demostró cómo se podía utilizar la API de cola en el mundo real.

Para aquellos de ustedes que son ya sea consiguiendo apenas iniciado con Laravel o buscando para expandir tu conocimiento, sitio o aplicación con extensiones, contamos con una variedad de cosas que puedes estudiar en Envato Market.

No dude en utilizar el formulario de comentarios abajo para enviar sus consultas y sugerencias.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.