ricos - Procesamiento de PHP retrasado y escalable
piña cantidad de fibra dietaria (15)
¿Qué pasa con el uso de cualquiera de los dos cron para ejecutar un comprobador, que puede ejecutar las cosas desde la base de datos, por ejemplo.
¿O usando el comando "at" de linux para programar la ejecución de algún comando?
Estoy trabajando en una aplicación de PHP en línea que necesita retrasar el evento de PHP. Básicamente, necesito poder ejecutar un código PHP arbitrario por muchos segundos (pero podrían pasar días) luego del golpe inicial a una URL. Necesito una ejecución bastante precisa de estos eventos de PHP, también quiero que sea bastante escalable. Estoy tratando de evitar la necesidad de programar un trabajo cron para ejecutar cada segundo. Estaba investigando Gearman , pero no parece proporcionar ninguna capacidad para programar eventos y, según tengo entendido, PHP no está destinado a ejecutarse como un demonio.
Sería ideal si pudiera indicar algún proceso externo para sondear un url de "comprobador de eventos" en el servidor PHP en el momento exacto en que se debe ejecutar el siguiente evento. Este tiempo de encuesta deberá poder reducirse o aumentarse a voluntad, ya que el evento se puede eliminar y agregar a la cola y. ¿Alguna idea sobre una forma elegante de lograr esto? Simplemente, existe una gran sobrecarga en la llamada externa de PHP (tener que analizar la solicitud HTTP o la llamada a través de CLI) para hacer que esta idea sea factible para mis necesidades.
Mi plan actual es escribir un demonio de PHP que ejecutará el evento e interactuará con él desde el servidor de PHP con gearman. El demonio de PHP se construiría alrededor de SplMinHeap así que espero que el rendimiento no sea tan malo. Esta idea deja un mal sabor de boca y me preguntaba si alguien tenía una mejor idea. Las ideas cambiaron un poco. Leer Edición 2.
EDITAR:
Estoy creando un juego en línea que evoluciona a los jugadores que se turnan con un límite de tiempo variable. Estoy usando XMPP y BOSH para permitirme enviar mensajes desde y hacia mis clientes, pero tengo esa parte terminada y funcionando. Ahora estoy tratando de agregar un evento arbitrario que se dispara después del juego del cliente para permitir que el cliente (y otras personas en el juego) que tomó demasiado tiempo. No puedo usar el desencadenante temporizado en el lado del cliente porque sería explotable (ya que el cliente puede jugar solo). Espero que ayude.
EDIT 2:
Gracias a todos por sus comentarios. Si bien creo que la mayoría de sus ideas funcionarán bien en pequeña escala, tengo la sensación de que no se escalarían muy bien (administrador de eventos externos) o que carecen de la exactitud que requiere este proyecto (CRON). Además, en ambos casos, son piezas externas que podrían fallar y agregar complejidad a un sistema ya complejo.
Personalmente, creo que la única solución limpia que cumple con los requisitos para este proyecto es escribir un demonio PHP que maneje los eventos retrasados. He empezado a escribir lo que creo que es el primer runloop de PHP. Se encarga de mirar los sockets y ejecutar eventos PHP retrasados. Con suerte, cuando esté más cerca de terminar con este proyecto, puedo publicar la fuente, si alguno de ustedes está interesado en él. Hasta ahora, en las pruebas se ha demostrado que es una solución prometedora (no hay problemas con pérdida de memoria o inestabilidad).
EDIT 3: aquí hay un enlace a la biblioteca de bucles de eventos de PHP llamada LooPHP para aquellos que estén interesados.
TL; requisitos de DR
- Llame (preferiblemente de forma nativa) PHP en un tiempo de retraso (desde segundos hasta días)
- Maneje la creación / actualización / eliminación de eventos de forma arbitraria (estoy esperando una gran cantidad de llamadas canceladas).
- Manejar alta carga de eventos programados (100-1000 por segundo por servidor)
- Las llamadas deben estar dentro de un segundo de su tiempo programado
- En este momento no estoy abierto a volver a escribir la base del código en otro idioma (tal vez algún día lo haré)
¿Qué pasa con esto?
Aquí está la respuesta correcta, pero puede que no te guste.
PHP está diseñado exclusivamente para ser utilizado como un lenguaje de solicitud-respuesta (http), y por lo tanto no es compatible con lo que está buscando, es genial hackear y encontrar formas de hacerlo, pero será solo eso, un hack, lo que sea '' solución ''terminas consiguiendo.
Lo que realmente necesita es un lenguaje dirigido a eventos que admita xmpp, y para eso no necesita buscar más que node.js / v8 y las bibliotecas XMPP compatibles: esto es compatible de forma nativa y está diseñado para lo que necesita. También puede ir por la ruta de Java, pero si desea realizar un puerto rápido y obtener toda una serie de nuevas características y soporte para lo que está haciendo, el nodo es el indicado.
Si insiste en utilizar PHP (como lo he hecho muchas veces durante muchos años), la forma más ''ligera'' y efectiva de hacerlo es un demonio PHP persistente con una cola de eventos en una base de datos. ¡Lamentablemente!
Checkout esto con redis. puede ser útil para su problema
Creo que una solución solo para PHP será difícil (casi imposible) de implementar. Se me ocurrieron dos soluciones a su problema.
Solución PHP / Redis
Pregunta hecha por Kendall:
- ¿Qué tan estable es el redis?
Redis es muy estable. El desarrollador realmente escribe un código C limpio. Deberías comprobarlo en github;). También muchos sitios grandes están usando redis. Por ejemplo, github. Tenían una post blog muy interesante sobre cómo hicieron que github fuera rápido :). También redis usa redis . Hay muchas más grandes empresas que están usando redis;). Te aconsejaría que lo busques en Google;).
- Cómo se rediseña el uso de PHP:
PHP es muy amigable con PHP Muchos usuarios están escribiendo bibliotecas PHP para redis. El protocolo es realmente simple. Puedes depurarlo con telnet;). Mirando rápidamente predis por ejemplo tiene el bloqueo pop implementado.
- ¿Cómo puedo eliminar eventos:
Creo que deberías usar algo como ZRemCommand .
Redis es una tienda avanzada de valor-clave. Es similar a memcached pero el conjunto de datos no es volátil, y los valores pueden ser cadenas, exactamente como en memcached, pero también listas, conjuntos y conjuntos ordenados. Todos estos tipos de datos se pueden manipular con operaciones atómicas para empujar / hacer estallar elementos, agregar / eliminar elementos, realizar la unión del lado del servidor, intersección, diferencia entre conjuntos, etc. Redis soporta diferentes tipos de habilidades de clasificación.
Lo que se me ocurrió (Pseudo-código ....):
procesador.php:
<?php
######----processer.php
######You should do something like nohup php processor.php enough times for processors to run event.
#$key: should be unique, but should also be used by wakeup.php
while(true) {
$event = blpop($key); #One of the available blocking threads will wakeup and process event
process($event); #You should write process. This could take some time so this process could not be available
zrem($key1, $event); #Remove event after processing it. Added this later!!!!!!
}
cliente.php:
######----client.php
######The user/browser I guess should generate these events.
#$key1: should be unique.
#$millis: when event should run
#$event: just the event to work on.
if ("add event") {
zadd($key1, $millis, $event);
} else if ("delete event") {
zremove($key1, $event)
}
#Get event which has to be scheduled first
$first = zrange($key1, 0, 0);
if ($oldfirst <> $first) { #got different first event => notify wakeup.php.
lpush($key2, $first);
}
$oldfirst = $first;
wakeup.php:
####wakeup.php
#### 1 time do something like nohup php wakeup.php
#http://code.google.com/p/redis/wiki/IntroductionToRedisDataTypes => read sorted set part.
while(true) {
$first = zrange($key1, 0, 0);
$event = blpop($key2, $timeoutTillFirstEvent);
if ($event == nill) {
#Blockingqueue has timedout which means event should be run by 1 of blocking threads.
blpop($key2, $first);
}
}
Algo parecido a esto, también podría escribir un programador bastante eficiente usando PHP (Bueno, redis es C, así que es muy rápido :)) y sería bastante eficiente también :). También me gustaría codificar esta solución para permanecer sintonizado;). Creo que podría escribir un prototipo útil en un día ...
Mi solucion java
Esta mañana creo que creé un programa java que puedes usar para tu problema.
descargar :
Visite la página de descargas de github para descargar el archivo jar (con todas las dependencias incluidas).
instalar :
java -jar schedule-broadcaster-1.0-SNAPSHOT-jar-with-dependencies-1277709762.jar
Ejecutar simples fragmentos de PHP
- Primero
php -f scheduler.php
- Siguiente
php -f receiver.php
- Primero
Creé estos pequeños fragmentos para que con suerte usted entienda cómo usar mi programa. También hay un poco de documentación en el WIKI .
TaskQueue de App Engine
Una solución rápida sería utilizar la cola de tareas del motor de aplicaciones de Google, que tiene una cuota libre razonable. Después de eso tienes que pagar por lo que usas.
Usando este modelo, la API de la cola de tareas de App Engine le permite especificar tareas como solicitudes HTTP (tanto el contenido de la solicitud como sus datos, y la URL de destino de la solicitud como su código de referencia). La referencia programática a una solicitud HTTP agrupada de esta manera a veces se denomina "enlace web".
Es importante destacar que la naturaleza fuera de línea de la API de la cola de tareas le permite especificar enganches web antes de tiempo, sin esperar su ejecución real. Por lo tanto, una aplicación puede crear muchos enlaces web a la vez y luego entregarlos a App Engine; el sistema luego los procesará de forma asíncrona en segundo plano (invocando la solicitud HTTP). Este modelo de enlace web permite un procesamiento paralelo eficiente: App Engine puede invocar múltiples tareas, o enlaces web, simultáneamente.
Para resumir, la API de la cola de tareas permite que un desarrollador ejecute el trabajo en segundo plano, de forma asíncrona, dividiendo ese trabajo en enlaces web fuera de línea. El sistema invocará esos enlaces web en nombre de la aplicación, programando un rendimiento óptimo posiblemente ejecutando múltiples webhooks en paralelo. Este modelo de unidades de trabajo granulares, basado en el estándar HTTP, permite que App Engine realice de manera eficiente el procesamiento en segundo plano de manera que funcione con cualquier lenguaje de programación o marco de aplicaciones web.
Este parece ser el lugar perfecto para una cola de eventos en una base de datos.
Haga que sus eventos creados por el usuario (activados al visitar la página web) creen una entrada en la base de datos que incluya las instrucciones para que se lleve a cabo la acción y la fecha y hora para el momento en que debe ocurrir. Su Daemon (ya sea una aplicación persistente o desencadenada por CRON) verifica la base de datos en busca de eventos que deberían haber ocurrido ( $TriggerTime <= time()
) y que aún no se han marcado como "procesados". Si encuentra uno o más de estos eventos, ejecute la instrucción y finalmente marque el evento como "procesado" en la base de datos o simplemente elimine la entrada.
La ventaja de usar la base de datos para almacenar los eventos (y no algo que reside en la memoria RAM de una aplicación) es que puede recuperarse de una falla sin pérdida de datos, puede tener más de un trabajador leyendo en un solo evento en un Tiempo, y puede modificar el evento simplemente.
Además, hay mucha gente que usa PHP como un lenguaje de scripts de daemon general en servidores, etc. Cron puede ejecutar un script PHP (y confirmar que ya se está ejecutando una instancia de esa "aplicación") que verifica la cola de eventos de todos modos -a menudo. Puede tener una pequeña aplicación que muere después de un minuto de inactividad, y luego se reinicia con CRON. La aplicación puede verificar la base de datos en busca de entradas a una frecuencia rápida de su elección (como 1s). Normalmente Cron no puede hacer un evento de tiempo más rápido que una vez por minuto.
Existe una solución PHP pura. Más o menos lo que dijo Evan en su respuesta. La carga en DB se puede reducir (y bloquear el problema) simplemente introduciendo un estado de "Procesamiento" para los eventos. Cuando el script de procesamiento recoge los eventos de la cola (DB), se marcan como "Procesando" y se confirman. Una vez que el script termina, se marcan como "Procesados". Si hubo un error o la secuencia de comandos falla, los eventos de "Procesamiento" deben actualizarse nuevamente a su estado original. (Esto estaba destinado a ser un comentario a la respuesta de Evan, pero todavía no tengo suficiente reputación)
Haga que su script php realice una llamada ejecutiva para programar su script PHP para que se ejecute a la hora que necesita usando el comando "at"
exec ("a las 22:56 / usr / bin / php myscript.php");
at ejecuta comandos a una hora especificada.
de la página del manual:
En permite especificaciones de tiempo bastante complejas, extendiendo el estándar POSIX.2. Acepta horas del formulario HH: MM para ejecutar un trabajo a una hora específica del día. (Si esa hora ya ha pasado, se asume el día siguiente). También puede especificar la medianoche, el mediodía o la hora del té (4 pm) y puede tener un sufijo de hora del día con AM o PM para correr por la mañana o el noche. También puede decir en qué día se ejecutará el trabajo, dando una fecha en el formulario nombre mes con un año opcional, o dando una fecha del formulario MMDDYY o MM / DD / YY o DD.MM.YY. La especificación de una fecha debe seguir la especificación de la hora del día. También puede dar tiempos como ahora + contar unidades de tiempo, donde las unidades de tiempo pueden ser minutos, horas, días o semanas, y puede indicar la ejecución del trabajo de hoy con un sufijo del tiempo de hoy y el trabajo de mañana. Asfixiando el tiempo con mañana.
Además, si necesita una resolución de una segunda vez, haga que su script se ejecute al comienzo del minuto, luego solo duerma n segundos hasta que sea el momento de ejecutar.
No estoy seguro de por qué estás tratando de evitar el cron. Podría crear una cola de solicitudes en una tabla y hacer que cron inicie un proceso para verificar los trabajos actuales.
Hay algunos problemas, dependiendo de sus requisitos exactos. Por ejemplo:
- ¿Qué tan precisa debe ser la llamada?
- ¿Cuánto tiempo tomaría cada llamada?
- ¿Cuál es la carga normal y máxima en un período determinado?
Entonces, si desea una ejecución precisa, o la ejecución demora más de un segundo, o existe la posibilidad de una carga pesada, entonces el enfoque cron puede tener problemas.
Tengo muchos daeons que ejecutan PHP (usando daemontools). Con este enfoque, puede mantener las solicitudes en el núcleo y realizar cualquier tiempo que desee internamente.
Sin embargo, si lo que desea es una sincronización exacta y confiable, probablemente debería alejarse completamente de PHP.
No puedo pensar en nada que haga todo lo que pediste:
- tiene que ser muy preciso
- retraso por largos periodos de tiempo
- Posibilidad de eliminar / cambiar la hora del evento.
La forma trivial sería usar una combinación de las siguientes funciones:
set_time_limit(0);
ignore_user_abort(true);
time_sleep_until(strtotime(''next Friday''));
// execute code
Sin embargo, como @deceze dijo que probablemente no es una muy buena idea, ya que si configura un alto retraso, Apache podría eventualmente detener el proceso secundario (a menos que esté usando PHP CLI, eso lo haría más fácil). Tampoco le permite cambiar / eliminar el evento a menos que configure una lógica más compleja y una base de datos para contener los eventos. Además, register_shutdown_function()
puede ser útil si quieres ir por este camino.
Un mejor enfoque sería establecer un trabajo CRON en mi opinión.
Podría usar Node.JS, que es un servidor web basado en JavaScript y basado en eventos. Ejecútelo en un puerto interno secreto con un script que recibe una notificación del script PHP y luego programa la acción para que se ejecute xx segundos más tarde. La acción en Node.JS podría ser tan simple como ejecutar un script PHP en el servidor web principal.
Solo usaría cron para ejecutar un archivo PHP de vez en cuando (es decir, 5 minutos). El archivo PHP verificará si hay eventos que deban activarse en el siguiente intervalo, tomar la lista de eventos de intervalo y suspender hasta el próximo evento. Despierte, dispare el (los) siguiente (s) evento (s) en la lista, duerma hasta el siguiente, repita hasta que termine.
Incluso podría escalarlo bifurcando o lanzando otro archivo php para disparar el evento. Entonces podrías disparar más de un evento al mismo tiempo.
También recomiendo la estrategia de cola, pero parece que no te gusta usar la base de datos como cola. Tiene una infraestructura XMPP, así que pubsub : use un Nodo pubsub y publique sus eventos en este nodo. Pubsub puede configurarse opcionalmente para almacenar elementos sin llegar de forma persistente.
Su proceso de daemon (sin importar el idioma) puede recuperar todos los elementos almacenados en el momento del inicio y suscribirse a los cambios para recibir notificaciones sobre las acciones entrantes. De esta manera usted puede resolver su problema de una manera elegante y asíncrona.
Utilice la función de suspensión: http://php.net/sleep
- Almacena todas las tareas en una base de datos con tiempo de encendido
- Cron trabajo se ejecuta cada hora
- Lee los siguientes 60 minutos de trabajos.
- Bucle principal
- Salir si no hay nada que hacer
- Microsleep hasta el próximo trabajo
- Despacha todos los trabajos en tiempo de encendido <= ahora + 0.5 segundos