A evolução do processamento de webhook no Facebook: de zero a 25.000 por segundo

Provavelmente, ninguém precisa dizer o que são webhooks. Mas, apenas no caso: webhooks são um mecanismo para relatar eventos em um sistema externo. Por exemplo, sobre comprar em uma loja online através de uma caixa online, enviar um código para um repositório GitHub ou ações do usuário em bate-papos. Em uma API típica, você precisa consultar constantemente o servidor se o usuário escreveu algo no chat. Usando o mecanismo de webhook, você pode "assinar" as notificações, e o próprio servidor enviará uma solicitação HTTP quando ocorrer um evento. Isso é mais conveniente e rápido do que a solicitação constante de novos dados no servidor.



ManyChat é uma plataforma que ajuda as empresas a se comunicar com seus clientes via chat em mensagens instantâneas. Os webhooks são uma das partes importantes do ManyChat, porque é através deles que a empresa se comunica com os clientes. E eles se comunicam muito - por exemplo, através de um sistema, as empresas enviam bilhões de mensagens por mês para seus clientes.

A maioria das mensagens é enviada via Facebook Messenger. Ele tem um recurso - uma API lenta. Quando um cliente escreve uma mensagem para pedir pizza, o Facebook envia um webhook para o ManyChat. A plataforma a processa, envia a solicitação de volta e o usuário recebe uma mensagem. Devido à lenta API, alguns pedidos demoram alguns segundos. Mas quando a plataforma não responde por muito tempo, a empresa perde o cliente e o Facebook pode desconectar o aplicativo dos webhooks.

Portanto, o processamento de webhooks é uma das principais tarefas de engenharia da plataforma. Para resolver o problema, o ManyChat mudou sua arquitetura de processamento várias vezes ao longo de três anos, de um simples controlador no Yii para um sistema distribuído com Galaxies. Leia mais sobre isso sob o corte Dmitry Kushnikov (cancellarius)

Dmitry Kushnikov lidera o desenvolvimento no ManyChat e programa profissionalmente em PHP desde 2001. Dmitry lhe dirá como a arquitetura mudou junto com o crescimento do serviço e da carga, quais soluções e tecnologias foram aplicadas em diferentes estágios, como o processamento do webhook evoluiu e como a plataforma consegue lidar com uma carga enorme usando recursos modestos em PHP.

Nota. O artigo é baseado no relatório de Dmitry “A evolução do processamento de webhook no Facebook: de zero a 12.500 por segundo” no PHP Rússia 2019 . Mas enquanto ele se preparava, os indicadores subiram para 25.000.


O que é o ManyChat


Primeiro, apresentarei você ao contexto de nossas tarefas. ManyChat é um serviço que ajuda as empresas a usar mensageiros instantâneos para marketing, vendas e suporte. O principal produto é a plataforma para o Messenger Marketing no Facebook Messenger . Durante três anos, mais de 1 milhão de empresas de 100 países do mundo usaram o serviço para se comunicar com 700 milhões de seus clientes.

No lado do cliente, fica assim.


Botões, imagens e galerias nas caixas de diálogo do Facebook Messenger.

Esta é a interface do Facebook Messenger. Além das mensagens de texto, você pode enviar elementos interativos para interagir com os clientes, dialogar, aumentar o interesse em seus produtos e vender.

Do lado comercialtudo parece diferente. Essa é a interface do nosso aplicativo da Web onde, usando uma interface visual, representantes comerciais criam e programam scripts de diálogo. A imagem é um exemplo de cenário.


O coração do nosso sistema é o componente Flow Builder.

O conjunto de scripts e regras de automação que chamamos de bot . Portanto, para simplificar, podemos dizer que o ManyChat é um designer de bot.


Um exemplo de um bot.

O cliente da empresa que participa do diálogo é chamado de assinante , porque, para interação, o cliente assina o bot .

Por que o Facebook


Por que o Facebook Messenger, nós somos o país do Telegram sobrevivente? Há razões para isso.

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

Facebook


A interação com o Facebook é organizada da seguinte maneira: os



negócios usam um aplicativo da web para configurar a lógica do bot. Quando um cliente interage com o bot por telefone, o Facebook recebe informações sobre isso e nos envia um webhook. O ManyChat o processa, dependendo da lógica programada pela empresa, e envia a solicitação de volta. Em seguida, o Facebook entrega a mensagem ao telefone do usuário.

Pilha tecnológica


Fazemos tudo isso em uma pilha modesta. O núcleo, é claro, é o PHP. O servidor web executa o Nginx, o banco de dados principal é o PostgreSQL e também existem Redis e Elasticsearch. Tudo gira nas nuvens do Amazon Web Services.

Manipulação do Facebook Webhook


É assim que a webcam do Facebook se parece: esta é uma solicitação com carga no formato JSON.

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

                    ...
                }
            ]  
        }
    ]
}

Os webhooks representam apenas 10% da nossa carga, mas a parte mais importante do sistema. Por meio deles, a empresa se comunica com os usuários. Se as mensagens diminuem a velocidade ou não são enviadas, o usuário se recusa a interagir com o bot e a empresa perde o cliente.

Vamos dar uma olhada na evolução de nossa arquitetura desde o lançamento do produto.

Maio de 2016 . Acabamos de lançar nosso serviço: 20 bots, 10 dos quais são de teste e 20 assinantes. A carga foi 0 RPS.

O esquema de interação ficou assim:



  • A solicitação vai para o nginx.
  • O Nginx acessa o PHP-FPM.
  • O PHP-FPM leva o aplicativo até o Yii.
  • O controlador webhook processa a lógica e envia solicitações ao Facebook de acordo com ela.


Um monte de Nginx e PHP-FPM


Junho de 2016. Um mês depois, anunciamos o ManyChat no ProductHunt e o número de bots aumentou para 2 mil. O número de assinantes aumentou para 7 mil.

Nesse momento, o primeiro problema apareceu no sistema. A API do Facebook não é muito rápida: algumas solicitações podem levar vários segundos e várias solicitações podem levar dezenas de segundos. Mas o servidor webhook quer que respondamos rapidamente. Devido à lenta API, não respondemos por um longo tempo: o servidor jura primeiro e, em seguida, pode desconectar completamente o aplicativo dos webhooks.

Existem poucos usuários, ainda estamos desenvolvendo o aplicativo, estamos procurando nosso mercado, público e o problema de carga já apareceu. Mas fomos salvos por uma solução simples: no momento em que o controlador é iniciado, interrompemos o acesso ao Facebook. Dizemos ao Facebook que está tudo bem, mas em segundo plano processamos solicitações e webhook.



Filas no PostgreSQL


Dezembro de 2016. O serviço cresceu 5 a 10 vezes: 10 mil bots e 700 mil assinantes.

Ao mesmo tempo, trabalhamos em novas tarefas: exibição de estatísticas, entrega de mensagens, conversão de impressões e transições. Também implementou o Live Chat. Além de automatizar as interações, oferece às empresas a capacidade de escrever mensagens diretamente para seus assinantes.

A solução para esses problemas aumentou o número de ganchos rastreados em 4 vezes. Para cada mensagem que enviamos, recebemos 3 webhooks adicionais. O sistema de processamento precisava ser aprimorado novamente. Como somos uma plataforma pequena, apenas duas pessoas trabalhavam no back-end, por isso escolhemos a solução mais simples - filas no PostgreSQL.

Ainda não queremos implementar sistemas complexos, então simplesmente compartilhamos os fluxos de processamento. Webhooks que precisam ser processados ​​rapidamente para que o usuário receba uma resposta são processados ​​de forma síncrona. Todo o resto é enviado em filas para solicitações assíncronas.



Filas na Redis


Junho de 2017. O serviço está crescendo: 75 mil bots, 7 milhões de assinantes.

Estamos implementando outro novo recurso. Todos os webhooks que processamos diziam apenas respeito a comunicações no messenger. Mas agora decidimos dar às empresas a oportunidade de se comunicar com os assinantes das páginas comerciais e começamos a processar novos tipos de webhooks - aqueles relacionados ao feed da própria página.

Os feeds de página comercial não são atualizados com frequência. Os profissionais de marketing costumam postar algo, então eles seguem cada um deles e os contam. Não há tráfego enorme nas páginas comerciais. Mas há situações inversas, por exemplo, Katy Perry Day .

Katy Perry é uma famosa cantora americana com um grande número de fãs em todo o mundo. Existem 64 milhões de assinantes apenas no seu grupo no Facebook. Em algum momento, os profissionais de marketing da cantora decidiram fazer um bot no Facebook Messenger e escolheram a nossa plataforma. Nesse momento, quando eles publicaram uma mensagem pedindo para se inscrever no bot, nossa carga aumentou de 3 a 4 vezes.

Essa situação nos ajudou a entender que, sem a implementação normal das filas, não podemos fazer nada. Como solução, eles escolheram o Redis.
Escolher Redis para filas é uma decisão fantasticamente boa.
Ele ajudou a resolver um grande número de problemas. Agora, a cada segundo em nosso cluster Redis, são transmitidos 1 milhão de solicitações diferentes. Nós o usamos não apenas para todas as filas em cascata, mas também para outras tarefas, por exemplo, monitoramento.

Filas no Redis não foram implementadas na primeira tentativa. Quando começamos a dobrar webhooks no Redis e processá-los em um processo, expandimos o funil na parte superior: havia mais webhooks recebidos processados ​​também, mas o processo em si ainda levou algum tempo. Esta primeira decisão não teve êxito.



Quando eles tentaram escalar o número desses pedidos, houve um leve colapso. A fila pode acumular solicitações de páginas diferentes, mas solicitações de uma página podem seguir uma linha. Se um manipulador for lento, as solicitações de um assinante e de um bot serão processadas na ordem errada. O usuário envia mensagens, executa algumas ações com o bot, mas recebe uma resposta aleatoriamente.



Esse parece ser um caso raro, mas os testes em nossas cargas de trabalho mostraram que isso acontecerá com frequência.

Começamos a procurar outra solução. Aqui, a simplicidade e o poder dos Redis vieram em socorro - decidimos fazer uma fila para cada bot .



Como funciona? Mensagens relacionadas a cada bot são adicionadas à fila. Para não elevar o manipulador para cada fila, fizemos uma fila de controle . Ela trabalha assim. Cada vez que uma solicitação vem de um bot, duas mensagens são publicadas no Redis: uma na fila do bot e a segunda no controle. O manipulador monitora o controle e cada vez que inicia o daemon quando há uma tarefa para processar o bot. O demônio percorre a fila do bot correspondente.

Além da tarefa principal, resolvemos o problema dos “vizinhos barulhentos”. É quando um bot gera uma enorme massa de webhooks e torna o sistema mais lento, porque outras páginas estão aguardando processamento. Para resolver o problema, basta escalar : quando a fila de controle está cheia, adicionamos novos manipuladores.

Além disso, as filas são virtuais . Estas são apenas células na memória Redis. Quando não há nada na fila, ele não existe, não ocupa nada.

ReactPHP


Janeiro de 2018 . Atingimos 1 bilhão de posts por mês.

A carga foi de 5 mil RPS por sistema. Isso não é carga de pico, mas padrão. Quando muitos artistas famosos aparecem, tudo já cresce várias vezes a partir dessa figura. Mas não é um problema. O problema está no PHP-FPM: ele não pode mais suportar a carga de 5 mil RPS.

Todo mundo naquela época estava falando sobre o processamento assíncrono da moda. Examinamos mais de perto, vimos o ReactPHP, realizamos testes rápidos, o substituímos pelo PHP-FPM e instantaneamente tivemos um aumento de 4 vezes.



Não reescrevemos o processamento do nosso processamento - o ReactPHP elevou a estrutura do Yii. Primeiro, criamos 4 serviços ReactPHP e, mais tarde, atingimos 30. Por muito tempo, vivemos neles, e a estrutura lidou com a carga.

Assim que expandimos o funil, ocorreu outro colapso: depois de iniciar o funil na recepção, o processamento começou a sofrer novamente. Para resolver esse problema, decidimos separar o processamento em clusters.

Clusters


Eles pegaram bots, os distribuíram em grupos e construíram cadeias lógicas de Redis, Postgres e um manipulador.



Como resultado, formamos o conceito de "Galáxia" - uma abstração física lógica sobre o processamento . Consiste em instâncias: Redis, PostgreSQL e um conjunto de serviços PHP. Cada bot pertence a um cluster específico e o ReactPHP sabe em qual cluster a mensagem para esse bot precisa ser inserida. O esquema acima funciona mais.


O universo está se expandindo, o universo de nossos sistemas também, e adicionamos uma nova "galáxia" quando isso acontece.
Galáxias são a nossa maneira de escalar.

Substituindo o ReactPHP por um monte de Nginx e Lua


Nos seis meses seguintes, continuamos a crescer: 200 milhões de assinantes e 3 bilhões de mensagens por mês. Imagine um site para 200 milhões de usuários registrados - a mesma carga.

Um novo problema surgiu. Webhooks são pequenas tarefas do mesmo tipo e o PHP não é adequado para resolvê-las. Mesmo o ReactPHP não ajudou mais.

  • Ele não conseguiu lidar com a carga de 10 mil RPS - desde a introdução do ReactPHP, a carga aumentou.
  • Além disso, era necessário reiniciá-lo mesmo com implantações, sequencialmente, porque você não pode interromper o processamento de webhooks de entrada. O Facebook desativa o aplicativo quando percebe que está com problemas. Para ManyChat, isso é um desastre - 650 mil empresas que operam ativamente não nos perdoarão.

Portanto, gradualmente cortamos a lógica diferente do ReactPHP, passamos para os processadores e isolamos novas filas. No processo, eles perceberam que o ReactPHP executa uma tarefa simples - pega um webhook e o coloca em uma fila . Todo o resto é feito por processamentos. Existem análogos para uma tarefa tão simples?

Lembramos que o Nginx possui módulos e notamos a biblioteca OpenResty . Além de suportar a linguagem de programação Lua, ela tinha um módulo para trabalhar com Redis. Um teste escrito em 3 horas mostrou que todo o trabalho de 30 serviços no ReactPHP pode ser feito diretamente no lado do nginx.



Foi assim: processamos algum tipo de terminal, selecionamos o corpo da solicitação e o adicionamos diretamente ao 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 e Lua ajudaram a aumentar a taxa de transferência. Continuamos a lidar com a nossa carga de trabalho, o serviço continua vivo, todos estão felizes.

Melhorando a solução em Lua


A última etapa ( observação: na época do relatório ) é fevereiro de 2019 . 500 milhões de assinantes enviam e recebem 7 milhões de mensagens de um milhão de bots todos os meses.

Este é um passo para melhorar nossa solução em Lua. Gradualmente, retire alguma lógica das filas e transfira o processamento principal da distribuição de webhooks entre sistemas para Lua. Agora, nossos sistemas são mais produtivos e menos dependentes.



Mantemos processamento separado e processamento assíncrono . O processamento diz respeito a estatísticas e outras coisas - agora é um sistema completamente diferente.

O sistema parece simples, mas não é. Sob o capô, existem 500 serviços que processam seus pedidos. Todo o sistema é executado em 50 instâncias da Amazon: Redis, PostgreSQL e os próprios manipuladores de PHP.

Evolução do processamento


Highload pode ser legal de fazer em PHP.

Lembre-se brevemente de como fizemos isso no processo de desenvolvimento do sistema.

  • Iniciado com Nginx e PHP-FPM regulares.
  • Adicionadas filas ao PostgreSQL e depois ao Redis.
  • Cluster adicionado.
  • ReactPHP implementado.
  • Substituímos o ReactPHP por um monte de Nginx e Lua e depois movemos a lógica para o grupo.



Pela nossa experiência, descobrimos que é possível crescer e construir arquitetura alterando sucessivamente partes vulneráveis, usando abordagens bem conhecidas e simples e, ao mesmo tempo, não expandindo a pilha.

, , 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