Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Real time and reliable processing with Apache Storm

Real time and reliable processing with Apache Storm

Andrea Iacono

February 12, 2015
Tweet

More Decks by Andrea Iacono

Other Decks in Programming

Transcript

  1. Real time and reliable processing with Apache Storm The code

    is available on: https://github.com/andreaiacono/StormTalk
  2. What is Apache Storm? Real time and reliable processing with

    Apache Storm Storm is a real-time distributed computing framework for reliably processing unbounded data streams. It was created by Nathan Marz and his team at BackType, and released as open source in 2011 (after BackType was acquired by Twitter).
  3. Topology A spout is the source of a data stream

    that is emitted to one or more bolts. Emitted data is called tuple and is an ordered list of values. A bolt performs computation on the data it receives and emits them to one or more bolts. If a bolt is at the end of the topology, it doesn't emit anything. Every task (either a spout or a bolt) can have multiple instances. A topology is a directed acyclic graph of computation formed by spouts and bolts. Real time and reliable processing with Apache Storm
  4. Real time and reliable processing with Apache Storm A simple

    topology We'd like to build a system that generates random numbers and writes them to a file. Here is a topology that represent it:
  5. public class RandomSpout extends BaseRichSpout { private SpoutOutputCollector spoutOutputCollector; private

    Random random; @Override public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) { outputFieldsDeclarer.declare(new Fields("val")); } @Override public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) { this.spoutOutputCollector = spoutOutputCollector; random = new Random(); } @Override public void nextTuple() { spoutOutputCollector.emit(new Values(random.nextInt() % 100)); } } Real time and reliable processing with Apache Storm A simple topology: the spout
  6. // no exception checking: it's a sample! public class FileWriteBolt

    extends BaseBasicBolt { private final String filename = "output.txt"; private BufferedWriter writer; @Override public void prepare(Map stormConf, TopologyContext context) { super.prepare(stormConf, context); writer = new BufferedWriter(new FileWriter(filename, true)); } @Override public void execute(Tuple input, BasicOutputCollector collector) { writer.write(tuple.getInteger(0) + "\n"); } @Override public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {} @Override public void cleanup() { writer.close(); } Real time and reliable processing with Apache Storm A simple topology: the bolt
  7. Real time and reliable processing with Apache Storm public class

    RandomValuesTopology { private static final String name = RandomValuesTopology.class.getName(); public static void main(String[] args) { TopologyBuilder builder = new TopologyBuilder(); builder.setSpout("random-spout", new RandomSpout()); builder.setBolt("writer-bolt",new FileWriteBolt()) .shuffleGrouping("random-spout"); Config conf = new Config(); conf.setDebug(false); conf.setMaxTaskParallelism(3); LocalCluster cluster = new LocalCluster(); cluster.submitTopology(name, conf, builder.createTopology()); Utils.sleep(300_000); cluster.killTopology(name); cluster.shutdown(); // to run it on a live cluster // StormSubmitter.submitTopology("topology", conf, builder.createTopology()); } } A simple topology: the topology
  8. Grouping Tuples path from one bolt to another is driven

    by grouping. Since we can have multiple instances of bolts, we have to decide where to send the tuples emitted. Real time and reliable processing with Apache Storm
  9. We want to create a webpage that shows the top-N

    hashtags and every time arrives a new tweet containing one of them, displays it on a world map. Twitter top-n hashtags: overview Real time and reliable processing with Apache Storm
  10. public class GeoTweetSpout extends BaseRichSpout { SpoutOutputCollector spoutOutputCollector; TwitterStream twitterStream;

    LinkedBlockingQueue<String> queue = null; @Override public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) { this.spoutOutputCollector = spoutOutputCollector; queue = new LinkedBlockingQueue<>(1000); ConfigurationBuilder config = new ConfigurationBuilder() .setOAuthConsumerKey(custkey) .setOAuthConsumerSecret(custsecret) .setOAuthAccessToken(accesstoken) .setOAuthAccessTokenSecret(accesssecret); TwitterStreamFactory streamFactory = new TwitterStreamFactory(config.build()); twitterStream = streamFactory.getInstance(); twitterStream.addListener(new GeoTwitterListener(queue)); double[][] boundingBox = {{-179d, -89d}, {179d, 89d}}; FilterQuery filterQuery = new FilterQuery().locations(boundingBox); twitterStream.filter(filterQuery); } @Override public void nextTuple() { String msg = queue.poll(); if (msg == null) { return; } String lat = MiscUtils.getLatFromMsg(msg);; String lon = MiscUtils.getLonFromMsg(msg);; String tweet = MiscUtils.getTweetFromMsg(msg);; spoutOutputCollector.emit(new Values(tweet, lat, lon)); } @Override public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) { outputFieldsDeclarer.declare(new Fields("tweet", "lat", "lon")); } } Real time and reliable processing with Apache Storm Twitter top-n hashtags: GeoTweetSpout
  11. public class NoHashtagDropperBolt extends BaseBasicBolt { @Override public void declareOutputFields(OutputFieldsDeclarer

    declarer) { declarer.declare(new Fields("tweet", "lat", "lon")); } @Override public void execute(Tuple tuple, BasicOutputCollector collector) { Set<String> hashtags = MiscUtils.getHashtags(tuple.getString(0)); if (hashtags.size() == 0) { return; } String tweet = tuple.getString(0); String lat = tuple.getString(1); String lon = tuple.getString(2); collector.emit(new Values(tweet, lat, lon)); } } Twitter top-n hashtags: NoHashtagDropperBolt Real time and reliable processing with Apache Storm
  12. Twitter top-n hashtags: GeoHashtagFilterBolt Real time and reliable processing with

    Apache Storm public class GeoHashtagsFilterBolt extends BaseBasicBolt { private Rankings rankings; @Override public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) { outputFieldsDeclarer.declare(new Fields("tweet", "lat", "lon","hashtag")); } @Override public void execute(Tuple tuple, BasicOutputCollector collector) { String componentId = tuple.getSourceComponent(); if ("total-rankings".equals(componentId)) { rankings = (Rankings) tuple.getValue(0); return; } if (rankings == null) return; String tweet = tuple.getString(0); for (String hashtag : MiscUtils.getHashtags(tweet)) { for (Rankable r : rankings.getRankings()) { String rankedHashtag = r.getObject().toString(); if (hashtag.equals(rankedHashtag)) { String lat = tuple.getString(1); String lon = tuple.getString(2); collector.emit(new Values(lat, lon, hashtag, tweet)); return; } } }
  13. public class ToRedisTweetBolt extends BaseBasicBolt { private RedisConnection<String, String> redis;

    @Override public void prepare(Map stormConf, TopologyContext context) { super.prepare(stormConf, context); RedisClient client = new RedisClient("localhost", 6379); redis = client.connect(); } @Override public void execute(Tuple tuple, BasicOutputCollector collector) { // gets the tweet and its rank String lat = tuple.getString(0); String lon = tuple.getString(1); String hashtag = tuple.getString(2); String tweet = tuple.getString(3); String message = "1|" + lat + "|" + lon + "|" + hashtag + "|" + tweet; redis.publish("tophashtagsmap", message); } @Override public void declareOutputFields(OutputFieldsDeclarer declarer) { } } Twitter top-n hashtags: ToRedisTweetBolt Real time and reliable processing with Apache Storm
  14. public class TopHashtagMapTopology { private static int n = 20;

    public static void main(String[] args) { GeoTweetSpout geoTweetSpout = new GeoTweetSpout(); TopologyBuilder builder = new TopologyBuilder(); builder.setSpout("geo-tweet-spout", geoTweetSpout, 4); builder.setBolt("no-ht-dropper",new NoHashtagDropperBolt(), 4) .shuffleGrouping("geo-tweet-spout"); builder.setBolt("parse-twt",new ParseTweetBolt(), 4) .shuffleGrouping("no-ht-dropper"); builder.setBolt("count-ht",new CountHashtagsBolt(), 4) .fieldsGrouping("parse-twt",new Fields("hashtag")); builder.setBolt("inter-rankings", new IntermediateRankingsBolt(n), 4) .fieldsGrouping("count-ht", new Fields("hashtag")); builder.setBolt("total-rankings", new TotalRankingsBolt(n), 1) .globalGrouping("inter-rankings"); builder.setBolt("to-redis-ht", new ToRedisTopHashtagsBolt(), 1) .shuffleGrouping("total-rankings"); builder.setBolt("geo-hashtag-filter", new GeoHashtagsFilterBolt(), 4) .shuffleGrouping("no-ht-dropper") .allGrouping("total-rankings"); builder.setBolt("to-redis-tweets", new ToRedisTweetBolt(), 4) .globalGrouping("geo-hashtag-filter"); // code to start the topology... } } Twitter top-n hashtags: topology Real time and reliable processing with Apache Storm
  15. Storm Cluster Nimbus: a daemon responsible for distributing code around

    the cluster, assigning jobs to nodes, and monitoring for failures. Worker node: executes a subset of a topology (spouts and/or bolts). It runs a supervisor daemon that listens for jobs assigned to the machine and starts and stops worker processes as necessary. Zookeeper: manages all the coordination between Nimbus and the supervisors. Real time and reliable processing with Apache Storm
  16. Worker Node Worker process: JVM (processes a specific topology) Executor:

    Thread Task: instance of bolt/spout Supervisor: syncing with Master Node The number of executors can be modified at runtime; the topology structure cannot. Real time and reliable processing with Apache Storm
  17. Tuples transfer • on the same JVM • on different

    JVMs For serialization, Storm tries to lookup a Kryo serializer, which is more efficient than Java standard serialization. The network layer for transport is provided by Netty. Also for performance reasons, the queues are implemented using the LMAX Disruptor library, which enables efficient queuing. Real time and reliable processing with Apache Storm Storm supports two different types of transfer:
  18. Tuples transfer: on the same JVM A generic task is

    composed by two threads and two queues. Tasks at the start (spout) or at the end of the topology (ending bolts) have only one queue. Real time and reliable processing with Apache Storm
  19. Queues failure Since the model behind the queue is the

    producer/consumer, if the producer supplies data at a higher rate than the consumer, the queue will overflow. The transfer queue is more critical because it has to serve all the tasks of the worker, so it's stressed more than the internal one. If an overflow happens, Storm tries - but not guarantees - to put the overflowing tuples into a temporary queue, with the side- effect of dropping the throughput of the topology. Real time and reliable processing with Apache Storm
  20. Reliability Levels of delivery guarantee • at-most-once: tuples are processed

    in the order coming from spouts and in case of failure (network, exceptions) are just dropped • at-least-once: in case of failure tuples are re-emitted from the spout; a tuple can be processed more than once and they can arrive out of order • exactly-once: only available with Trident, a layer sitting on top of Storm that allows to write topologies with different semantic Real time and reliable processing with Apache Storm
  21. Reliability for bolts Real time and reliable processing with Apache

    Storm The three main concepts to achieve at-least-once guarantee level are: • anchoring: every tuple emitted by a bolt has to be linked to the input tuple using the emit(tuple, values) method • acking: when a bolt successfully finishes to execute() a tuple, it has to call the ack() method to notify Storm • failing: when a bolt encounters a problem with the incoming tuple, it has to call the fail() method The BaseBasicBolt we saw before takes care of them automatically (when a tuple has to fail, it must be thrown a FailedException). When the topology is complex (expanding tuples, collapsing tuples, joining streams) they must be explicitly managed extending a BaseRichBolt.
  22. Reliability for spouts Real time and reliable processing with Apache

    Storm The ISpout interface defines - beside others - these methods: void open(Map conf,TopologyContext context,SpoutOutputCollector collector); void close(); void nextTuple(); void ack(Object msgId); void fail(Object msgId); To implement a reliable spout we have to call inside the nextTuple() method: Collector.emit(values, msgId); and we have to manage the ack() and fail() methods accordingly.
  23. Reliability A tuple tree is the set of all the

    additional tuples emitted by the subsequent bolts starting from the tuple emitted by a spout. When all the tuples of a tree are marked as processed, Storm will consider the initial tuple from a spout correctly processed. If any tuple of the tree is not marked as processed within a timeout (30 secs default) or is explicitly set as failed, Storm will replay the tuple starting from the spout (this means that the operations made by a task have to be idempotent). Real time and reliable processing with Apache Storm
  24. Real time and reliable processing with Apache Storm Reliability –

    Step 1 This is the starting state, nothing has yet happened. For better understading the following slides, it's important to review this binary XOR property: 1 ^ 1 == 0 1 ^ 2 ^ 1 == 2 1 ^ 2 ^ 2 ^ 1 == 0 1 ^ 2 ^ 3 ^ 2 ^ 1 == 3 1 ^ 2 ^ 3 ^ 3 ^ 2 ^ 1 == 0 1 ^ 2 ^ 3 ^ ... ^ N ^ ... ^ 3 ^ 2 ^ 1 == N 1 ^ 2 ^ 3 ^ ... ^ N ^ N ^ ... ^ 3 ^ 2 ^ 1 == 0 (whatever the order of the operands is)
  25. Real time and reliable processing with Apache Storm Reliability –

    Step 2 The spout has received something, so it sends to its acker task a couple of values: • the tuple ID of the tuple to emit to the bolt • its task ID The acker puts those data in a map <TupleID,[TaskID, AckVal]> where the AckVal is initially set to the TupleID value. The ID values are of type long, so they're 64 bit.
  26. Real time and reliable processing with Apache Storm Reliability –

    Step 3 After having notified the acker that a new tuple was created, It sends the tuple to its attached bolt (Bolt1).
  27. Real time and reliable processing with Apache Storm Reliability –

    Step 4 Bolt1 computes the outgoing tuple according to its business logic and notifies the acker that it's going to emit a new tuple. The acker gets the ID of the new tuple and XORs it with the AckVal (that contained the initial tuple ID).
  28. Real time and reliable processing with Apache Storm Reliability –

    Step 5 Bolt1 sends the new tuple to its attached bolt (Bolt2).
  29. Real time and reliable processing with Apache Storm Reliability –

    Step 6 After emitting the new tuple, Bolt1 sets as finished its work with the incoming tuple (Tuple0), and so it sends an ack to the acker to notify that. The acker gets the tuple ID and XORs it with the AckVal.
  30. Real time and reliable processing with Apache Storm Reliability –

    Step 7 Bolt2 process the incoming tuple according to its business logic and - probably - will write some data on a DB, on a queue or somewhere else. Since it's a terminal bolt, it will not emit a new Tuple. Since its job is done, it can send an ack to the acker for the incoming tuple. The acker gets the tuple ID and XORs it with the AckVal. The value of AckVal will be 0, so the acker knows that the starting tuple has been successfully processed by the topology.