blob: a7872f8ca64bdee73fa395792c719578f50de9cd [file] [log] [blame]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Apache Flink: When Flink & Pulsar Come Together</title>
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<!-- Bootstrap -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/flink.css">
<link rel="stylesheet" href="/css/syntax.css">
<!-- Blog RSS feed -->
<link href="/blog/feed.xml" rel="alternate" type="application/rss+xml" title="Apache Flink Blog: RSS feed" />
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<!-- We need to load Jquery in the header for custom google analytics event tracking-->
<script src="/js/jquery.min.js"></script>
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<!-- Main content. -->
<div class="container">
<div class="row">
<div id="sidebar" class="col-sm-3">
<!-- Top navbar. -->
<nav class="navbar navbar-default">
<!-- The logo. -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<div class="navbar-logo">
<a href="/">
<img alt="Apache Flink" src="/img/flink-header-logo.svg" width="147px" height="73px">
</a>
</div>
</div><!-- /.navbar-header -->
<!-- The navigation links. -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-main">
<!-- First menu section explains visitors what Flink is -->
<!-- What is Stream Processing? -->
<!--
<li><a href="/streamprocessing1.html">What is Stream Processing?</a></li>
-->
<!-- What is Flink? -->
<li><a href="/flink-architecture.html">What is Apache Flink?</a></li>
<!-- What is Stateful Functions? -->
<li><a href="/stateful-functions.html">What is Stateful Functions?</a></li>
<!-- Use cases -->
<li><a href="/usecases.html">Use Cases</a></li>
<!-- Powered by -->
<li><a href="/poweredby.html">Powered By</a></li>
&nbsp;
<!-- Second menu section aims to support Flink users -->
<!-- Downloads -->
<li><a href="/downloads.html">Downloads</a></li>
<!-- Getting Started -->
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Getting Started<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="https://ci.apache.org/projects/flink/flink-docs-release-1.11/getting-started/index.html" target="_blank">With Flink <small><span class="glyphicon glyphicon-new-window"></span></small></a></li>
<li><a href="https://ci.apache.org/projects/flink/flink-statefun-docs-release-2.1/getting-started/project-setup.html" target="_blank">With Flink Stateful Functions <small><span class="glyphicon glyphicon-new-window"></span></small></a></li>
<li><a href="/training.html">Training Course</a></li>
</ul>
</li>
<!-- Documentation -->
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Documentation<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="https://ci.apache.org/projects/flink/flink-docs-release-1.11" target="_blank">Flink 1.11 (Latest stable release) <small><span class="glyphicon glyphicon-new-window"></span></small></a></li>
<li><a href="https://ci.apache.org/projects/flink/flink-docs-master" target="_blank">Flink Master (Latest Snapshot) <small><span class="glyphicon glyphicon-new-window"></span></small></a></li>
<li><a href="https://ci.apache.org/projects/flink/flink-statefun-docs-release-2.1" target="_blank">Flink Stateful Functions 2.1 (Latest stable release) <small><span class="glyphicon glyphicon-new-window"></span></small></a></li>
<li><a href="https://ci.apache.org/projects/flink/flink-statefun-docs-master" target="_blank">Flink Stateful Functions Master (Latest Snapshot) <small><span class="glyphicon glyphicon-new-window"></span></small></a></li>
</ul>
</li>
<!-- getting help -->
<li><a href="/gettinghelp.html">Getting Help</a></li>
<!-- Blog -->
<li><a href="/blog/"><b>Flink Blog</b></a></li>
<!-- Flink-packages -->
<li>
<a href="https://flink-packages.org" target="_blank">flink-packages.org <small><span class="glyphicon glyphicon-new-window"></span></small></a>
</li>
&nbsp;
<!-- Third menu section aim to support community and contributors -->
<!-- Community -->
<li><a href="/community.html">Community &amp; Project Info</a></li>
<!-- Roadmap -->
<li><a href="/roadmap.html">Roadmap</a></li>
<!-- Contribute -->
<li><a href="/contributing/how-to-contribute.html">How to Contribute</a></li>
<!-- GitHub -->
<li>
<a href="https://github.com/apache/flink" target="_blank">Flink on GitHub <small><span class="glyphicon glyphicon-new-window"></span></small></a>
</li>
&nbsp;
<!-- Language Switcher -->
<li>
<a href="/zh/2019/05/03/pulsar-flink.html">中文版</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-bottom">
<hr />
<!-- Twitter -->
<li><a href="https://twitter.com/apacheflink" target="_blank">@ApacheFlink <small><span class="glyphicon glyphicon-new-window"></span></small></a></li>
<!-- Visualizer -->
<li class=" hidden-md hidden-sm"><a href="/visualizer/" target="_blank">Plan Visualizer <small><span class="glyphicon glyphicon-new-window"></span></small></a></li>
<hr />
<li><a href="https://apache.org" target="_blank">Apache Software Foundation <small><span class="glyphicon glyphicon-new-window"></span></small></a></li>
<li>
<style>
.smalllinks:link {
display: inline-block !important; background: none; padding-top: 0px; padding-bottom: 0px; padding-right: 0px; min-width: 75px;
}
</style>
<a class="smalllinks" href="https://www.apache.org/licenses/" target="_blank">License</a> <small><span class="glyphicon glyphicon-new-window"></span></small>
<a class="smalllinks" href="https://www.apache.org/security/" target="_blank">Security</a> <small><span class="glyphicon glyphicon-new-window"></span></small>
<a class="smalllinks" href="https://www.apache.org/foundation/sponsorship.html" target="_blank">Donate</a> <small><span class="glyphicon glyphicon-new-window"></span></small>
<a class="smalllinks" href="https://www.apache.org/foundation/thanks.html" target="_blank">Thanks</a> <small><span class="glyphicon glyphicon-new-window"></span></small>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</nav>
</div>
<div class="col-sm-9">
<div class="row-fluid">
<div class="col-sm-12">
<div class="row">
<h1>When Flink & Pulsar Come Together</h1>
<p><i></i></p>
<article>
<p>03 May 2019 Sijie Guo (<a href="https://twitter.com/sijieg">@sijieg</a>)</p>
<p>The open source data technology frameworks <a href="https://flink.apache.org/">Apache Flink</a> and <a href="https://pulsar.apache.org/en/">Apache Pulsar</a> can integrate in different ways to provide elastic data processing at large scale. I recently gave a talk at <a href="https://www.flink-forward.org/">Flink Forward</a> San Francisco 2019 and presented some of the integrations between the two frameworks for batch and streaming applications. In this post, I will give a short introduction to Apache Pulsar and its differentiating elements from other messaging systems and describe the ways that Pulsar and Flink can work together to provide a seamless developer experience for elastic data processing at scale.</p>
<h2 id="a-brief-introduction-to-apache-pulsar">A brief introduction to Apache Pulsar</h2>
<p><a href="https://pulsar.apache.org/en/">Apache Pulsar</a> is an open-source distributed pub-sub messaging system under the stewardship of the <a href="https://www.apache.org/">Apache Software Foundation</a>. Pulsar is a multi-tenant, high-performance solution for server-to-server messaging including multiple features such as native support for multiple clusters in a Pulsar instance, with seamless <a href="https://pulsar.apache.org/docs/en/administration-geo">geo-replication</a> of messages across clusters, very low publish and end-to-end latency, seamless scalability to over a million topics, and guaranteed message delivery with <a href="https://pulsar.apache.org/docs/en/concepts-architecture-overview#persistent-storage">persistent message storage</a> provided by <a href="https://bookkeeper.apache.org/">Apache BookKeeper</a> among others. Let’s now discuss the primary differentiators between Pulsar and other pub-sub messaging frameworks:</p>
<p>The first differentiating factor stems from the fact that although Pulsar provides a flexible pub-sub messaging system it is also backed by durable log storage — hence combining both messaging and storage under one framework. Because of that layered architecture, Pulsar provides instant failure recovery, independent scalability and balance-free cluster expansion.</p>
<p>Pulsar’s architecture follows a similar pattern to other pub-sub systems as the framework is organized in topics as the main data entity, with producers sending data to, and consumers receiving data from a topic as shown in the diagram below.</p>
<center>
<img src="/img/blog/pulsar-flink/image-1.png" width="400px" alt="Pulsar producers and consumers" />
</center>
<p><br /></p>
<p>The second differentiator of Pulsar is that the framework is built from the get-go with <a href="https://pulsar.apache.org/docs/en/concepts-multi-tenancy/">multi-tenancy</a> in mind. What that means is that each Pulsar topic has a hierarchical management structure making the allocation of resources as well as the resource management and coordination between teams efficient and easy. With Pulsar’s multi-tenancy structure, data platform maintainers can onboard new teams with no friction as Pulsar provides resource isolation at the property (tenant), namespace or topic level, while at the same time data can be shared across the cluster for easy collaboration and coordination.</p>
<center>
<img src="/img/blog/pulsar-flink/image-2.png" width="640px" alt="Apache Flink and Apache Pulsar" />
</center>
<p><br /></p>
<p>Finally, Pulsar’s flexible messaging framework unifies the streaming and queuing data consumption models and provides greater flexibility. As shown in the below diagram, Pulsar holds the data in the topic while multiple teams can consume the data independently depending on their workloads and data consumption patterns.</p>
<center>
<img src="/img/blog/pulsar-flink/image-3.png" width="640px" alt="Apache Flink and Apache Pulsar" />
</center>
<p><br /></p>
<h2 id="pulsars-view-on-data-segmented-data-streams">Pulsar’s view on data: Segmented data streams</h2>
<p>Apache Flink is a streaming-first computation framework that perceives <a href="/news/2019/02/13/unified-batch-streaming-blink.html">batch processing as a special case of streaming</a>. Flink’s view on data streams distinguishes batch and stream processing between bounded and unbounded data streams, assuming that for batch workloads the data stream is finite, with a beginning and an end.</p>
<p>Apache Pulsar has a similar perspective to that of Apache Flink with regards to the data layer. The framework also uses streams as a unified view on all data, while its layered architecture allows traditional pub-sub messaging for streaming workloads and continuous data processing or usage of <em>Segmented Streams</em> and bounded data stream for batch and static workloads.</p>
<center>
<img src="/img/blog/pulsar-flink/image-4.png" width="640px" alt="Apache Flink and Apache Pulsar" />
</center>
<p><br /></p>
<p>With Pulsar, once a producer sends data to a topic, it is partitioned depending on the data traffic and then further segmented under those partitions — using Apache Bookkeeper as segment store — to allow for parallel data processing as illustrated in the diagram below. This allows a combination of traditional pub-sub messaging and distributed parallel computations in one framework.</p>
<center>
<img src="/img/blog/pulsar-flink/image-5.png" width="640px" alt="Apache Flink and Apache Pulsar" />
</center>
<p><br /></p>
<h2 id="when-flink--pulsar-come-together">When Flink + Pulsar come together</h2>
<p>Apache Flink and Apache Pulsar integrate in multiple ways already. In the following sections, I will present some potential future integrations between the frameworks and share examples of existing ways in which you can utilize the frameworks together.</p>
<h3 id="potential-integrations">Potential Integrations</h3>
<p>Pulsar can integrate with Apache Flink in different ways. Some potential integrations include providing support for streaming workloads with the use of <em>Streaming Connectors</em> and support for batch workloads with the use of <em>Batch Source Connectors</em>. Pulsar also comes with native support for schema that can integrate with Flink and provide structured access to the data, for example by using Flink SQL as a way of querying data in Pulsar. Finally, an alternative way of integrating the technologies could include using Pulsar as a state backend with Flink. Since Pulsar has a layered architecture (<em>Streams</em> and <em>Segmented Streams</em>, powered by Apache Bookkeeper), it becomes natural to use Pulsar as a storage layer and store Flink state.</p>
<p>From an architecture point of view, we can imagine the integration between the two frameworks as one that uses Apache Pulsar for a unified view of the data layer and Apache Flink as a unified computation and data processing framework and API.</p>
<h3 id="existing-integrations">Existing Integrations</h3>
<p>Integration between the two frameworks is ongoing and developers can already use Pulsar with Flink in multiple ways. For example, Pulsar can be used as a streaming source and streaming sink in Flink DataStream applications. Developers can ingest data from Pulsar into a Flink job that makes computations and processes real-time data, to then send the data back to a Pulsar topic as a streaming sink. Such an example is shown below:</p>
<div class="highlight"><pre><code class="language-java"><span class="c1">// create and configure Pulsar consumer</span>
<span class="n">PulsarSourceBuilder</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="n">builder</span> <span class="o">=</span> <span class="n">PulsarSourceBuilder</span>
<span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="k">new</span> <span class="nf">SimpleStringSchema</span><span class="o">())</span>
<span class="o">.</span><span class="na">serviceUrl</span><span class="o">(</span><span class="n">serviceUrl</span><span class="o">)</span>
<span class="o">.</span><span class="na">topic</span><span class="o">(</span><span class="n">inputTopic</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscriptionName</span><span class="o">(</span><span class="n">subscription</span><span class="o">);</span>
<span class="n">SourceFunction</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">src</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="c1">// ingest DataStream with Pulsar consumer</span>
<span class="n">DataStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">words</span> <span class="o">=</span> <span class="n">env</span><span class="o">.</span><span class="na">addSource</span><span class="o">(</span><span class="n">src</span><span class="o">);</span>
<span class="c1">// perform computation on DataStream (here a simple WordCount)</span>
<span class="n">DataStream</span><span class="o">&lt;</span><span class="n">WordWithCount</span><span class="o">&gt;</span> <span class="n">wc</span> <span class="o">=</span> <span class="n">words</span>
<span class="o">.</span><span class="na">flatMap</span><span class="o">((</span><span class="n">FlatMapFunction</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">WordWithCount</span><span class="o">&gt;)</span> <span class="o">(</span><span class="n">word</span><span class="o">,</span> <span class="n">collector</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="o">{</span>
<span class="n">collector</span><span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="k">new</span> <span class="nf">WordWithCount</span><span class="o">(</span><span class="n">word</span><span class="o">,</span> <span class="mi">1</span><span class="o">));</span>
<span class="o">})</span>
<span class="o">.</span><span class="na">returns</span><span class="o">(</span><span class="n">WordWithCount</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="o">.</span><span class="na">keyBy</span><span class="o">(</span><span class="s">&quot;word&quot;</span><span class="o">)</span>
<span class="o">.</span><span class="na">timeWindow</span><span class="o">(</span><span class="n">Time</span><span class="o">.</span><span class="na">seconds</span><span class="o">(</span><span class="mi">5</span><span class="o">))</span>
<span class="o">.</span><span class="na">reduce</span><span class="o">((</span><span class="n">ReduceFunction</span><span class="o">&lt;</span><span class="n">WordWithCount</span><span class="o">&gt;)</span> <span class="o">(</span><span class="n">c1</span><span class="o">,</span> <span class="n">c2</span><span class="o">)</span> <span class="o">-&gt;</span>
<span class="k">new</span> <span class="nf">WordWithCount</span><span class="o">(</span><span class="n">c1</span><span class="o">.</span><span class="na">word</span><span class="o">,</span> <span class="n">c1</span><span class="o">.</span><span class="na">count</span> <span class="o">+</span> <span class="n">c2</span><span class="o">.</span><span class="na">count</span><span class="o">));</span>
<span class="c1">// emit result via Pulsar producer</span>
<span class="n">wc</span><span class="o">.</span><span class="na">addSink</span><span class="o">(</span><span class="k">new</span> <span class="n">FlinkPulsarProducer</span><span class="o">&lt;&gt;(</span>
<span class="n">serviceUrl</span><span class="o">,</span>
<span class="n">outputTopic</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">AuthenticationDisabled</span><span class="o">(),</span>
<span class="n">wordWithCount</span> <span class="o">-&gt;</span> <span class="n">wordWithCount</span><span class="o">.</span><span class="na">toString</span><span class="o">().</span><span class="na">getBytes</span><span class="o">(</span><span class="n">UTF_8</span><span class="o">),</span>
<span class="n">wordWithCount</span> <span class="o">-&gt;</span> <span class="n">wordWithCount</span><span class="o">.</span><span class="na">word</span><span class="o">)</span>
<span class="o">);</span></code></pre></div>
<p>Another integration between the two frameworks that developers can take advantage of includes using Pulsar as both a streaming source and a streaming table sink for Flink SQL or Table API queries as shown in the example below:</p>
<div class="highlight"><pre><code class="language-java"><span class="c1">// obtain a DataStream with words</span>
<span class="n">DataStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">words</span> <span class="o">=</span> <span class="o">...</span>
<span class="c1">// register DataStream as Table &quot;words&quot; with two attributes (&quot;word&quot;, &quot;ts&quot;). </span>
<span class="c1">// &quot;ts&quot; is an event-time timestamp.</span>
<span class="n">tableEnvironment</span><span class="o">.</span><span class="na">registerDataStream</span><span class="o">(</span><span class="s">&quot;words&quot;</span><span class="o">,</span> <span class="n">words</span><span class="o">,</span> <span class="s">&quot;word, ts.rowtime&quot;</span><span class="o">);</span>
<span class="c1">// create a TableSink that produces to Pulsar</span>
<span class="n">TableSink</span> <span class="n">sink</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">PulsarJsonTableSink</span><span class="o">(</span>
<span class="n">serviceUrl</span><span class="o">,</span>
<span class="n">outputTopic</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">AuthenticationDisabled</span><span class="o">(),</span>
<span class="n">ROUTING_KEY</span><span class="o">);</span>
<span class="c1">// register Pulsar TableSink as table &quot;wc&quot;</span>
<span class="n">tableEnvironment</span><span class="o">.</span><span class="na">registerTableSink</span><span class="o">(</span>
<span class="s">&quot;wc&quot;</span><span class="o">,</span>
<span class="n">sink</span><span class="o">.</span><span class="na">configure</span><span class="o">(</span>
<span class="k">new</span> <span class="n">String</span><span class="o">[]{</span><span class="s">&quot;word&quot;</span><span class="o">,</span> <span class="s">&quot;cnt&quot;</span><span class="o">},</span>
<span class="k">new</span> <span class="n">TypeInformation</span><span class="o">[]{</span><span class="n">Types</span><span class="o">.</span><span class="na">STRING</span><span class="o">,</span> <span class="n">Types</span><span class="o">.</span><span class="na">LONG</span><span class="o">}));</span>
<span class="c1">// count words per 5 seconds and write result to table &quot;wc&quot;</span>
<span class="n">tableEnvironment</span><span class="o">.</span><span class="na">sqlUpdate</span><span class="o">(</span>
<span class="s">&quot;INSERT INTO wc &quot;</span> <span class="o">+</span>
<span class="s">&quot;SELECT word, COUNT(*) AS cnt &quot;</span> <span class="o">+</span>
<span class="s">&quot;FROM words &quot;</span> <span class="o">+</span>
<span class="s">&quot;GROUP BY word, TUMBLE(ts, INTERVAL &#39;5&#39; SECOND)&quot;</span><span class="o">);</span></code></pre></div>
<p>Finally, Flink integrates with Pulsar for batch workloads as a batch sink where all results get pushed to Pulsar after Apache Flink has completed the computation in a static data set. Such an example is shown below:</p>
<div class="highlight"><pre><code class="language-java"><span class="c1">// obtain DataSet from arbitrary computation</span>
<span class="n">DataSet</span><span class="o">&lt;</span><span class="n">WordWithCount</span><span class="o">&gt;</span> <span class="n">wc</span> <span class="o">=</span> <span class="o">...</span>
<span class="c1">// create PulsarOutputFormat instance</span>
<span class="n">OutputFormat</span> <span class="n">pulsarOutputFormat</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">PulsarOutputFormat</span><span class="o">(</span>
<span class="n">serviceUrl</span><span class="o">,</span>
<span class="n">topic</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">AuthenticationDisabled</span><span class="o">(),</span>
<span class="n">wordWithCount</span> <span class="o">-&gt;</span> <span class="n">wordWithCount</span><span class="o">.</span><span class="na">toString</span><span class="o">().</span><span class="na">getBytes</span><span class="o">());</span>
<span class="c1">// write DataSet to Pulsar</span>
<span class="n">wc</span><span class="o">.</span><span class="na">output</span><span class="o">(</span><span class="n">pulsarOutputFormat</span><span class="o">);</span></code></pre></div>
<h2 id="conclusion">Conclusion</h2>
<p>Both Pulsar and Flink share a similar view on how the data and the computation level of an application can be <em>“streaming-first”</em> with batch as a special case streaming. With Pulsar’s Segmented Streams approach and Flink’s steps to unify batch and stream processing workloads under one framework, there are numerous ways of integrating the two technologies together to provide elastic data processing at massive scale. Subscribe to the <a href="/community.html#mailing-lists">Apache Flink</a> and <a href="https://lists.apache.org/list.html?dev@pulsar.apache.org">Apache Pulsar</a> mailing lists to stay up-to-date with the latest developments in this space or share your thoughts and recommendations with both communities.</p>
</article>
</div>
<div class="row">
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
var disqus_shortname = 'stratosphere-eu'; // required: replace example with your forum shortname
/* * * DON'T EDIT BELOW THIS LINE * * */
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
</div>
</div>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="footer text-center col-sm-12">
<p>Copyright © 2014-2019 <a href="http://apache.org">The Apache Software Foundation</a>. All Rights Reserved.</p>
<p>Apache Flink, Flink®, Apache®, the squirrel logo, and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation.</p>
<p><a href="/privacy-policy.html">Privacy Policy</a> &middot; <a href="/blog/feed.xml">RSS feed</a></p>
</div>
</div>
</div><!-- /.container -->
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.matchHeight/0.7.0/jquery.matchHeight-min.js"></script>
<script src="/js/codetabs.js"></script>
<script src="/js/stickysidebar.js"></script>
<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-52545728-1', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>