Apache Flink 101: guia para desenvolvedores

nos últimos anos, o Apache Flink se estabeleceu como a norma de facto para o processamento de fluxo de dados em tempo real. O processamento de fluxo de dados é um paradigma para a construção de sistemas que trata dos fluxos de eventos (sequências de eventos em tempo) como seu bloco de construção fundamental. Um processador de fluxo de dados, como o Flink, consome fluxos de entrada produzidos por fontes de eventos e produz fluxos de saída que são consumidos por becos (os becos armazenam resultados e os tornam disponíveis para processamento adicional).

Nomes conhecidos como Amazon, Netflix e Uber dependem do Flink para potenciar os pipelines de dados que funcionam em escala tremenda no cerne dos seus negócios, mas o Flink também desempenha um papel chave em muitas empresas pequenas com requisitos semelhantes para serem capazes de reagir rapidamente a eventos críticos de negócios.

O que o Flink está sendo usado para? Casos de uso comuns se enquadram nestas três categorias:

Pipelines de dados em streaming

Análise em tempo real

Aplicações baseadas em eventos

Ingere, enriqueça e transforme constantemente fluxos de dados, carregando-os em sistemas de destino para ação oportuna (em oposição ao processamento em lotes).

Produza e atualize constantemente resultados que são exibidos e entregues aos usuários enquanto os fluxos de dados em tempo real são consumidos.

Reconheça padrões e reaja a eventos em chegada acionando cálculos, atualizações de estado ou ações externas.

Alguns exemplos incluem:

  • Streaming ETL

  • Ingestão de data lake

  • Pipelines de ML

Alguns exemplos incluem:

  • Desempenho de campanhas de publicidade

  • Medição de uso e faturamento

  • Monitoramento de rede

  • Engenharia de recursos

Alguns exemplos incluem:

  • Detecção de fraude

  • Monitoramento e automação de processos de negócios

  • Geo-encerferimento

Flink inclui:

  • Forte suporte para cargas de fluxo de dados em escala necessária por empresas globais
  • Fortes garantias de correção exatamente uma vez e recuperação de falha
  • Suporte para Java, Python e SQL, com suporte unificado tanto para processamento em lote quanto em fluxo
  • Flink é um projeto de código aberto maduro da Apache Software Foundation e tem uma comunidade muito ativa e apoiante.

Flink às vezes é descrito como sendo complexo e difícil de aprender. Sim, a implementação do runtime de Flink é complexa, mas esse não deve surpreender, já que resolve alguns problemas difíceis. As APIs de Flink podem ser algo desafiadoras de aprender, mas isso tem mais a ver com os conceitos e princípios de organização sendo desconhecidos do que com qualquer complexidade inerente.

Flink pode ser diferente de tudo que você já usou antes, mas, em muitos aspectos, é mesmo bem simples. Em certo ponto, conforme você se torna mais familiarizado com a maneira como o Flink é composto e com os problemas que o tempo de execução deve resolver, os detalhes das APIs do Flink devem começar a parecer-se com as consequências óbvias de alguns princípios chave, em vez de uma coleção de detalhes arcane que você deve memorizar.

Este artigo visa tornar o processo de aprendizado do Flink muito mais fácil, expondo os princípios centrais que underline seu design.

O Flink encarna algumas grandes ideias

Streams

O Flink é um framework para construir aplicações que processam fluxos de eventos, onde um fluxo é uma seqüência limitada ou não limitada de eventos.

Uma aplicação Flink é um pipeline de processamento de dados. Seus eventos fluem por este pipeline e são operados em cada estágio pelo código que você escreve. Nós chamamos este pipeline de grafo de job e os nós deste grafo (ou seja, as etapas do pipeline de processamento) são chamados de operadores.

O código que você escreve usando uma das APIs do Flink descreve o grafo de job, incluindo o comportamento dos operadores e suas conexões.

Processamento Paralelo

Cada operador pode ter muitas instâncias paralelas, cada uma operando independentemente em algum subconjunto dos eventos.

Às vezes, você quererá impor um esquema de particionamento específico nestas sub-streams para que os eventos sejam agrupados juntos de acordo com alguma lógica específica da aplicação. Por exemplo, se você estiver processando transações financeiras, você pode precisar arranjar para que cada evento de qualquer transação dada seja processado pela mesma thread. Isto permitirá que você conecte juntos os vários eventos que ocorrem ao longo do tempo para cada transação

No Flink SQL, você faria isso com GROUP BY transaction_id, enquanto na API DataStream você usaria keyBy(event -> event.transaction_id) para especificar este agrupamento ou particionamento. Em ambos os casos, isso aparecerá no grafo do trabalho como uma rede de troca totalmente conectada entre dois estágios consecutivos do grafo.

Estado

Operadores trabalhando em streams particionadas por chave podem usar o armazenamento de estado de chave/valor distribuído do Flink para persistir duravelmente o que quiserem. O estado para cada chave é local a uma instância específica de um operador e não pode ser acessado de nenhuma outra parte. As sub-topologias paralelas compartilham nada — essa é uma característica crucial para a escalabilidade sem restrições.

Um trabalho Flink pode ser deixado rodando indefinidamente. Se um trabalho Flink estiver criando novas chaves (por exemplo, IDs de transação) e armazenando algo para cada nova chave, então esse trabalho corre risco de explodir porque está usando uma quantidade ilimitada de estado. Cada API do Flink está organizada em torno de mecanismos para ajudar você a evitar explosões de estado desenfreadas.

Tempo

Uma maneira de evitar manter um estado por muito tempo é manter-o apenas até um ponto específico de tempo. Por exemplo, se você quer contar transações em janelas de 60 segundos, assim que cada minuto acaba, o resultado para esse minuto pode ser produzido, e esse contador pode ser liberado.

Flink faz uma importante distinção entre duas noções diferentes de tempo:

  • Tempo de processamento (ou relógio de parede), que é derivado do tempo real do dia em que um evento está sendo processado
  • Tempo de evento, que é baseado em timestamps gravados com cada evento

Para ilustrar a diferença entre eles, considere o que significa que uma janela de 60 segundos está completa:

  • Uma janela de tempo de processamento é completa quando o minuto acaba. Isto é perfeitamente simples.
  • Uma janela de tempo de evento é completa quando todos os eventos que ocorreram durante esse minuto foram processados. Isto pode ser complicado, já que Flink não sabe nada sobre eventos que ainda não foram processados. O melhor que podemos fazer é fazer uma suposição sobre quanto o fluxo de dados pode estar fora de ordem e aplicar essa suposição heurísticamente.

Checkpointing para Recuperação de Falhas

Falhas são inevitáveis. Apesar das falhas, Flink é capaz de fornecer garantias de exatamente uma vez, o que significa que cada evento afetará o estado que Flink está gerenciando exatamente uma vez, como se a falha nunca tivesse ocorrido. Isso é feito tomando snapshots periódicos, globais e auto-consistentes de todos os estados. Esses snapshots, criados e gerenciados automaticamente por Flink, são chamados de checkpoints.

Recuperação envolve voltar à condição capturada no ponto de verificação mais recente e realizar um reinício global de todos os operadores a partir desse ponto de verificação. Durante a recuperação, alguns eventos são reprocessados, mas o Flink consegue garantir a corretude ao certificar que cada ponto de verificação é um snapshop global e self-consistent do estado completo do sistema.

Arquitetura do Sistema

Aplicações Flink executam em clusters Flink, portanto, antes de colocar uma aplicação Flink em produção, você precisará de um cluster para deploy-la. Felizmente, durante o desenvolvimento e teste, é fácil começar executando o Flink localmente em um ambiente de desenvolvimento integrado (IDE) como o IntelliJ ou o Docker.

Um cluster Flink tem dois tipos de componentes: um Gerenciador de Trabalhos e um conjunto de Gerentes de Tarefas. Os gerentes de tarefas executam suas aplicações (em paralelo), enquanto o gerente de trabalhos age como um gateway entre os gerentes de tarefas e o mundo exterior. As aplicações são submetidas ao gerente de trabalhos, que gerencia os recursos fornecidos pelos gerentes de tarefas, coordena o checkpointing e fornece visibilidade no cluster em formas de métricas.

A Experiência do Desenvolvedor

A experiência que você terá como desenvolvedor Flink depende, em certa medida, da API que você escolher: ou a mais antiga, de nível inferior DataStream API ou a mais nova, relacional Table e SQL APIs.

Quando você está programando com a API DataStream do Flink, você está pensando conscienciosamente sobre o que o runtime do Flink estará fazendo enquanto sua aplicação está sendo executada. Isso significa que você está montando o gráfico de trabalho de um operador de cada vez, descrevendo o estado que você está usando juntamente com os tipos envolvidos e sua serialização, criando timers e implementando funções de callback para serem executadas quando esses timers são disparados, etc. A abstração central na API DataStream é o evento, e as funções que você escreve vão lidar com um evento de cada vez, conforme eles chegam.

Por outro lado, quando você usa a API Table/SQL do Flink, essas preocupações de nível baixo já foram cuidadas por você, e você pode se concentrar mais diretamente na lógica de negócios. A abstração central é a tabela, e você pensa mais em juntar tabelas para enriquecimento, agrupar linhas juntas para computar análise agregada, etc. Um planejador e otimizador de consulta internos gerem os detalhes. O planejador/otimizador faz um excelente trabalho de gerenciamento de recursos eficientemente, muitas vezes superando o código escrito à mão.

Há algumas más observações antes de mergulharmos em detalhes: primeiro, você não precisa escolher entre a DataStream ou a Table/SQL API – ambas as APIs são interoperáveis e você pode combiná-las. Essa pode ser uma boa forma de proceder se você precisar de personalizações que não forem possíveis na Table/SQL API. Mas outra boa opção para ultrapassar o que a Table/SQL API oferece de fora do pacote é adicionar algumas capacidades adicionais na forma de funções definidas pelo usuário (UDFs). Aqui, a SQL do Flink oferece muitas opções para extensão.

Construindo o Gráfico de Trabalho

independentemente da API que você use, o objetivo final do código que você escreve é construir o gráfico de trabalho que o runtime do Flink executará em seu nome. Isso significa que essas APIs estão organizadas em torno da criação de operadores e da especificação tanto do seu comportamento quanto das suas conexões entre si. Com a API DataStream, você está construindo diretamente o gráfico de trabalho, enquanto com a API Table/SQL, o planejador SQL do Flink cuida disso.

Serializando Funções e Dados

Eventualmente, o código que você fornece ao Flink será executado em paralelo pelos trabalhadores (gerenciadores de tarefas) em um cluster Flink. Para fazer isso acontecer, os objetos de função que você cria são serializados e enviados para os gerenciadores de tarefas onde são executados. Similarmente, os eventos próprios às vezes precisarão ser serializados e enviados pela rede de um gerenciador de tarefas para outro. Novamente, com a API Table/SQL você não precisa se preocupar com isso.

Gerenciando Estado

O runtime do Flink precisa estar ciente de qualquer estado que você espera que ele recupere em caso de falha. Para fazer isso funcionar, o Flink precisa de informações de tipo que possa usar para serializar e desserializar esses objetos (então eles podem ser escritos em checkpoints e lidos deles). Você pode opcionalmente configurar este estado gerenciado com descritores de tempo de vida que o Flink usará então para expirar automaticamente o estado assim que ele terá superado sua utilidade.

Com a API DataStream, você normalmente acaba lidando diretamente com o estado que sua aplicação precisa (as operações de janela embutidas são a exceção a isso). Por outro lado, com a API de Tabela/SQL, essa preocupação é abstratada. Por exemplo, dado a consulta abaixo, sabe que algures no tempo de execução do Flink, alguma estrutura de dados precisa manter um contador para cada URL, mas todos os detalhes são cuidados por você.

SQL

 

SELECT url, COUNT(*)

FROM pageviews

GROUP BY URL;

Configuração e Disparo de Timers

Timers têm muitos usos em processamento de fluxo. Por exemplo, é comum que as aplicações do Flink precisem coletar informações de muitos diferentes fontes de eventos antes de eventualmente produzir resultados. Os timers funcionam bem para casos onde faz sentido esperar (mas não indefinidamente) por dados que podem (ou não) chegar eventualmente.

Timers também são essenciais para a implementação de operações de janela baseadas em tempo. Ambos as APIs DataStream e Table/SQL têm suporte integrado para Janelas e criam e gerem timers em seu nome.

Casos de Uso

Voltando às três categorias amplas de casos de uso de streaming introduzidas no início deste artigo, vejamos como elas se mapem àquele que você acabou de aprender sobre o Flink.

Pipeline de Dados em Fluxo

Abaixo, à esquerda, está um exemplo de um trabalho tradicional de batch extrair, transformar e carregar (ETL) que periodicamente lê de um banco de dados transacionais, transforma os dados e escreve os resultados para outro armazenamento, como um banco de dados, sistema de arquivos ou lago de dados.

O pipeline de streaming correspondente é superficialmente semelhante, mas há diferenças significativas:

  • O pipeline de streaming está sempre em execução.
  • Os dados transacionais são entregues ao pipeline de streaming em duas partes: um carregamento em massa inicial do banco de dados, combinado com uma stream de captura de mudança (CDC) que transporta as atualizações do banco de dados desde esse carregamento em massa.
  • A versão streaming continuamente produz novos resultados assim que eles se tornam disponíveis.
  • O estado é gerenciado explicitamente para que possa ser recuperado robustamente em caso de falha. Os pipelines de ETL streaming tipicamente usam muito pouco estado. As fontes de dados mantêm controle exato de quanto do input foi processado, normalmente na forma de deslocamentos que contam registros desde o início dos streams. As fonte usam transações para gerenciar suas escritas em sistemas externos, como bancos de dados ou Kafka. Durante o checkpointing, as fontes registram seus deslocamentos, e as fontes concluem as transações que carregam os resultados de ter lido exatamente até, mas não além, desse deslocamento de origem.

Para este caso de uso, a API Table/SQL seria uma boa escolha.

Análise em Tempo Real

Em comparação com a aplicação de streaming ETL, esta aplicação de análise de streaming apresenta algumas diferenças interessantes:

  • Novamente, Flink está sendo usado para executar uma aplicação contínua, mas para essa aplicação, Flink provavelmente precisará gerenciar substancialmente mais estado.
  • Para esse caso de uso, é sensato para a stream sendo processada ser armazenada em um sistema de armazenamento nativo de stream, como o Apache Kafka.
  • Em vez de produzir periodicamente um relatório estático, a versão streaming pode ser usada para operar um painel de instrumentos em tempo real.

Novamente, a API de Tabela/SQL é frequentemente uma boa escolha para este caso de uso.

Aplicações Baseadas em Eventos

Nossa terceira e última família de casos de uso envolve a implementação de aplicações baseadas em eventos ou microsserviços. Muito já foi escrito noutros lugares sobre este tópico; este é um padrão de projeto arquitetural que oferece muitos benefícios.

Flink pode ser uma ótima escolha para essas aplicações, especialmente se você precisar do desempenho que o Flink pode fornecer. Em alguns casos, a API de Tabela/SQL tem tudo o que você precisa, mas em muitos casos, você precisará da flexibilidade adicional da API DataStream, pelo menos para parte do trabalho.

Começando com Flink

O Flink fornece um framework poderoso para construir aplicações que processam fluxos de eventos. Como covermos, algumas das ideias podem parecer inovadoras ao princípio, mas uma vez que você estiver familiarizado com a maneira como o Flink está projetado e funciona, o software é fácil de usar e as recompensas de conhecer o Flink são significativas.

Como próximo passo, siga com as instruções na documentação do Flink, que o guiará pelo processo de baixar, instalar e executar a versão estável mais recente do Flink. Pense nos casos de uso abrangentes que discutimos – pipelines de dados modernos, análise em tempo real e microserviços baseados em eventos – e como esses podem ajudar a resolver um desafio ou aumentar o valor para sua organização.

O streaming de dados é hoje uma das áreas mais emocionantes da tecnologia empresarial, e o processamento de stream com Flink torna-o ainda mais poderoso. Aprender com Flink será benéfico não só para sua organização quanto para sua carreira, pois o processamento de dados em tempo real está se tornando cada vez mais valioso para as empresas em todo o mundo. Então, Explore Flink hoje e veja o que essa poderosa tecnologia pode ajudá-lo a alcançar.

Source:
https://dzone.com/articles/apache-flink-101-a-guide-for-developers