blob: 7d24524434acebbd41e5e897c1b6bca02d15e6f8 [file] [log] [blame]
<!DOCTYPE html>
<html lang="en" dir=ZgotmplZ>
<head>
<link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css">
<script src="/bootstrap/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" type="text/css" href="/font-awesome/css/font-awesome.min.css">
<script src="/js/anchor.min.js"></script>
<script src="/js/flink.js"></script>
<link rel="canonical" href="https://flink.apache.org/2020/08/18/monitoring-and-controlling-networks-of-iot-devices-with-flink-stateful-functions/">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="In this blog post, we&rsquo;ll take a look at a class of use cases that is a natural fit for Flink Stateful Functions: monitoring and controlling networks of connected devices (often called the “Internet of Things” (IoT)).
IoT networks are composed of many individual, but interconnected components, which makes getting some kind of high-level insight into the status, problems, or optimization opportunities in these networks not trivial. Each individual device “sees” only its own state, which means that the status of groups of devices, or even the network as a whole, is often a complex aggregation of the individual devices’ state.">
<meta name="theme-color" content="#FFFFFF"><meta property="og:title" content="Monitoring and Controlling Networks of IoT Devices with Flink Stateful Functions" />
<meta property="og:description" content="In this blog post, we&rsquo;ll take a look at a class of use cases that is a natural fit for Flink Stateful Functions: monitoring and controlling networks of connected devices (often called the “Internet of Things” (IoT)).
IoT networks are composed of many individual, but interconnected components, which makes getting some kind of high-level insight into the status, problems, or optimization opportunities in these networks not trivial. Each individual device “sees” only its own state, which means that the status of groups of devices, or even the network as a whole, is often a complex aggregation of the individual devices’ state." />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://flink.apache.org/2020/08/18/monitoring-and-controlling-networks-of-iot-devices-with-flink-stateful-functions/" /><meta property="article:section" content="posts" />
<meta property="article:published_time" content="2020-08-18T08:00:00+00:00" />
<meta property="article:modified_time" content="2020-08-18T08:00:00+00:00" />
<title>Monitoring and Controlling Networks of IoT Devices with Flink Stateful Functions | Apache Flink</title>
<link rel="manifest" href="/manifest.json">
<link rel="icon" href="/favicon.png" type="image/x-icon">
<link rel="stylesheet" href="/book.min.22eceb4d17baa9cdc0f57345edd6f215a40474022dfee39b63befb5fb3c596b5.css" integrity="sha256-IuzrTRe6qc3A9XNF7dbyFaQEdAIt/uObY777X7PFlrU=">
<script defer src="/en.search.min.2698f0d1b683dae4d6cb071668b310a55ebcf1c48d11410a015a51d90105b53e.js" integrity="sha256-Jpjw0baD2uTWywcWaLMQpV688cSNEUEKAVpR2QEFtT4="></script>
<!--
Made with Book Theme
https://github.com/alex-shpak/hugo-book
-->
<meta name="generator" content="Hugo 0.124.1">
<script>
var _paq = window._paq = window._paq || [];
_paq.push(['disableCookies']);
_paq.push(["setDomains", ["*.flink.apache.org","*.nightlies.apache.org/flink"]]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//analytics.apache.org/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '1']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
</head>
<body dir=ZgotmplZ>
<header>
<nav class="navbar navbar-expand-xl">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<img src="/img/logo/png/100/flink_squirrel_100_color.png" alt="Apache Flink" height="47" width="47" class="d-inline-block align-text-middle">
<span>Apache Flink</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<i class="fa fa-bars navbar-toggler-icon"></i>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">About</a>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="/what-is-flink/flink-architecture/">Architecture</a>
</li>
<li>
<a class="dropdown-item" href="/what-is-flink/flink-applications/">Applications</a>
</li>
<li>
<a class="dropdown-item" href="/what-is-flink/flink-operations/">Operations</a>
</li>
<li>
<a class="dropdown-item" href="/what-is-flink/use-cases/">Use Cases</a>
</li>
<li>
<a class="dropdown-item" href="/what-is-flink/powered-by/">Powered By</a>
</li>
<li>
<a class="dropdown-item" href="/what-is-flink/roadmap/">Roadmap</a>
</li>
<li>
<a class="dropdown-item" href="/what-is-flink/community/">Community & Project Info</a>
</li>
<li>
<a class="dropdown-item" href="/what-is-flink/security/">Security</a>
</li>
<li>
<a class="dropdown-item" href="/what-is-flink/special-thanks/">Special Thanks</a>
</li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Getting Started</a>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-docs-stable/docs/try-flink/local_installation/">With Flink<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-kubernetes-operator-docs-stable/docs/try-flink-kubernetes-operator/quick-start/">With Flink Kubernetes Operator<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-cdc-docs-stable/docs/get-started/introduction/">With Flink CDC<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-ml-docs-stable/docs/try-flink-ml/quick-start/">With Flink ML<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-statefun-docs-stable/getting-started/project-setup.html">With Flink Stateful Functions<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-docs-stable/docs/learn-flink/overview/">Training Course<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Documentation</a>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-docs-stable/">Flink 1.19 (stable)<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-docs-master/">Flink Master (snapshot)<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-kubernetes-operator-docs-stable/">Kubernetes Operator 1.8 (latest)<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-kubernetes-operator-docs-main">Kubernetes Operator Main (snapshot)<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-cdc-docs-stable">CDC 3.0 (stable)<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-cdc-docs-master">CDC Master (snapshot)<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-ml-docs-stable/">ML 2.3 (stable)<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-ml-docs-master">ML Master (snapshot)<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-statefun-docs-stable/">Stateful Functions 3.3 (stable)<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
<li>
<a class="dropdown-item" href="https://nightlies.apache.org/flink/flink-statefun-docs-master">Stateful Functions Master (snapshot)<i class="link fa fa-external-link title" aria-hidden="true"></i>
</a>
</li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">How to Contribute</a>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="/how-to-contribute/overview/">Overview</a>
</li>
<li>
<a class="dropdown-item" href="/how-to-contribute/contribute-code/">Contribute Code</a>
</li>
<li>
<a class="dropdown-item" href="/how-to-contribute/reviewing-prs/">Review Pull Requests</a>
</li>
<li>
<a class="dropdown-item" href="/how-to-contribute/code-style-and-quality-preamble/">Code Style and Quality Guide</a>
</li>
<li>
<a class="dropdown-item" href="/how-to-contribute/contribute-documentation/">Contribute Documentation</a>
</li>
<li>
<a class="dropdown-item" href="/how-to-contribute/documentation-style-guide/">Documentation Style Guide</a>
</li>
<li>
<a class="dropdown-item" href="/how-to-contribute/improve-website/">Contribute to the Website</a>
</li>
<li>
<a class="dropdown-item" href="/how-to-contribute/getting-help/">Getting Help</a>
</li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="/posts/">Flink Blog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/downloads/">Downloads</a>
</li>
</ul>
<div class="book-search">
<div class="book-search-spinner hidden">
<i class="fa fa-refresh fa-spin"></i>
</div>
<form class="search-bar d-flex" onsubmit="return false;"su>
<input type="text" id="book-search-input" placeholder="Search" aria-label="Search" maxlength="64" data-hotkeys="s/">
<i class="fa fa-search search"></i>
<i class="fa fa-circle-o-notch fa-spin spinner"></i>
</form>
<div class="book-search-spinner hidden"></div>
<ul id="book-search-results"></ul>
</div>
</div>
</div>
</nav>
<div class="navbar-clearfix"></div>
</header>
<main class="flex">
<section class="container book-page">
<article class="markdown">
<h1>
<a href="/2020/08/18/monitoring-and-controlling-networks-of-iot-devices-with-flink-stateful-functions/">Monitoring and Controlling Networks of IoT Devices with Flink Stateful Functions</a>
</h1>
August 18, 2020 -
Igal Shilman
<a href="https://twitter.com/IgalShilman">(@IgalShilman)</a>
<p><p>In this blog post, we&rsquo;ll take a look at a class of use cases that is a natural fit for <a href="https://flink.apache.org/stateful-functions.html">Flink Stateful Functions</a>: monitoring and controlling networks of connected devices (often called the “Internet of Things” (IoT)).</p>
<p>IoT networks are composed of many individual, but interconnected components, which makes getting some kind of high-level insight into the status, problems, or optimization opportunities in these networks not trivial. Each individual device “sees” only its own state, which means that the status of groups of devices, or even the network as a whole, is often a complex aggregation of the individual devices’ state. Diagnosing, controlling, or optimizing these groups of devices thus requires distributed logic that analyzes the &ldquo;bigger picture&rdquo; and then acts upon it.</p>
<p>A powerful approach to implement this is using <em><a href="https://en.wikipedia.org/wiki/Digital_twin">digital twins</a></em>: each device has a corresponding virtual entity (i.e. the digital twin), which also captures their relationships and interactions. The digital twins track the status of their corresponding devices and send updates to other twins, representing groups (such as geographical regions) of devices. Those, in turn, handle the logic to obtain the network&rsquo;s aggregated view, or this &ldquo;bigger picture&rdquo; we mentioned before.</p>
<h1 id="our-scenario-datacenter-monitoring-and-alerting">
Our Scenario: Datacenter Monitoring and Alerting
<a class="anchor" href="#our-scenario-datacenter-monitoring-and-alerting">#</a>
</h1>
<figure style="float:right;padding-left:1px;padding-top: 20px">
<img src="/img/blog/2020-08-18-statefun/rack.png" width="350px">
<figcaption style="padding-top: 10px;text-align:center"><b>Fig.1</b> An oversimplified view of a data center.</figcaption>
</figure>
<p>There are many examples of the digital twins approach in the real world, such as <a href="https://www.infoq.com/presentations/tesla-vpp/">smart grids of batteries</a>, <a href="https://www.alibabacloud.com/solutions/intelligence-brain/city">smart cities</a>, or <a href="https://www.youtube.com/watch?v=9y27FJgz5-M">monitoring infrastructure software clusters</a>. In this blogpost, we&rsquo;ll use the example of data center monitoring and alert correlation implemented with Stateful Functions.</p>
<p>Consider a very simplified view of a data center, consisting of many thousands of commodity servers arranged in server racks. Each server rack typically contains up to 40 servers, with a ToR (Top of the Rack) network switch connected to each server. The switches from all the racks connect through a larger switch (<strong>Fig. 1</strong>).</p>
<p>In this datacenter, many things can go wrong: a disk in a server can stop working, network cards can start dropping packets, or ToR switches might cease to function. The entire data center might also be affected by power supply degradation, causing servers to operate at reduced capacity. On-site engineers must be able to identify these incidents quickly and fix them promptly.</p>
<p>Diagnosing individual server failures is rather straightforward: take a recent history of metric reports from that particular server, analyse it and pinpoint the anomaly. On the other hand, other incidents only make sense &ldquo;together&rdquo;, because they share a common root cause. Diagnosing or predicting causes of networking degradation at a rack or datacenter level requires an aggregate view of metrics (such as package drop rates) from the individual machines and racks, and possibly some prediction model or diagnosis code that runs under certain conditions.</p>
<h2 id="monitoring-a-virtual-datacenter-via-digital-twins">
Monitoring a Virtual Datacenter via Digital Twins
<a class="anchor" href="#monitoring-a-virtual-datacenter-via-digital-twins">#</a>
</h2>
<p>For the sake of this blog post, our oversimplified data center has some servers and racks, each with a unique ID. Each server has a metrics-collecting daemon that publishes metrics to a message queue, and there is a provisioning service that operators will use to ask for server commission- and decommissioning.</p>
<div style="line-height:60%;">
<br>
</div>
<center>
<img src="/img/blog/2020-08-18-statefun/1.png" width="550px" alt=""/>
</center>
<div style="line-height:60%;">
<br>
</div>
<p>Our application will consume these server metrics and commission/decommission events, and produce server/rack/datacenter alerts. There will also be an operator consuming any alerts triggered by the monitoring system. In the next section, we&rsquo;ll show how this use case can be naturally modeled with Stateful Functions (StateFun).</p>
<h2 id="implementing-the-use-case-with-flink-statefun">
Implementing the use case with Flink StateFun
<a class="anchor" href="#implementing-the-use-case-with-flink-statefun">#</a>
</h2>
<div class="alert alert-info" markdown="1">
You can find the code for this example at: <a href="https://github.com/igalshilman/iot-statefun-blogpost">https://github.com/igalshilman/iot-statefun-blogpost</a>
</div>
<p>The basic building block for modeling a StateFun application is a <a href="//nightlies.apache.org/flink/flink-statefun-docs-release-2.1/concepts/application-building-blocks.html#stateful-functions"><em>stateful function</em></a>, which has the following properties:</p>
<ul>
<li>
<p>It has a logical unique address; and persisted, fault tolerant state, scoped to that address.</p>
</li>
<li>
<p>It can <em>react</em> to messages, both internal (or, sent from other stateful functions) and external (e.g. a message from Kafka).</p>
</li>
<li>
<p>Invocations of a specific function are serializable, so messages sent to a specific address are <strong>not</strong> executed concurrently.</p>
</li>
<li>
<p>There can be many billions of function instances in a single StateFun cluster.</p>
</li>
</ul>
<p>To model our use case, we&rsquo;ll define three functions: <strong>ServerFun</strong>, <strong>RackFun</strong> and <strong>DataCenterFun</strong>.</p>
<p><strong>ServerFun</strong></p>
<p>Each physical server is represented with its <em>digital twin</em> stateful function. This function is responsible for:</p>
<ol>
<li>
<p>Maintaining a sliding window of incoming metrics.</p>
</li>
<li>
<p>Applying a model that decides whether or not to trigger an alert.</p>
</li>
<li>
<p>Alerting if metrics are missing for too long.</p>
</li>
<li>
<p>Notifying its containing <strong>RackFun</strong> about any open incidents.</p>
</li>
</ol>
<p><strong>RackFun</strong></p>
<p>While the <em>ServerFun</em> is responsible for identifying server-local incidents, we need a function that correlates incidents happening on the different servers deployed in the same rack and:</p>
<ol>
<li>
<p>Collects open incidents reported by the <strong>ServerFun</strong> functions.</p>
</li>
<li>
<p>Maintains an histogram of currently opened incidents on this rack.</p>
</li>
<li>
<p>Applies a correlation model to the individual incidents sent by the <strong>ServerFun</strong>, and reports high-level, related incidents as a single incident to the <strong>DataCenterFun</strong>.</p>
</li>
</ol>
<p><strong>DataCenterFun</strong></p>
<p>This function maintains a view of incidents across different racks in our datacenter.</p>
<div style="line-height:60%;">
<br>
</div>
<center>
<img src="/img/blog/2020-08-18-statefun/2.png" width="600px" alt=""/>
</center>
<div style="line-height:60%;">
<br>
</div>
<p>To summarize our plan:</p>
<ul>
<li>
<p>Leaf functions ingest raw metric data (<span style="color:blue">blue</span> lines), and apply localized logic to trigger an alert.</p>
</li>
<li>
<p>Intermediate functions operate on already summarized events (<span style="color:orange">orange</span> lines) and correlate them into high-level events.</p>
</li>
<li>
<p>A root function correlates the high-level events across the intermediate functions and into a single <em>healthy/not healthy</em> value.</p>
</li>
</ul>
<h2 id="how-does-it-really-look">
How does it really look?
<a class="anchor" href="#how-does-it-really-look">#</a>
</h2>
<h3 id="serverfun">
ServerFun
<a class="anchor" href="#serverfun">#</a>
</h3>
<center>
<img src="/img/blog/2020-08-18-statefun/3_1.png" width="600px" alt=""/>
</center>
<div style="line-height:60%;">
<br>
</div>
<ol>
<li>This section associates a behaviour for every message that the function expects to be invoked with.</li>
<li>The <code>metricsHistory</code> buffer is our sliding window of the last 15 minutes worth of <code>ServerMetricReports</code>. Note that this buffer is configured to expire entries 15 minutes after they were written.</li>
<li><code>serverHealthState</code> represents the current physical server state, open incidents and so on.</li>
</ol>
<p>Let&rsquo;s take a look at what happens when a <code>ServerMetricReport</code> message arrives:</p>
<center>
<img src="/img/blog/2020-08-18-statefun/3_2.png" width="600px" alt=""/>
</center>
<div style="line-height:60%;">
<br>
</div>
<ol>
<li>Retrieve the previously computed <code>serverHealthState</code> that is kept in state.</li>
<li>Evaluate a model on the sliding window of the previous metric reports + the current metric reported + the previously computed server state to obtain an assessment of the current server health.</li>
<li>If the server is not believed to be healthy, emit an alert via an alerts topic, and also send a message to our containing rack with all the open incidents that this server currently has.</li>
</ol>
<div class="alert alert-warning" markdown="1">
We'll omit the other handlers for brevity, but it's important to mention that <b>onTimer</b> makes sure that metric reports are coming in periodically, otherwise it'd trigger an alert stating that we didn’t hear from that server for a long time.
</div>
<h3 id="rackfun">
RackFun
<a class="anchor" href="#rackfun">#</a>
</h3>
<center>
<img src="/img/blog/2020-08-18-statefun/5.png" width="650px" alt=""/>
</center>
<div style="line-height:60%;">
<br>
</div>
<ol>
<li>This function keeps a mapping between a <code>ServerId</code> and a set of open incidents on that server.</li>
<li>When new alerts are received, this function tries to correlate the alert with any other open alerts on that rack. If a correlated rack alert is present, this function notifies the <strong>DataCenterFun</strong> about it.</li>
</ol>
<h3 id="datacenterfun">
DataCenterFun
<a class="anchor" href="#datacenterfun">#</a>
</h3>
<center>
<img src="/img/blog/2020-08-18-statefun/6.png" width="650px" alt=""/>
</center>
<div style="line-height:60%;">
<br>
</div>
<ol>
<li>A persisted mapping between a <code>RackId</code> and the latest alert that rack reported.</li>
<li>Throughout the usage of ingress/egress pairs, this function can report back its current view of the world of what racks are currently known to be unhealthy.</li>
<li>An operator (via a front-end) can send a <code>GetUnhealthyRacks</code> message addressed to that <strong>DataCenterFun</strong>, and wait for the corresponding response <code>message(UnhealthyRacks)</code>. Whenever a rack reports <em>OK</em>, it&rsquo;ll be removed from the unhealthy racks map.</li>
</ol>
<h2 id="conclusion">
Conclusion
<a class="anchor" href="#conclusion">#</a>
</h2>
<p>This pattern — where each layer of functions performs a stateful aggregation of events sent from the previous layer (or the input) — is useful for a whole class of problems. And, although we used connected devices to motivate this use case, it&rsquo;s not limited to the IoT domain.</p>
<center>
<img src="/img/blog/2020-08-18-statefun/7.png" width="500px" alt=""/>
</center>
<div style="line-height:60%;">
<br>
</div>
<p>Stateful Functions provides the building blocks necessary for building complex distributed applications (here the digital twins that support analysis and interactions of the physical entities), while removing common complexities of distributed systems like service discovery, retires, circuit breakers, state management, scalability and similar challenges. If you&rsquo;d like to learn more about Stateful Functions, head over to the official <a href="//nightlies.apache.org/flink/flink-statefun-docs-master/">documentation</a>, where you can also find more hands-on tutorials to try out yourself!</p>
</p>
</article>
<div class="edit-this-page">
<p>
<a href="https://cwiki.apache.org/confluence/display/FLINK/Flink+Translation+Specifications">Want to contribute translation?</a>
</p>
<p>
<a href="//github.com/apache/flink-web/edit/asf-site/docs/content/posts/2020-08-18-statefun.md">
Edit This Page<i class="fa fa-edit fa-fw"></i>
</a>
</p>
</div>
</section>
<aside class="book-toc">
<nav id="TableOfContents"><h3>On This Page <a href="javascript:void(0)" class="toc" onclick="collapseToc()"><i class="fa fa-times" aria-hidden="true"></i></a></h3>
<ul>
<li><a href="#our-scenario-datacenter-monitoring-and-alerting">Our Scenario: Datacenter Monitoring and Alerting</a>
<ul>
<li><a href="#monitoring-a-virtual-datacenter-via-digital-twins">Monitoring a Virtual Datacenter via Digital Twins</a></li>
<li><a href="#implementing-the-use-case-with-flink-statefun">Implementing the use case with Flink StateFun</a></li>
<li><a href="#how-does-it-really-look">How does it really look?</a>
<ul>
<li><a href="#serverfun">ServerFun</a></li>
<li><a href="#rackfun">RackFun</a></li>
<li><a href="#datacenterfun">DataCenterFun</a></li>
</ul>
</li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</li>
</ul>
</nav>
</aside>
<aside class="expand-toc hidden">
<a class="toc" onclick="expandToc()" href="javascript:void(0)">
<i class="fa fa-bars" aria-hidden="true"></i>
</a>
</aside>
</main>
<footer>
<div class="separator"></div>
<div class="panels">
<div class="wrapper">
<div class="panel">
<ul>
<li>
<a href="https://flink-packages.org/">flink-packages.org</a>
</li>
<li>
<a href="https://www.apache.org/">Apache Software Foundation</a>
</li>
<li>
<a href="https://www.apache.org/licenses/">License</a>
</li>
<li>
<a href="/zh/">
<i class="fa fa-globe" aria-hidden="true"></i>&nbsp;中文版
</a>
</li>
</ul>
</div>
<div class="panel">
<ul>
<li>
<a href="/what-is-flink/security">Security</a-->
</li>
<li>
<a href="https://www.apache.org/foundation/sponsorship.html">Donate</a>
</li>
<li>
<a href="https://www.apache.org/foundation/thanks.html">Thanks</a>
</li>
</ul>
</div>
<div class="panel icons">
<div>
<a href="/posts">
<div class="icon flink-blog-icon"></div>
<span>Flink blog</span>
</a>
</div>
<div>
<a href="https://github.com/apache/flink">
<div class="icon flink-github-icon"></div>
<span>Github</span>
</a>
</div>
<div>
<a href="https://twitter.com/apacheflink">
<div class="icon flink-twitter-icon"></div>
<span>Twitter</span>
</a>
</div>
</div>
</div>
</div>
<hr/>
<div class="container disclaimer">
<p>The contents of this website are © 2024 Apache Software Foundation under the terms of the Apache License v2. Apache Flink, Flink, and the Flink logo are either registered trademarks or trademarks of The Apache Software Foundation in the United States and other countries.</p>
</div>
</footer>
</body>
</html>