Limitar tareas por segundo con PHP

PHP tareas por segundoHoy traigo un pequeño HOWTO para limitar en el tiempo la cantidad de tareas a realizar. Necesitaba hacer un pequeño script para hacer 200 000 pequeñas tareas. El servicio sobre el que actúan éstas tareas está limitado a 12 por segundo, con lo que estábamos obligados a hacer algo para limitarlo en el tiempo y no ser bloqueados.

Este script puede servir para enviar una cola de emails transaccionales, para hacer un mailing a tus suscriptores sin pasarte de la cuota que tengas contratada con tu proveedor de hosting, también es útil para limitar la cantidad de veces por segundo que consumes de una API de algún servicio.

Las API REST son cada vez más habituales. Con HTTP tenemos las instrucciones básicas de listar, editar, crear o borrar. Es cada vez más habitual encontrarnos con estos servicios, o los antiguos Webservices o SOAP.. Habrá APIs con las que también si te pasas de tareas por segundo también te cierran para que no colapses el sistema y puedan dar servicio al resto. Así que pienso que si estás aquí te puede ser útil..

Vamos al grano con el código

La idea principal es limitar las tareas por segundo en una variable. Si cuando las has hecho te queda tiempo del segundo, entonces esperamos hasta consumirlo. Si no te queda tiempo entonces podemos seguir haciendo las siguientes x tareas porque hemos hecho menos de las permitidas por segundos, es decir, en este caso no nos paramos:

<?php

$nline = "\n";
$tasksLimitPerSecond = 10;
$totalTasksLimit = 100;

$cont = $contTasksDone = $contTotalTasksDone = 0;

$timeStart = microtime(true);
while ($totalTasksLimit > $contTotalTasksDone) {
  ++$cont;

  doTask();

  ++$contTotalTasksDone;
  ++$contTasksDone;

  $timeEnd = microtime(true);
  if ($contTasksDone >= $tasksLimitPerSecond) {
    $timeConsumed = $timeEnd - $timeStart;

    echo 'Time start: '.$timeStart.$nline;
    echo 'Time end: '.$timeEnd.$nline;
    echo $contTasksDone.' tasks done in this second, '.$cont.' tasks done in total, '
      .$timeConsumed.' seconds consumed.'.$nline;

    $waitTime = 1000000 - $timeConsumed * 1000000;
    if ($waitTime > 0) {
      echo 'Waiting '.($waitTime / 1000000).' seconds to continue..'.$nline;
      usleep($waitTime);
    } else {
      echo 'Second consumed, continue doing..'.$nline;
    }

    $contTasksDone = 0;
    $timeStart = microtime(true);
  }
}

echo $cont.' loops counted, '.$contTotalTasksDone.' total tasks counted.'.$nline;

function doTask()
{
  usleep(10000);
  echo "Do something!\n";
}

La salida por pantalla

Al ejecutarlo desde línea de comandos tienes que ver algo parecido a esto, dependiendo de los valores de las variables que hayas asignado a $tasksLimitPerSecond y a $totalTasksLimit:

Do something!
Do something!
Do something!
Do something!
Do something!
Do something!
Do something!
Do something!
Do something!
Do something!
Time start: 1471686381.2994
Time end: 1471686381.4005
10 tasks done in this second, 10 tasks done in total, 0.10109782218933 seconds consumed.
Waiting 0.89890217781067 seconds to continue..
Do something!
Do something!
Do something!
Do something!
Do something!
Do something!
Do something!
Do something!
Do something!
Do something!
Time start: 1471686382.2996
Time end: 1471686382.4007
10 tasks done in this second, 20 tasks done in total, 0.10114002227783 seconds consumed.
Waiting 0.89885997772217 seconds to continue..

Más información

Es interesante ver la documentación oficial sobre las funciones microtime() y usleep(). También están las funciones de PHP time() y sleep(), pero las usadas en el script trabajan con microsegundos para ser más precisos al limitarnos en tareas por segundo. microtime() devuelve la fecha en microsegundos y usleep() pone a dormir el script esperando una cantidad de microsegundos indicados, en este caso se duerme por 10 000 microsegundos.

Espero que sirva.

Un saludo.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *