Hej, Habr! Idag ska vi bygga ett system som med hjÀlp av Spark Streaming behandlar meddelandeflöden frÄn Apache Kafka och skriver resultaten till en molndatabas, AWS RDS.
LÄt oss anta att en kreditorganisation stÀller oss inför uppgiften att behandla inkommande transaktioner "i realtid" för alla sina filialer. Detta kan göras för att snabbt berÀkna den öppna valutapositionen för kassan, grÀnser eller det finansiella resultatet för affÀrer, etc.
Hur man implementerar detta fall utan att anvĂ€nda magi och trollformler â lĂ€s vidare! LĂ„t oss köra!

Introduktion
SjÀlvklart erbjuder behandling av stora datamÀngder i realtid stora möjligheter för anvÀndning i moderna system. En av de mest populÀra kombinationerna för detta Àr tandem Apache Kafka och Spark Streaming, dÀr Kafka skapar en ström av inkommande meddelandepaket och Spark Streaming behandlar dessa paket under ett givet tidsintervall.
För att öka systemets tillförlitlighet kommer vi att anvÀnda checkpoints. Med denna mekanism behöver Spark Streaming-modulen, nÀr den behöver ÄterstÀlla förlorade data, bara ÄtervÀnda till den senaste kontrollpunkten och Äteruppta berÀkningarna dÀrifrÄn.
Arkitekturen för det utvecklade systemet

AnvÀnda komponenter:
- â Ă€r ett distribuerat meddelandebytesystem med publicering och prenumeration. Det Ă€r lĂ€mpligt för bĂ„de offline och online konsumption av meddelanden. För att förhindra dataloss sparas meddelanden frĂ„n Kafka pĂ„ disk och replikeras inom klustret. Kafka-systemet Ă€r byggt ovanpĂ„ ZooKeeper-tjĂ€nsten;
- â komponenten Spark för bearbetning av strömmande data. Modulen Spark Streaming Ă€r byggd med hjĂ€lp av mikrobatch-arkitektur, dĂ€r en datastream tolkas som en kontinuerlig sekvens av smĂ„ datapaket. Spark Streaming tar emot data frĂ„n olika kĂ€llor och samlar dem i smĂ„ paket. Nya paket skapas med regelbundna tidsintervall. I början av varje tidsintervall skapas ett nytt paket, och all data som kommer in under detta intervall inkluderas i paketet. I slutet av intervallet upphör paketets tillvĂ€xt. Storleken pĂ„ intervallet bestĂ€ms av en parameter som kallas batchintervall;
- â förenar relationell bearbetning med funktionell programmering i Spark. Strukturerade data definieras som data som har ett schema, det vill sĂ€ga en enhetlig uppsĂ€ttning fĂ€lt för alla poster. Spark SQL stödjer indata frĂ„n mĂ„nga kĂ€llor med strukturerade data och, tack vare att schemainformation finns, kan det effektivt extrahera endast de nödvĂ€ndiga fĂ€lten i posterna samt tillhandahĂ„lla API-grĂ€nssnitt för DataFrame;
- â en relativt billig molnrelaterad relationsdatabas, en webbtjĂ€nst som förenklar installation, drift och skalering, hanteras direkt av Amazon.
Installation och körning av Kafka-servern
Innan Kafka kan anvÀndas direkt, mÄste du sÀkerstÀlla att Java finns installerat, eftersom JVM anvÀnds för att köra det:
sudo apt-get update
sudo apt-get install default-jre
java -version
Skapa en ny anvÀndare för att arbeta med Kafka:
sudo useradd kafka -m
sudo passwd kafka
sudo adduser kafka sudo
Ladda ner distributionspaketet frÄn Apache Kafkas officiella webbplats:
wget -P /YOUR_PATH "http://apache-mirror.rbc.ru/pub/apache/kafka/2.2.0/kafka_2.12-2.2.0.tgz"Packa upp den nedladdade arkivet:
tar -xvzf /YOUR_PATH/kafka_2.12-2.2.0.tgz
ln -s /YOUR_PATH/kafka_2.12-2.2.0 kafka
NÀsta steg Àr valfritt. FrÄgan Àr att standardinstÀllningarna tillÄter inte att man fullt ut utnyttjar alla funktioner i Apache Kafka. Till exempel att ta bort en Àmne, kategori eller grupp som meddelanden kan publiceras pÄ. För att Àndra detta, redigera konfigurationsfilen:
vim ~/kafka/config/server.propertiesLÀgg till följande i slutet av filen:
delete.topic.enable = trueInnan vi startar Kafka-servern, mÄste vi starta ZooKeeper-servern. Vi kommer att anvÀnda ett hjÀlpskript som följer med Kafka-distributionen:
Cd ~/kafka
bin/zookeeper-server-start.sh config/zookeeper.properties
NÀr ZooKeeper har startat framgÄngsrikt, starta Kafka-servern i ett separat terminalfönster:
bin/kafka-server-start.sh config/server.propertiesLÄt oss skapa ett nytt Àmne med namnet Transaction:
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic transactionVi kontrollerar att Àmnet har skapats med rÀtt antal partitioner och replikering:
bin/kafka-topics.sh --describe --zookeeper localhost:2181 
Vi hoppar över testmomentet för producer och konsument för det nyligen skapade Ă€mnet. Mer detaljerat om hur man kan testa att skicka och ta emot meddelanden stĂ„r i den officiella dokumentationen â . Men vi gĂ„r vidare till att skriva en producer pĂ„ Python med KafkaProducer API.
Skriv en producer
Producenten kommer att generera slumpmĂ€ssiga data â 100 meddelanden varje sekund. Med slumpmĂ€ssiga data menar vi en ordbok som bestĂ„r av tre fĂ€lt:
- Filial â namnet pĂ„ försĂ€ljningsstĂ€llet för kreditorganisationen;
- Valuta â valutakursen för transaktionen;
- Belopp â beloppet för transaktionen. Beloppet kommer att vara ett positivt tal om det handlar om köp av valuta av banken, och negativt om det handlar om försĂ€ljning.
Koden för producenten ser ut som följande:
from numpy.random import choice, randint
def get_random_value():
new_dict = {}
branch_list = ['Kazan', 'SPB', 'Novosibirsk', 'Surgut']
currency_list = ['RUB', 'USD', 'EUR', 'GBP']
new_dict['branch'] = choice(branch_list)
new_dict['currency'] = choice(currency_list)
new_dict['amount'] = randint(-100, 100)
return new_dict
DÀrefter, med hjÀlp av metoden send, skickar vi meddelandet till servern, till det Àmne vi vill ha, i JSON-format:
from kafka import KafkaProducer
producer = KafkaProducer(bootstrap_servers=['localhost:9092'],
value_serializer=lambda x:dumps(x).encode('utf-8'),
compression_type='gzip')
my_topic = 'transaction'
data = get_random_value()
try:
future = producer.send(topic = my_topic, value = data)
record_metadata = future.get(timeout=10)
print('--> Meddelandet har skickats till ett Àmne:
{}, partition: {}, offset: {}'
.format(record_metadata.topic,
record_metadata.partition,
record_metadata.offset ))
except Exception as e:
print('--> Det verkar ha uppstÄtt ett fel: {}'.format(e))
finally:
producer.flush()
NÀr skriptet körs fÄr vi följande meddelanden i terminalen:

Det betyder att allt fungerar som vi ville â producenten genererar och skickar meddelanden till det Ă€mne vi vill ha.
NÀsta steg blir att installera Spark och bearbeta denna ström av meddelanden.
Installation av Apache Spark
Apache Spark â Ă€r en mĂ„ngsidig och högpresterande klusterberĂ€kningsplattform.
NÀr det gÀller prestanda övertrÀffar Spark populÀra implementationer av MapReduce, samtidigt som det erbjuder stöd för en bredare uppsÀttning berÀkningstyper, inklusive interaktiva frÄgor och strömmande behandling. Hastighet spelar en viktig roll vid hantering av stora datamÀngder, eftersom hastigheten möjliggör interaktivt arbete utan att behöva vÀnta minuter eller timmar. En av de viktigaste fördelarna med Spark, som möjliggör denna höga hastighet, Àr dess förmÄga att utföra berÀkningar i minnet.
Denna ramverk Àr skriven i Scala, sÄ vi mÄste installera det först:
sudo apt-get install scalaLadda ner Spark-distributionen frÄn den officiella webbplatsen:
wget "http://mirror.linux-ia64.org/apache/spark/spark-2.4.2/spark-2.4.2-bin-hadoop2.7.tgz"Packa upp arkivet:
sudo tar xvf spark-2.4.2/spark-2.4.2-bin-hadoop2.7.tgz -C /usr/local/sparkLÀgg till sökvÀgen till Spark i bash-filen:
vim ~/ .bashrcLÀgg till följande rader via redigeraren:
SPARK_HOME=/usr/local/spark
export PATH=$SPARK_HOME/bin:$PATH
Kör kommandot nedan efter att ha gjort Àndringar i bashrc:
source ~/ .bashrcDistribution av AWS PostgreSQL
Det ÄterstÄr att distribuera databasen dÀr vi ska ladda upp den bearbetade informationen frÄn strömmarna. För detta kommer vi att anvÀnda tjÀnsten AWS RDS.
Logga in pĂ„ konsolen AWS â> AWS RDS â> Databaser â> Skapa databas:

VÀlj PostgreSQL och klicka pÄ knappen NÀsta:

Eftersom detta exempel endast behandlas för utbildningsÀndamÄl kommer vi att anvÀnda en gratis server "med minimala resurser" (Free Tier):

DĂ€refter markerar vi kryssrutan i blocket Free Tier, och efter det fĂ„r vi automatiskt ett instans av typen t2.micro â om Ă€n svagt, men gratis och helt tillrĂ€ckligt för vĂ„r uppgift:

Följande Àr mycket viktiga saker: namnet pÄ DB-instansen, namn pÄ huvud-anvÀndaren och hans lösenord. Vi döper instansen till: myHabrTest, huvud-anvÀndare: habr, lösenord: habr12345 och klickar pÄ knappen NÀsta:

PÄ nÀsta sida finns parametrarna som ansvarar för tillgÀngligheten av vÄr DB-server utifrÄn (Public accessibility) och tillgÀngligheten av portar:

LÄt oss skapa en ny instÀllning för VPC-sÀkerhetsgruppen, som tillÄter extern Ätkomst till vÄr DB-server via port 5432 (PostgreSQL).
LĂ„t oss gĂ„ till AWS-konsolen i ett separat webblĂ€sarfönster till VPC Dashboard â> SĂ€kerhetsgrupper â> Skapa sĂ€kerhetsgrupp:

Ange ett namn för Security group â PostgreSQL, en beskrivning, ange vilken VPC denna grupp ska associeras med och klicka pĂ„ knappen Skapa:

Fyll i den nyss skapade gruppens Inbound rules för port 5432, som visas pÄ bilden nedan. Porten behöver inte anges manuellt, utan kan vÀljas frÄn rullgardinsmenyn Typ som PostgreSQL.
Rent tekniskt betyder vÀrdet ::/0 att servern har tillgÄng till inkommande trafik frÄn hela vÀrlden, vilket kan anses vara lite missvisande, men för att förtydliga exemplet tillÄter vi oss att anvÀnda denna metod:

Ă
tervĂ€nd till webblĂ€sarsidan dĂ€r vi har öppnat "Configure advanced settings" och vĂ€lj i avsnittet VPC security groups â> VĂ€lj befintliga VPC security groups â> PostgreSQL:

DĂ€refter, i avsnittet Databasalternativ â> Databasnamn â> ange namnet â habrDB.
Ăvriga parametrar, med undantag för eventuell avstĂ€ngning av backup (backup retention period â 0 days), övervakning och Performance Insights, kan lĂ€mnas som standard. Klicka pĂ„ knappen Skapa databas:

Strömhanterare
Det sista steget kommer att vara utvecklingen av en Spark-jobb, som varannan sekund kommer att bearbeta nya data som kommer frÄn Kafka och lagra resultatet i databasen.
Som nÀmnt ovan Àr checkpoints den grundlÀggande mekanismen i SparkStreaming, som mÄste stÀllas in för att sÀkerstÀlla fel tolerans. Vi kommer att anvÀnda checkpoints och, om processen kraschar, krÀvs det bara att ÄtervÀnda till den senaste checkpointen för att Äteruppta berÀkningarna.
Checkpointen kan aktiveras genom att stÀlla in en katalog i ett fel toleranta, pÄlitligt filsystem (t.ex. HDFS, S3 osv.), dÀr checkpointinformation sparas. Detta görs exempelvis med:
streamingContext.checkpoint(checkpointDirectory)I vÄrt exempel kommer vi att anvÀnda följande tillvÀgagÄngssÀtt, nÀmligen, om checkpointDirectory finns, kommer kontexten att Äterskapas frÄn checkpointdata. Om katalogen inte finns (det vill sÀga körs första gÄngen), sÄ anropas funktionen functionToCreateContext för att skapa en ny kontext och stÀlla in DStreams:
from pyspark.streaming import StreamingContext
context = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext)
Skapa ett objekt DirectStream för att ansluta till Àmnet "transaction" med hjÀlp av metoden createDirectStream frÄn KafkaUtils:
frÄn pyspark.streaming.kafka import KafkaUtils
sc = SparkContext(conf=conf)
ssc = StreamingContext(sc, 2)
broker_list = 'localhost:9092'
topic = 'transaction'
directKafkaStream = KafkaUtils.createDirectStream(ssc,
[topic],
{"metadata.broker.list": broker_list})
Parsing av inkommande data i JSON-format:
rowRdd = rdd.map(lambda w: Row(branch=w['branch'],
currency=w['currency'],
amount=w['amount']))
testDataFrame = spark.createDataFrame(rowRdd)
testDataFrame.createOrReplaceTempView("treasury_stream")
Vi anvÀnder Spark SQL för att göra en enkel gruppering och skriver ut resultatet i konsolen:
select
from_unixtime(unix_timestamp()) as curr_time,
t.branch as branch_name,
t.currency as currency_code,
sum(amount) as batch_value
from treasury_stream t
group by
t.branch,
t.currency
Vi fÄr texten av frÄgan och kör den genom Spark SQL:
sql_query = get_sql_query()
testResultDataFrame = spark.sql(sql_query)
testResultDataFrame.show(n=5)
Och sedan sparar vi de aggregerade data i en tabell i AWS RDS. För att spara aggregatresultaten i databastabellen anvÀnder vi metoden write för DataFrame-objektet:
testResultDataFrame.write
.format("jdbc")
.mode("append")
.option("driver", 'org.postgresql.Driver')
.option("url","jdbc:postgresql://myhabrtest.ciny8bykwxeg.us-east-1.rds.amazonaws.com:5432/habrDB")
.option("dbtable", "transaction_flow")
.option("user", "habr")
.option("password", "habr12345")
.save()
NÄgra ord om hur du konfigurerar anslutningen till AWS RDS. AnvÀndaren och lösenordet skapades i steg "Distribution av AWS PostgreSQL". För databasens serveradress bör du anvÀnda Endpoint som visas i avsnittet Connectivity & security:
För en korrekt koppling mellan Spark och Kafka bör jobben köras genom spark-submit med hjĂ€lp av artefaktet spark-streaming-kafka-0-8_2.11. Dessutom kommer vi Ă€ven att tillĂ€mpa artefakten för att interagera med PostgreSQL-databasen, som vi kommer att överföra via âpackages.
För flexibiliteten i skriptet ska vi ocksÄ föra ut namnet pÄ meddelandeservern och Àmnet som ingÄngsparametrar frÄn vilket vi vill hÀmta data.
SÄ nu Àr det dags att köra och kontrollera systemets funktionalitet:
spark-submit
--packages org.apache.spark:spark-streaming-kafka-0-8_2.11:2.0.2,
org.postgresql:postgresql:9.4.1207
spark_job.py localhost:9092 transaction
Det fungerade! Som du kan se pĂ„ bilden nedan â under applikationens drift visas nya aggregater var 2:a sekund, eftersom vi satte batchintervallet till 2 sekunder nĂ€r vi skapade StreamingContext-objektet:

DÀrefter gör vi en enkel frÄga till databasen för att kontrollera om det finns poster i tabellen transaction_flow:

Slutsats
I denna artikel granskades ett exempel pÄ strömmande informationsbehandling med hjÀlp av Spark Streaming i samarbete med Apache Kafka och PostgreSQL. Med den ökande mÀngden data frÄn olika kÀllor Àr det svÄrt att överskatta det praktiska vÀrdet av Spark Streaming för att skapa strömmande applikationer och applikationer som fungerar i realtid.
Den fullstÀndiga koden kan du hitta i mitt arkiv pÄ .
Jag ser fram emot att diskutera denna artikel, jag vÀntar pÄ dina kommentarer och hoppas Àven pÄ konstruktiv kritik frÄn alla intresserade lÀsare.
Jag önskar dig framgÄng!
Ps. Det var först planerat att anvÀnda en lokal PostgreSQL-databas, men med tanke pÄ min kÀrlek till AWS beslutade jag mig för att flytta databasen till molnet. I nÀsta artikel om detta Àmne kommer jag att visa hur man helt implementerar det ovan beskrivna systemet i AWS med hjÀlp av AWS Kinesis och AWS EMR. HÄll utkik efter nyheter!
KĂ€lla: habr.com

