La evolución del procesamiento de webhook de Facebook: de cero a 25,000 por segundo

Lo más probable es que nadie necesite decir qué son los webhooks. Pero por si acaso: los webhooks son un mecanismo para informar eventos en un sistema externo. Por ejemplo, sobre comprar en una tienda en línea a través de un cajero en línea, enviar un código a un repositorio de GitHub o acciones del usuario en chats. En una API típica, debe consultar constantemente el servidor si el usuario escribió algo en el chat. Usando el mecanismo de webhook, puede "suscribirse" a las notificaciones, y el servidor mismo enviará una solicitud HTTP cuando ocurra un evento. Esto es más conveniente y más rápido que solicitar constantemente nuevos datos en el servidor.



ManyChat es una plataforma que ayuda a las empresas a comunicarse con sus clientes a través del chat en mensajería instantánea. Los webhooks son una de las partes importantes de ManyChat, porque es a través de ellos que la empresa se comunica con los clientes. Y se comunican mucho, por ejemplo, a través de un sistema, las empresas envían miles de millones de mensajes al mes a sus clientes.

La mayoría de los mensajes se envían a través de Facebook Messenger. Tiene una característica: una API lenta. Cuando un cliente escribe un mensaje para pedir pizza, Facebook envía un webhook a ManyChat. La plataforma lo procesa, devuelve la solicitud y el usuario recibe un mensaje. Debido a la lenta API, algunas solicitudes tardan unos segundos. Pero cuando la plataforma no responde durante mucho tiempo, la empresa pierde al cliente y Facebook puede desconectar la aplicación de los webhooks.

Por lo tanto, el procesamiento de webhooks es una de las principales tareas de ingeniería de la plataforma. Para resolver el problema, ManyChat cambió su arquitectura de procesamiento varias veces en el transcurso de tres años de un controlador simple en Yii a un sistema distribuido con Galaxias. Lea más sobre esto bajo el corte Dmitry Kushnikov (cancellarius)

Dmitry Kushnikov lidera el desarrollo en ManyChat y ha estado programando profesionalmente en PHP desde 2001. Dmitry le dirá cómo la arquitectura ha cambiado junto con el crecimiento del servicio y la carga, qué soluciones y tecnologías se aplicaron en diferentes etapas, cómo ha evolucionado el procesamiento de webhook y cómo la plataforma logra hacer frente a una carga enorme utilizando recursos modestos en PHP.

Nota. El artículo se basa en el informe de Dmitry "La evolución del procesamiento de webhook de Facebook: de cero a 12.500 por segundo" en PHP Rusia 2019 . Pero mientras se preparaba, los indicadores aumentaron a 25,000.


¿Qué es ManyChat?


Primero, te presentaré en el contexto de nuestras tareas. ManyChat es un servicio que ayuda a las empresas a utilizar mensajería instantánea para marketing, ventas y soporte. El producto principal es la plataforma de Messenger Marketing en Facebook Messenger . Durante tres años, más de 1 millón de empresas de 100 países del mundo utilizaron el servicio para comunicarse con 700 millones de sus clientes.

En el lado del cliente, se ve así.


Botones, imágenes y galerías en los cuadros de diálogo en Facebook Messenger.

Esta es la interfaz de Facebook Messenger. Además de los mensajes de texto, puede enviar elementos interactivos para interactuar con los clientes, dialogar, aumentar el interés en sus productos y vender.

Desde el lado comercialTodo se ve diferente. Esta es la interfaz de nuestra aplicación web donde, utilizando una interfaz visual, los representantes comerciales crean y programan guiones de diálogo. La imagen es un ejemplo de un escenario.


El corazón de nuestro sistema es el componente Flow Builder.

El conjunto de scripts y reglas de automatización que llamamos bot . Por lo tanto, para simplificar, podemos decir que ManyChat es un diseñador de bot.


Un ejemplo de un bot.

El cliente de la empresa que participa en el diálogo se llama suscriptor , porque para la interacción, el cliente se suscribe al bot .

Por que facebook


¿Por qué Facebook Messenger, somos el país del Telegrama sobreviviente? Hay razones para esto.

  • Telegram , №1 Facebook. 1,5 , Telegram 200-300 .
  • Facebook , . , Facebook - .
  • Facebook F8 - 300 . Facebook Messenger. 20 . ManyChat 40%.

Facebook


La interacción con Facebook se organiza de la siguiente manera: las



empresas utilizan una aplicación web para configurar la lógica del bot. Cuando un cliente interactúa con el bot por teléfono, Facebook recibe información al respecto y nos envía un webhook. ManyChat lo procesa, dependiendo de la lógica programada por la empresa, y envía la solicitud de regreso. Luego Facebook entrega el mensaje al teléfono del usuario.

Pila tecnológica


Hacemos todo esto en una pila modesta. En el núcleo, por supuesto, está PHP. El servidor web ejecuta Nginx, la base de datos principal es PostgreSQL, y también hay Redis y Elasticsearch. Todo gira en las nubes de Amazon Web Services.

Manejo de Webhook de Facebook


Así es como se ve la cámara web de Facebook: esta es una solicitud con carga útil en formato JSON.

{
    "object":"page",
    "entry":[
        }
            "id":"<PAGE_ID>",
            "time":1458692752478,
            "messaging":[
                {
                    "sender":{
                        "id":"<PSID>"
                    },
                    "recipient":{
                        "id":"<PAGE_ID>"
                    },

                    ...
                }
            ]  
        }
    ]
}

Los webhooks son solo el 10% de nuestra carga, pero son la parte más importante del sistema. A través de ellos, la empresa se comunica con los usuarios. Si los mensajes se ralentizan o no se envían, el usuario se niega a interactuar con el bot y la empresa pierde al cliente.

Echemos un vistazo a la evolución de nuestra arquitectura desde el lanzamiento del producto.

Mayo de 2016 . Acabamos de lanzar nuestro servicio: 20 bots, 10 de los cuales son de prueba y 20 suscriptores. La carga fue de 0 RPS.

El esquema de interacción se veía así:



  • La solicitud va a nginx.
  • Nginx accede a PHP-FPM.
  • PHP-FPM lleva la aplicación hasta Yii.
  • El controlador de webhook procesa la lógica y envía solicitudes a Facebook de acuerdo con ella.


Un montón de Nginx y PHP-FPM


Junio ​​de 2016. Un mes después, anunciamos ManyChat en ProductHunt y el número de bots aumentó a 2 mil. El número de suscriptores ha aumentado a 7 mil.

En este momento, el primer problema apareció en el sistema. La API de Facebook no es muy rápida: algunas solicitudes pueden demorar varios segundos y varias solicitudes pueden demorar decenas de segundos. Pero el servidor webhook quiere que respondamos rápidamente. Debido a la lenta API, no respondemos durante mucho tiempo: el servidor primero jura y luego puede desconectar completamente la aplicación de los webhooks.

Hay pocos usuarios, todavía estamos desarrollando la aplicación, estamos buscando nuestro mercado, audiencia y el problema de carga ya ha aparecido. Pero nos salvó una solución simple: en el momento en que se inicia el controlador, interrumpimos el acceso a Facebook. Le decimos a Facebook que todo está bien, pero en el fondo procesamos solicitudes y webhook.



Colas en PostgreSQL


Diciembre de 2016. El servicio creció 5-10 veces: 10 mil bots y 700 mil suscriptores.

Al mismo tiempo, trabajamos en nuevas tareas: mostrar estadísticas, entrega de mensajes, conversión de impresiones y transiciones. También se implementó Live Chat. Además de automatizar las interacciones, ofrece a las empresas la capacidad de escribir mensajes directamente a sus suscriptores.

La solución a estos problemas aumentó el número de ganchos seguidos en 4 veces. Por cada mensaje que enviamos, recibimos 3 webhooks adicionales. El sistema de procesamiento necesitaba ser mejorado nuevamente. Somos una plataforma pequeña, solo dos personas trabajaron en el back-end, por lo que elegimos la solución más simple: colas en PostgreSQL.

Todavía no queremos implementar sistemas complejos, por lo que simplemente compartimos los flujos de procesamiento. Los webhooks que deben procesarse rápidamente para que el usuario reciba una respuesta se procesan sincrónicamente. Todo lo demás se envía en colas para solicitudes asincrónicas.



Colas en Redis


Junio ​​de 2017. El servicio está creciendo: 75 mil bots, 7 millones de suscriptores.

Estamos implementando otra nueva característica. Todos los webhooks que procesamos se referían solo a las comunicaciones en el messenger. Pero ahora decidimos dar a las empresas la oportunidad de comunicarse con los suscriptores de las páginas de negocios y comenzamos a procesar nuevos tipos de webhooks, aquellos que se relacionan con el feed de la página.

Los feeds de páginas comerciales no se actualizan con poca frecuencia. Los especialistas en marketing a menudo publican algo, luego siguen cada me gusta y los cuentan. No hay mucho tráfico en las páginas de negocios. Pero hay situaciones inversas, por ejemplo, Katy Perry Day .

Katy Perry es una famosa cantante estadounidense con una gran cantidad de fanáticos en todo el mundo. Hay 64 millones de suscriptores solo en su grupo de Facebook. En algún momento, los vendedores del cantante decidieron hacer un bot en Facebook Messenger y eligieron nuestra plataforma. En ese momento, cuando publicaron un mensaje llamando a suscribirse al bot, nuestra carga aumentó 3-4 veces.

Esta situación nos ayudó a comprender que sin la implementación normal de las colas, no podemos hacer nada. Como solución, eligieron Redis.
Elegir Redis para las colas es una decisión fantásticamente buena.
Ayudó a resolver una gran cantidad de problemas. Ahora, cada segundo a través de nuestro clúster Redis pasa 1 millón de solicitudes diferentes. Lo usamos no solo para todas las colas en cascada, sino también para otras tareas, por ejemplo, monitoreo.

Las colas en Redis no se implementaron en el primer intento. Cuando comenzamos a doblar webhooks en Redis y a procesarlos en un solo proceso, expandimos el embudo en la parte superior: también se procesaron más webhooks entrantes, pero el proceso en sí todavía tomó algo de tiempo. Esta primera decisión no tuvo éxito.



Cuando intentaron escalar el número de estas solicitudes, hubo un ligero colapso. La cola puede acumular solicitudes de diferentes páginas, pero las solicitudes de una página pueden ir en una fila. Si un controlador es lento, las solicitudes de un suscriptor y de un bot se procesarán en el orden incorrecto. El usuario envía mensajes, realiza algunas acciones con el bot, pero recibe una respuesta al azar.



Este parece ser un caso raro, pero las pruebas en nuestras cargas de trabajo han demostrado que esto sucederá con frecuencia.

Comenzamos a buscar otra solución. Aquí la simplicidad y el poder de Redis vinieron al rescate: decidimos hacer una cola para cada bot .



¿Cómo funciona? Los mensajes relacionados con cada bot se agregan a la cola. Para no subir el controlador a cada cola, hicimos una cola de control . Ella trabaja asi. Cada vez que una solicitud proviene de un bot, se publican dos mensajes en Redis: uno en la cola del bot y el segundo en el control. El controlador supervisa el control y cada vez que inicia el demonio cuando hay una tarea para procesar el bot. El demonio rastrilla la cola del bot correspondiente.

Además de la tarea principal, resolvimos el problema de los "vecinos ruidosos". Esto es cuando un bot generó una gran cantidad de webhooks y ralentiza el sistema, porque otras páginas están esperando el procesamiento. Para resolver el problema, es suficiente escalar : cuando la cola de control está llena, agregamos nuevos controladores.

Además, las colas son virtuales . Estas son solo células en la memoria de Redis. Cuando no hay nada en la cola, no existe, no ocupa nada.

ReactPHP


Enero 2018 . Hemos alcanzado mil millones de publicaciones por mes.

La carga fue de 5 mil RPS por sistema. Esto no es carga máxima, sino estándar. Cuando aparecen bots de cantantes famosos, todo crece varias veces a partir de esta figura. Pero no es un problema. El problema está en PHP-FPM: ya no puede soportar la carga de 5 mil RPS.

Todos en ese momento hablaban sobre el procesamiento asincrónico de moda. Lo miramos más de cerca, vimos ReactPHP, realizamos pruebas rápidas, lo reemplazamos con PHP-FPM e instantáneamente obtuvimos un aumento de 4 veces.



No reescribimos el procesamiento de nuestro procesamiento: ReactPHP planteó el marco Yii. Primero, planteamos 4 servicios ReactPHP, y luego llegamos a 30. Durante mucho tiempo vivimos de ellos, y el marco hizo frente a la carga.

Tan pronto como expandimos el embudo, ocurrió otro colapso: después de comenzar el embudo en la recepción, el procesamiento comenzó a sufrir nuevamente. Para resolver este problema ya, decidimos separar el procesamiento en grupos.

Racimos


Tomaron bots, los distribuyeron en grupos y construyeron cadenas lógicas de Redis, Postgres y un controlador.



Como resultado, hemos formado el concepto de "Galaxy", una abstracción física lógica sobre el procesamiento . Consiste en instancias: Redis, PostgreSQL y un conjunto de servicios PHP. Cada bot pertenece a un grupo particular, y ReactPHP sabe en qué grupo debe colocarse el mensaje para este bot. El esquema anterior funciona más allá.


El Universo se está expandiendo, el Universo de nuestros sistemas también, y agregamos una nueva "Galaxia" cuando esto sucede.
Las galaxias son nuestra forma de escalar.

Reemplazando ReactPHP con un montón de Nginx y Lua


Durante los siguientes seis meses, seguimos creciendo: 200 millones de suscriptores y 3 mil millones de mensajes por mes. Imagine un sitio para 200 millones de usuarios registrados, la misma carga.

Ha surgido un nuevo problema. Los webhooks son pequeñas tareas del mismo tipo, y PHP no es adecuado para resolverlos. Incluso ReactPHP ya no ayudó.

  • No pudo hacer frente a la carga de 10 mil RPS: desde la introducción de ReactPHP, la carga ha aumentado.
  • Fue necesario reiniciarlo incluso con implementaciones, además, secuencialmente, porque no puede interrumpir el procesamiento de los webhooks entrantes. Facebook desactiva la aplicación cuando se da cuenta de que tiene problemas. Para ManyChat, esto es un desastre: 650 mil empresas que operan activamente no nos perdonarán.

Por lo tanto, gradualmente mordimos lógica diferente de ReactPHP, la pasamos a los procesadores y aislamos nuevas colas. En el proceso, notaron que ReactPHP realiza una tarea simple: toma un webhook y lo pone en una cola . Todo lo demás se realiza mediante procesamientos. ¿Hay análogos para una tarea tan simple?

Recordamos que Nginx tiene módulos y notamos la biblioteca OpenResty . Además de soportar el lenguaje de programación Lua, tenía un módulo para trabajar con Redis. Una prueba escrita en 3 horas mostró que todo el trabajo de 30 servicios en ReactPHP se puede hacer directamente en el lado nginx.



Así es como resultó: procesamos algún tipo de punto final, recogemos el cuerpo de la solicitud y lo agregamos directamente a Redis.

location / {
    error_log /var/log/nginx/error.log;

    resolver ###resolver###;

    content_by_lua '

        ngx.req.read_body()
        local mybody = ngx.req.get_body_data()

        if not mybody then
            return ngx.exit(400)
        end

        local hash = ngx.crc32_long(mybody)
        local cluster = hash % ###wh_inbound_shards### + 1

        local redis = require "resty.redis";
        local red = radis.new()
        red:set_timeout(3000)

        local ok, err = red:connect("###redisConnectionWh2.server.host###", 6379)
		
        if not ok then
            ngx.log(ngx.ERR, err, "Redis failed to connect")
            return ngx.exit(403)
        end

        local ok, err = red:rpush("###wh_inbound_queue###" .. queuesuffix .. cluster, mybody)
        
        if not ok then
            ngx.log(ngx.ERR, err, "Failed to write data", mybody)
            return ngx.exit(500)
        end

        local ok, err = red:set_keepalive(10000, 100)

        ngn.say("ok")
    ';
}

OpenResty y Lua han ayudado a aumentar el rendimiento. Continuamos haciendo frente a nuestra carga de trabajo, el servicio sigue vivo, todos están felices.

Mejorando la solución en Lua


La última etapa ( nota: en el momento del informe ) es febrero de 2019 . 500 millones de suscriptores envían y reciben 7 millones de mensajes de un millón de bots cada mes.

Este es un paso para mejorar nuestra solución en Lua. Gradualmente, elimine parte de la lógica de las colas y transfiera el procesamiento primario de distribución de webhooks entre sistemas a Lua. Ahora nuestros sistemas son más productivos y menos dependientes.



Mantenemos procesamiento separado y procesamiento asincrónico . El procesamiento se refiere a estadísticas y otras cosas: ahora es un sistema completamente diferente.

El sistema parece simple, pero no lo es. Debajo del capó, hay 500 servicios que procesan sus solicitudes. Todo el sistema se ejecuta en 50 instancias de Amazon: Redis, PostgreSQL y los mismos controladores PHP.

Evolución de procesamiento


Highload puede ser genial para hacer en PHP.

Recuerde brevemente cómo lo hicimos en el proceso de desarrollo del sistema.

  • Comenzó con Nginx y PHP-FPM normales.
  • Se agregaron colas a PostgreSQL y luego a Redis.
  • Clustering agregado.
  • Implementado ReactPHP.
  • Reemplazamos ReactPHP con un montón de Nginx y Lua, y luego trasladamos la lógica al grupo.



Según nuestra experiencia, descubrimos que es posible crecer y construir arquitectura cambiando sucesivamente las partes vulnerables, utilizando enfoques simples y conocidos y, al mismo tiempo, no expandiendo la pila.

, , 11 TeamLead Conf. , LeSS, .

PHP Russia Saint HighLoad++, . PHP , — PHP Russia 13 . highload PHP, Saint HighLoad++ .

Source: https://habr.com/ru/post/undefined/


All Articles