Notas Previas al Desarrollo
Al desarrollar aplicaciones con Workerman, necesitas conocer lo siguiente:
I. Diferencias entre el desarrollo en Workerman y el desarrollo PHP tradicional
Aparte de que las funciones de variables relacionadas con el protocolo HTTP no pueden ser utilizadas directamente, el desarrollo en Workerman no difiere mucho del desarrollo PHP tradicional.
1. Diferencias en el protocolo de la capa de aplicación
- El desarrollo PHP tradicional suele basarse en el protocolo de la capa de aplicación HTTP, donde el WebServer ya ha realizado el análisis del protocolo para el desarrollador.
-
Workerman admite varios protocolos, teniendo incorporados protocolos como HTTP y WebSocket. Se recomienda a los desarrolladores utilizar protocolos personalizados más simples para la comunicación.
- Para el desarrollo con el protocolo HTTP, consulta la sección de servicios HTTP.
2. Diferencias en el ciclo de vida de las solicitudes
- PHP liberará todas las variables y recursos tras completar una solicitud en una aplicación web.
- Las aplicaciones desarrolladas con Workerman permanecen en memoria después de su primera carga y análisis, lo que significa que las definiciones de clases, objetos globales y miembros estáticos de las clases no se liberan, permitiendo su reutilización posterior.
3. Precaución con la redefinición de clases y constantes
- Dado que Workerman almacenará en caché los archivos PHP compilados, hay que evitar requerir o incluir varias veces el mismo archivo de definición de clases o constantes. Se recomienda usar
require_once/include_oncepara cargar archivos.
4. Precaución con la liberación de recursos de conexiones en el patrón Singleton
- Debido a que Workerman no libera objetos globales ni miembros estáticos de clases tras cada solicitud, en patrones Singleton como el de bases de datos, generalmente se conservará la instancia de la base de datos (que contiene una conexión de socket de base de datos) en miembros estáticos de la base de datos, lo que lleva a que Workerman reutilice esta conexión de socket de base de datos durante el ciclo de vida del proceso. Es importante tener en cuenta que si el servidor de base de datos detecta que una conexión ha estado inactiva durante un período de tiempo, puede cerrar activamente la conexión del socket, y al intentar volver a usar esta instancia de base de datos se producirá un error (un mensaje de error similar a "mysql gone away"). Workerman proporciona una clase de base de datos que tiene la función de reconexión, y los desarrolladores pueden utilizarla directamente.
5. Precaución al no usar exit o die
- Workerman se ejecuta en modo de línea de comandos de PHP. Al llamar a
exitodie, se saldrá del proceso actual. Aunque el subproceso se volverá a crear inmediatamente para continuar el servicio, esto podría afectar el funcionamiento del negocio.
6. Los cambios de código requieren reiniciar el servicio para que surtan efecto
Debido a que Workerman reside en memoria, las definiciones y cargas de funciones de clases PHP se mantienen en memoria tras una única carga, y no se volverán a leer desde el disco. Por lo tanto, se requiere un reinicio después de cada modificación del código del negocio para que surta efecto.
II. Conceptos Básicos a Tener en Cuenta
1. Protocolo de transporte TCP
TCP es un protocolo de transporte orientado a la conexión, confiable y basado en IP. Una característica importante del protocolo de transporte TCP es que se basa en flujos de datos, donde las solicitudes del cliente se envían continuamente al servidor, y los datos recibidos por el servidor pueden no ser una solicitud completa, o pueden ser múltiples solicitudes concatenadas. Esto requiere que tengamos que distinguir los límites de cada solicitud en este constante flujo de datos. El protocolo de la capa de aplicación define un conjunto de reglas para los límites de las solicitudes, evitando la confusión de los datos de las solicitudes.
2. Protocolo de capa de aplicación
El protocolo de capa de aplicación (application layer protocol) define cómo los procesos de aplicación que se ejecutan en diferentes sistemas finales (cliente, servidor) intercambian mensajes, por ejemplo, HTTP y WebSocket son protocolos de capa de aplicación. Un protocolo de capa de aplicación simple podría ser {"module":"user","action":"getInfo","uid":456}\n" en el que la solicitud finaliza con "\n" (nota que aquí "\n" representa un salto de línea), y el cuerpo del mensaje es una cadena.
3. Conexiones cortas
Una conexión corta se refiere a establecer una conexión al interactuar los dos lados de la comunicación, y después de completar el envío de datos, se cierra esta conexión, es decir, cada conexión solo completa una tarea de envío. Por lo general, los servicios HTTP en sitios WEB utilizan conexiones cortas.
Para el desarrollo de aplicaciones de conexiones cortas, consulta el capítulo sobre el flujo de desarrollo básico.
4. Conexiones largas
Una conexión larga permite el envío de múltiples paquetes de datos sobre una única conexión.
Nota: Las aplicaciones de conexiones largas deben incluir heartbeat; de lo contrario, la conexión puede ser interrumpida por un firewall de nodo de enrutamiento debido a la inactividad prolongada.
Las conexiones largas se utilizan comúnmente en situaciones donde la operación es frecuente y la comunicación es punto a punto. Cada conexión TCP requiere tres pasos de apretón de manos, lo que consume tiempo. Si cada operación requiere primero una conexión y luego la operación, la velocidad de procesamiento se verá muy afectada. Así que, en lugar de cerrar la conexión después de cada operación, la conexión se mantiene abierta para poder enviar directamente los paquetes de datos en la siguiente operación, sin tener que establecer una nueva conexión TCP. Por ejemplo, las conexiones a bases de datos utilizan conexiones largas. Usar conexiones cortas para comunicaciones frecuentes puede causar errores de socket y la creación frecuente de sockets también desperdicia recursos.
Cuando se necesite enviar datos activamente a un cliente, como en aplicaciones de chat, juegos en tiempo real, notificaciones móviles, etc., se requieren conexiones largas.
Para el desarrollo de aplicaciones de conexiones largas, consulta el flujo de desarrollo de Gateway/Worker.
5. Reinicio suave
El proceso tradicional de reinicio implica detener todos los procesos y luego comenzar a crear nuevos procesos de servicio. Durante este proceso, habrá un breve período de tiempo en el que no hay procesos ofreciendo servicio, lo que puede causar que el servicio esté temporalmente indisponible, lo que inevitablemente conducirá a fracasos en las solicitudes en situaciones de alta concurrencia.
En cambio, el reinicio suave no detiene todos los procesos a la vez, sino que detiene uno a uno. Después de detener cada proceso, se crea inmediatamente un nuevo proceso para reemplazarlo, hasta que todos los procesos antiguos hayan sido reemplazados.
Para reiniciar Workerman de forma suave, se puede utilizar el comando php your_file.php reload, lo que permite actualizar la aplicación sin afectar la calidad del servicio.
Nota: Solo los archivos cargados en el callback de on{...} se actualizarán automáticamente después del reinicio suave. Los archivos cargados directamente en el script de inicio o el código duro no se actualizarán automáticamente al ejecutar reload.
III. Diferenciación entre el proceso principal y los procesos hijos
Es importante notar si el código se ejecuta en el proceso principal o en un proceso hijo; en general, el código que se ejecuta antes de la llamada a Worker::runAll(); se ejecuta en el proceso principal, mientras que el código que se ejecuta dentro de los callbacks onXXX pertenece a procesos hijos. Ten en cuenta que el código escrito después de Worker::runAll(); nunca se ejecutará.
Por ejemplo, en el siguiente código:
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
// Ejecutado en el proceso principal
$tcp_worker = new Worker("tcp://0.0.0.0:2347");
// Asignación ejecutada en el proceso principal
$tcp_worker->onMessage = function(TcpConnection $connection, $data)
{
// Esta parte se ejecuta en el proceso hijo
$connection->send('hello ' . $data);
};
Worker::runAll();
Nota: No inicialices conexiones a bases de datos, memcache, redis, etc., en el proceso principal, ya que las conexiones inicializadas en el proceso principal pueden ser heredadas automáticamente por los procesos hijos (especialmente cuando se usan singletons), lo que significa que todos los procesos tendrán la misma conexión, y los datos devueltos por el servidor a través de esta conexión serán legibles en múltiples procesos, lo que puede causar inconsistencia en los datos. Del mismo modo, si cualquier proceso cierra una conexión (por ejemplo, si el proceso principal sale durante la ejecución en modo daemon, esto provocará que la conexión se cierre), todas las conexiones de los procesos hijos también se cerrarán, lo que provocará errores impredecibles, como el error "mysql gone away".
Se recomienda inicializar las conexiones en el callback de onWorkerStart.