├── doc ├── memorysegment.md ├── zeppelin.md ├── exactlyonce.md ├── projection.md ├── streamingandsink.md ├── flinkonzeppelin.md ├── task.md ├── thinking2.md ├── loadclass.md ├── kafkasource.md ├── datadeduplication.md ├── realtimedatawarehouse.md ├── idlestate.md ├── spi.md ├── datadeliver.md ├── partitioner.md ├── sql.md ├── sourceandsink.md ├── latency.md ├── scheduler.md ├── capandbase.md ├── eventtimeandwatermark.md ├── triggerandevictor.md ├── streamtask.md ├── typeerasureandtypehint.md ├── operatorchain.md ├── streamoperator.md ├── memory.md ├── window.md ├── ttl.md ├── 2pc.md ├── raft.md ├── thinking.md ├── nio.md ├── backpressure.md ├── statestore.md ├── jobsubmit.md ├── timer.md ├── flinkkafkaconsumer.md └── chandylamport.md ├── docs ├── _build │ ├── html │ │ ├── _static │ │ │ ├── custom.css │ │ │ ├── file.png │ │ │ ├── minus.png │ │ │ ├── plus.png │ │ │ ├── fonts │ │ │ │ ├── Lato-Bold.ttf │ │ │ │ ├── Inconsolata.ttf │ │ │ │ ├── Lato-Regular.ttf │ │ │ │ ├── Lato │ │ │ │ │ ├── lato-bold.eot │ │ │ │ │ ├── lato-bold.ttf │ │ │ │ │ ├── lato-bold.woff │ │ │ │ │ ├── lato-bold.woff2 │ │ │ │ │ ├── lato-italic.eot │ │ │ │ │ ├── lato-italic.ttf │ │ │ │ │ ├── lato-italic.woff │ │ │ │ │ ├── lato-regular.eot │ │ │ │ │ ├── lato-regular.ttf │ │ │ │ │ ├── lato-bolditalic.eot │ │ │ │ │ ├── lato-bolditalic.ttf │ │ │ │ │ ├── lato-italic.woff2 │ │ │ │ │ ├── lato-regular.woff │ │ │ │ │ ├── lato-regular.woff2 │ │ │ │ │ ├── lato-bolditalic.woff │ │ │ │ │ └── lato-bolditalic.woff2 │ │ │ │ ├── RobotoSlab-Bold.ttf │ │ │ │ ├── Inconsolata-Bold.ttf │ │ │ │ ├── Inconsolata-Regular.ttf │ │ │ │ ├── RobotoSlab-Regular.ttf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ └── RobotoSlab │ │ │ │ │ ├── roboto-slab-v7-bold.eot │ │ │ │ │ ├── roboto-slab-v7-bold.ttf │ │ │ │ │ ├── roboto-slab-v7-bold.woff │ │ │ │ │ ├── roboto-slab-v7-bold.woff2 │ │ │ │ │ ├── roboto-slab-v7-regular.eot │ │ │ │ │ ├── roboto-slab-v7-regular.ttf │ │ │ │ │ ├── roboto-slab-v7-regular.woff │ │ │ │ │ └── roboto-slab-v7-regular.woff2 │ │ │ ├── documentation_options.js │ │ │ ├── css │ │ │ │ └── badge_only.css │ │ │ ├── js │ │ │ │ └── theme.js │ │ │ └── pygments.css │ │ ├── objects.inv │ │ ├── _images │ │ │ ├── shuffle.png │ │ │ ├── shuffle-data.png │ │ │ └── flink-submit-to-yarn.png │ │ ├── .buildinfo │ │ ├── _sources │ │ │ ├── index.rst.txt │ │ │ ├── contents.rst.txt │ │ │ ├── example.rst.txt │ │ │ ├── dataDeliver.rst.txt │ │ │ └── jobSubmit.rst.txt │ │ ├── genindex.html │ │ ├── search.html │ │ ├── index.html │ │ ├── contents.html │ │ ├── example.html │ │ └── dataDeliver.html │ ├── latex │ │ ├── LatinRules.xdy │ │ ├── flink-submit-to-yarn.png │ │ ├── latexmkrc │ │ ├── python.ist │ │ ├── make.bat │ │ ├── latexmkjarc │ │ ├── sphinxmessages.sty │ │ ├── Makefile │ │ ├── sphinxcyrillic.sty │ │ ├── sphinxhowto.cls │ │ ├── sphinxmanual.cls │ │ ├── LICRcyr2utf8.xdy │ │ ├── flink.tex │ │ └── sphinxhighlight.sty │ ├── doctrees │ │ ├── index.doctree │ │ ├── contents.doctree │ │ ├── example.doctree │ │ ├── dataDeliver.doctree │ │ ├── environment.pickle │ │ └── jobSubmit.doctree │ └── text │ │ ├── index.txt │ │ ├── example.txt │ │ └── jobSubmit.txt ├── image │ ├── shuffle.png │ ├── shuffle-data.png │ └── flink-submit-to-yarn.png ├── contents.rst ├── Makefile ├── example.rst ├── make.bat ├── dataDeliver.rst ├── conf.py └── jobSubmit.rst ├── images ├── 2pc.jpg ├── ttl.png ├── type.png ├── apiandspi.jpg ├── asyncio.png ├── latency.jpg ├── memorynew.png ├── shuffle.png ├── typeinfo.png ├── atleastonce.jpg ├── checkpoint.jpeg ├── exactlyonce.jpg ├── flinkkafka.png ├── partitioner.png ├── shuffledata.png ├── streamtask.png ├── cpand2pccommit.jpg ├── flinkcomponent.png ├── memorymanage.png ├── operatorchain.png ├── streamoperator.png ├── cpand2pcprepare.jpg ├── typeinfotomemory.png └── flinksubmittoyarn.png ├── .idea ├── copyright │ └── profiles_settings.xml ├── vcs.xml ├── modules.xml ├── flink-source-zh.iml ├── compiler.xml └── misc.xml ├── .gitignore └── README.md /doc/memorysegment.md: -------------------------------------------------------------------------------- 1 | ### MemorySegment 2 | 3 | -------------------------------------------------------------------------------- /doc/zeppelin.md: -------------------------------------------------------------------------------- 1 | ### Apache Zeppelin 2 | 3 | -------------------------------------------------------------------------------- /docs/_build/html/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* This file intentionally left blank. */ 2 | -------------------------------------------------------------------------------- /images/2pc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/2pc.jpg -------------------------------------------------------------------------------- /images/ttl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/ttl.png -------------------------------------------------------------------------------- /images/type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/type.png -------------------------------------------------------------------------------- /images/apiandspi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/apiandspi.jpg -------------------------------------------------------------------------------- /images/asyncio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/asyncio.png -------------------------------------------------------------------------------- /images/latency.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/latency.jpg -------------------------------------------------------------------------------- /images/memorynew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/memorynew.png -------------------------------------------------------------------------------- /images/shuffle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/shuffle.png -------------------------------------------------------------------------------- /images/typeinfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/typeinfo.png -------------------------------------------------------------------------------- /docs/image/shuffle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/image/shuffle.png -------------------------------------------------------------------------------- /images/atleastonce.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/atleastonce.jpg -------------------------------------------------------------------------------- /images/checkpoint.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/checkpoint.jpeg -------------------------------------------------------------------------------- /images/exactlyonce.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/exactlyonce.jpg -------------------------------------------------------------------------------- /images/flinkkafka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/flinkkafka.png -------------------------------------------------------------------------------- /images/partitioner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/partitioner.png -------------------------------------------------------------------------------- /images/shuffledata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/shuffledata.png -------------------------------------------------------------------------------- /images/streamtask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/streamtask.png -------------------------------------------------------------------------------- /images/cpand2pccommit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/cpand2pccommit.jpg -------------------------------------------------------------------------------- /images/flinkcomponent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/flinkcomponent.png -------------------------------------------------------------------------------- /images/memorymanage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/memorymanage.png -------------------------------------------------------------------------------- /images/operatorchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/operatorchain.png -------------------------------------------------------------------------------- /images/streamoperator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/streamoperator.png -------------------------------------------------------------------------------- /docs/image/shuffle-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/image/shuffle-data.png -------------------------------------------------------------------------------- /images/cpand2pcprepare.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/cpand2pcprepare.jpg -------------------------------------------------------------------------------- /images/typeinfotomemory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/typeinfotomemory.png -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/_build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/objects.inv -------------------------------------------------------------------------------- /images/flinksubmittoyarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/images/flinksubmittoyarn.png -------------------------------------------------------------------------------- /docs/_build/latex/LatinRules.xdy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/latex/LatinRules.xdy -------------------------------------------------------------------------------- /docs/_build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/_build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/file.png -------------------------------------------------------------------------------- /docs/_build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/image/flink-submit-to-yarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/image/flink-submit-to-yarn.png -------------------------------------------------------------------------------- /docs/_build/doctrees/contents.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/doctrees/contents.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/example.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/doctrees/example.doctree -------------------------------------------------------------------------------- /docs/_build/html/_images/shuffle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_images/shuffle.png -------------------------------------------------------------------------------- /docs/_build/doctrees/dataDeliver.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/doctrees/dataDeliver.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/_build/doctrees/jobSubmit.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/doctrees/jobSubmit.doctree -------------------------------------------------------------------------------- /docs/_build/html/_images/shuffle-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_images/shuffle-data.png -------------------------------------------------------------------------------- /docs/_build/latex/flink-submit-to-yarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/latex/flink-submit-to-yarn.png -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /doc/exactlyonce.md: -------------------------------------------------------------------------------- 1 | exactly-once 2 | ---------------- 3 | 4 | Flink作为下一代实时流式计算引擎,相比于早期原生Storm的重要特性之一就是它所提供的exactly-once特性,以及以此为基础提供的端到端exactly-once。 5 | 6 | -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Inconsolata.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Inconsolata.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /docs/_build/html/_images/flink-submit-to-yarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_images/flink-submit-to-yarn.png -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-bold.eot -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-bold.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-bold.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/RobotoSlab-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/RobotoSlab-Bold.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Inconsolata-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Inconsolata-Bold.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-italic.eot -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-italic.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-italic.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-regular.eot -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-regular.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Inconsolata-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Inconsolata-Regular.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-bolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-bolditalic.eot -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-bolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-bolditalic.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-italic.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-regular.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-regular.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/RobotoSlab-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/RobotoSlab-Regular.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlzhang0122/flink-source-zh/HEAD/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Example user template template 3 | ### Example user template 4 | 5 | # IntelliJ project files 6 | .idea 7 | *.iml 8 | out 9 | gen 10 | -------------------------------------------------------------------------------- /docs/_build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: c7976bc93c2b98792a8e5ce034a8bfcf 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /doc/projection.md: -------------------------------------------------------------------------------- 1 | ###数据查询优化的利器-谓词下推 2 | 3 | 面对快速发展的数据查询领域,以及人类所拥有的数据量的爆发式增长,尤其是AI和大模型的迅猛发展,如何对海量数据进行管理、分析、查询和挖掘变得及其重 4 | 要,SQL优化器就是为了解决以上问题而诞生的。 5 | SQL优化器中最重要的一个组件是查询优化器,它是数据库、数据仓库、数据湖等系统的重要组成部分。特别是对于现代大数据系统,执行计划的搜索空间非常庞 6 | 大,很多时候需要扫描的数据会非常多,但很多时候扫描的结果其实根本不需要,如何对执行计划空间进行裁剪,尽量减少搜索空间的代价,从而加速查询成为了 7 | 研究人员研究的重点。 -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /doc/streamingandsink.md: -------------------------------------------------------------------------------- 1 | ### Flink Sink 2 | 3 | Flink Table/SQL API有三种输出同步方式:Append、Upsert和Retract。 4 | 5 | 在具体介绍这三种方式前,先了解一下基础知识。 6 | 首先是,流表二象性。简单来说就是,流和表是同一事实的不同表现,可以相互转化。流体现的是事实在时间维度上的变化,而表则体现的是事实在某一时间点的视图。 7 | 8 | 那么如何将流转化为表呢,只需要将聚合统计函数应用在流上,比如按照url对用户访问日志进行聚合就能形成类似(url, view count)的表。 9 | 那么怎么样可以将表转化为流呢? 10 | -------------------------------------------------------------------------------- /.idea/flink-source-zh.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/_build/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: 'v0.1', 4 | LANGUAGE: 'None', 5 | COLLAPSE_INDEX: false, 6 | FILE_SUFFIX: '.html', 7 | HAS_SOURCE: true, 8 | SOURCELINK_SUFFIX: '.txt', 9 | NAVIGATION_WITH_KEYS: false 10 | }; -------------------------------------------------------------------------------- /docs/_build/text/index.txt: -------------------------------------------------------------------------------- 1 | Welcome to zlzhang0122’s documentation! 2 | *************************************** 3 | 4 | Contents: 5 | 6 | * This is a Title 7 | 8 | * Subject Subtitle 9 | 10 | * Inline Markup 11 | 12 | * 任务提交 13 | 14 | * 任务提交源码解析 15 | 16 | * Inline Markup 17 | 18 | 19 | Indices and tables 20 | ****************** 21 | 22 | * Index 23 | 24 | * Module Index 25 | 26 | * Search Page 27 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. Flink源码阅读 documentation master file, created by 2 | sphinx-quickstart on Thu Oct 24 19:54:15 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Flink源码阅读文档! 7 | ========================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | jobSubmit.rst 14 | -------------------------------------------------------------------------------- /docs/contents.rst: -------------------------------------------------------------------------------- 1 | .. Flink源码阅读 documentation master file, created by 2 | sphinx-quickstart on Thu Oct 24 19:54:15 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Flink源码阅读文档! 7 | ========================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | jobSubmit.rst 14 | dataDeliver.rst 15 | -------------------------------------------------------------------------------- /doc/flinkonzeppelin.md: -------------------------------------------------------------------------------- 1 | ### Flink on Zeppelin 2 | 3 | 首先需要安装zeppelin。下载zeppelin,解压缩,将conf文件夹下的zeppelin-env.sh.template重命名为zeppelin-env.sh,并增加配置 4 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home。 5 | 然后即可启动zeppelin。启动命令为bin/zeppelin-daemon.sh start,zeppelin默认启动地址是127.0.0.1,也就是本机,这样就只能在启动 6 | zeppelin的机器上访问,若想在所有机器上都能访问,可将zeppelin.server.addr设置为0.0.0.0,默认端口是8080,因此在zeppelin启动后即 7 | 可在浏览器输入127.0.0.1:8080访问zeppelin。 8 | -------------------------------------------------------------------------------- /docs/_build/latex/latexmkrc: -------------------------------------------------------------------------------- 1 | $latex = 'latex ' . $ENV{'LATEXOPTS'} . ' %O %S'; 2 | $pdflatex = 'pdflatex ' . $ENV{'LATEXOPTS'} . ' %O %S'; 3 | $lualatex = 'lualatex ' . $ENV{'LATEXOPTS'} . ' %O %S'; 4 | $xelatex = 'xelatex --no-pdf ' . $ENV{'LATEXOPTS'} . ' %O %S'; 5 | $makeindex = 'makeindex -s python.ist %O -o %D %S'; 6 | add_cus_dep( "glo", "gls", 0, "makeglo" ); 7 | sub makeglo { 8 | return system( "makeindex -s gglo.ist -o '$_[0].gls' '$_[0].glo'" ); 9 | } -------------------------------------------------------------------------------- /docs/_build/html/_sources/contents.rst.txt: -------------------------------------------------------------------------------- 1 | .. Flink源码阅读 documentation master file, created by 2 | sphinx-quickstart on Thu Oct 24 19:54:15 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Flink源码阅读文档! 7 | ========================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | jobSubmit.rst 14 | dataDeliver.rst 15 | -------------------------------------------------------------------------------- /docs/_build/latex/python.ist: -------------------------------------------------------------------------------- 1 | line_max 100 2 | headings_flag 1 3 | heading_prefix " \\bigletter " 4 | 5 | preamble "\\begin{sphinxtheindex} 6 | \\let\\bigletter\\sphinxstyleindexlettergroup 7 | \\let\\spxpagem \\sphinxstyleindexpagemain 8 | \\let\\spxentry \\sphinxstyleindexentry 9 | \\let\\spxextra \\sphinxstyleindexextra 10 | 11 | " 12 | 13 | postamble "\n\n\\end{sphinxtheindex}\n" 14 | 15 | symhead_positive "{\\sphinxsymbolsname}" 16 | numhead_positive "{\\sphinxnumbersname}" 17 | -------------------------------------------------------------------------------- /doc/task.md: -------------------------------------------------------------------------------- 1 | ### StreamTask 2 | 3 | 当StreamTask被调度执行的时候,其具体的生命周期如下: 4 | * -- setInitialState -> provides state of all operators in the chain 5 | * -- invoke() 6 | * | 7 | * +----> Create basic utils (config, etc) and load the chain of operators 8 | * +----> operators.setup() 9 | * +----> task specific init() 10 | * +----> initialize-operator-states() 11 | * +----> open-operators() 12 | * +----> run() 13 | * +----> close-operators() 14 | * +----> dispose-operators() 15 | * +----> common cleanup 16 | * +----> task specific cleanup() -------------------------------------------------------------------------------- /doc/thinking2.md: -------------------------------------------------------------------------------- 1 | ### 大state优化 2 | 3 | 我们知道Flink目前主要支持三种StateBackend,分别是MemoryStateBackend、RocksDBStateBackend和FsStateBackend,由于MemoryStateBackend受限 4 | 于本地机器的内存且其checkpoint受限于JobManager的内存,所以在实际的生产环境基本上不会使用,而FsStateBackend在大state时保存会非常慢且耗时,所以 5 | 当状态较大时目前只有RocksDBStateBackend能够选择,一方面其使用RocksDB在内存中保存state比较快速,另一方面支持增量checkpoint也能较快的进行快照。 6 | 7 | 但是RocksDB是基于LSM树原理实现的KV数据库,如果听说过LSM树就应该知道它存在读放大问题(没听过也没关系,简单点说就是在进行读取时需要从新数据到老数据 8 | 不断进行查找,直到找到所需数据),当然它会在后台对数据进行合并和压缩以减少读放大问题,但这就同时导致产生了写放大问题。不管是读放大问题,还是写放大问 9 | 题,都导致其对磁盘的性能要求非常之高,所以通常在生产环境中都会使用SSD硬盘作为RocksDB的存储介质,否则在频繁访问状态时,磁盘的I/O就可能成为性能瓶颈。 10 | 11 | 如果我们确实没有办法更换SSD的硬盘,又该如何解决这个瓶颈呢? 12 | -------------------------------------------------------------------------------- /docs/_build/latex/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | pushd %~dp0 6 | 7 | set PDFLATEX=latexmk -pdf -dvi- -ps- 8 | 9 | set "LATEXOPTS= " 10 | 11 | if "%1" == "" goto all-pdf 12 | 13 | if "%1" == "all-pdf" ( 14 | :all-pdf 15 | for %%i in (*.tex) do ( 16 | %PDFLATEX% %LATEXMKOPTS% %%i 17 | ) 18 | goto end 19 | ) 20 | 21 | if "%1" == "all-pdf-ja" ( 22 | goto all-pdf 23 | ) 24 | 25 | if "%1" == "clean" ( 26 | del /q /s *.dvi *.log *.ind *.aux *.toc *.syn *.idx *.out *.ilg *.pla *.ps *.tar *.tar.gz *.tar.bz2 *.tar.xz *.fls *.fdb_latexmk 27 | goto end 28 | ) 29 | 30 | :end 31 | popd -------------------------------------------------------------------------------- /doc/loadclass.md: -------------------------------------------------------------------------------- 1 | ### 类加载 2 | 3 | 在Flink开发的过程中,很容易遇到的一个问题就是jar包冲突的问题,由于一个Flink应用程序包含有大量的jar包依赖,而Flink本省也同样有许多的jar 4 | 包依赖,这两者之间极有可能产生冲突,比如Flink中依赖了某个具体的jar包的版本,而用户程序中极有可能依赖该jar包的一个更新或是更老的版本。而这 5 | 两个版本之间极有可能在同一个类的方法接口上存在差异,从而出现依赖冲突导致的运行时异常。 6 | 7 | 解决这种问题一般有两种办法,一种是直接将冲突的jar包打shaded,这样系统的jar包和用户jar包拥有不同的全限定名,也就不会产生冲突了。另一个方法 8 | 是控制类在classpath的顺序,如果一个类在前面加载过后在后面将不再会被加载,但要实现这样的精准控制比较得不容易,因为classpath中jar包顺序的 9 | 调整可能会影响到jar包内多个类的加载顺序,因此最简单的办法就是打破双亲委派模型,也就是在加载依赖时不使用双亲委派模型,而是首先尝试加载用户提 10 | 供的依赖,这样也能较好的解决问题,Flink中提供了child-first的类加载策略来实现。其具体的实现其实非常之简单,就是首先有一个alwaysParentFirstPatterns 11 | 的过滤器判断,因为还是有些内部类和基础类不希望被子加载起篡改。经过这个过滤器的过滤后才会真的打破双亲委派模型,它首先查看类是否已经被加载,如果 12 | 没有加载的话,不再先尝试使用parent类加载器去加载,而是直接由自己尝试去加载,通过这种方式扭转了类加载的顺序。 -------------------------------------------------------------------------------- /doc/kafkasource.md: -------------------------------------------------------------------------------- 1 | ### Kafka Source 2 | 3 | 在Flink 1.12中,基于Flip-27对KafkaSource重新的改造,不仅实现了批流一体,可以通过Bounded和UnBounded来统一实现批处理和流处理,而且还通过在 4 | JobManager上增加Split Enumerator及在TaskManager上增加Source Reader来进行RPC通信,新的架构图如下图所示(图片来自Flink官网): 5 | ![架构图](../images/kafka_source.svg "架构图") 6 | 7 | 在Flink 1.12以前,kafka的消息都是无界流,如果想要回溯指定时间段内的消息,就只能自己手动控制消费消息的边界,在消费完成后退出,其实在生产过程中还 8 | 是很有可能遇到这样的需求,毕竟某段时间的数据处理异常需要回溯数据是一个常见的需求,在1.12以前,我们在内部框架上自己实现了这样的一个功能。从Flink 9 | 1.12开始,新的KafkaSource已经可以非常方便的实现这个需求了。 10 | 11 | 可以首先查看KafkaSource的源代码,在其最上面的类注释中可以看到一个非常常见的KafkaSource的调用方式,用到的一些参数都是消费kafka时常见的一些参数。 12 | 我们可以发现它其实是使用了KafkaSourceBuilder这个类来创建的,可以通过setUnbounded()/setBounded()来设置无界和有界,而从StreamGraphGenerator 13 | 类可以看出有界和无界其实会在最开始生成StreamGraph时就确定了。 -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /doc/datadeduplication.md: -------------------------------------------------------------------------------- 1 | ### 数据去重 2 | 3 | 数据去重是大数据领域经常碰见的问题,其目的在于清洗掉不可靠数据源产生的脏数据,使得计算结果更加精确。在常见的实时数据去重方案中, 4 | HashSet由于是实现简单使用方便而得到了广泛的应用。此外,也还有一些其它的方案,如:布隆过滤器(Bloom Filter)、RocksDB StateBackend 5 | 以及外部存储。下面分别就这三种去重方案进行分析: 6 | * 布隆过滤器(Bloom Filter):作为消耗较少效率较高的方案在允许一定的误判的情况下是首选,如果不考虑自己重复造一遍轮子,可以 7 | 使用Guava的BloomFilter来实现去重,使用put()方法放入数据,使用mightContain()判断是否存在,其特点是如果它判断不存在则数 8 | 据一定不存在,如果它判断存在,则数据有一定的概率不存在(这叫做假阳性概率,在构建BloomFilter时可以指定)。 9 | * RocksDB StateBackend:前面已经提到布隆过滤器存在一定概率的误判,因此在一些要求非常精确的场合并不适用。在必须保证非常准确 10 | 的场景下,可以选择使用Flink内置的RocksDB状态后端,后果是状态会极其巨大。 11 | * 引入外部K-V存储:实际上,这种方案与上述的RocksDB状态后端方案相差不大,因为RocksDB也是一种K-V存储,只不过它是由Flink所管 12 | 理的,如果不想在Flink中维护巨大的状态,就可以选择此种方案。缺点是如果作业重启,外部存储是不会同步恢复到一致的状态的,此时结果可 13 | 能出现偏差。 -------------------------------------------------------------------------------- /docs/_build/text/example.txt: -------------------------------------------------------------------------------- 1 | This is a Title 2 | *************** 3 | 4 | That has a paragraph about a main subject and is set when the ‘=’ is 5 | at least the same length of the title itself. 6 | 7 | 8 | Subject Subtitle 9 | ================ 10 | 11 | Subtitles are set with ‘-‘ and are required to have the same length of 12 | the subtitle itself, just like titles. 13 | 14 | Lists can be unnumbered like: 15 | 16 | * Item Foo 17 | 18 | * Item Bar 19 | 20 | Or automatically numbered: 21 | 22 | 1. Item 1 23 | 24 | 2. Item 2 25 | 26 | 27 | Inline Markup 28 | ============= 29 | 30 | Words can have *emphasis in italics* or be **bold** and you can define 31 | code samples with back quotes, like when you talk about a command: 32 | "sudo" gives you super user powers! 33 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/_build/latex/latexmkjarc: -------------------------------------------------------------------------------- 1 | $latex = 'platex ' . $ENV{'LATEXOPTS'} . ' -kanji=utf8 %O %S'; 2 | $dvipdf = 'dvipdfmx %O -o %D %S'; 3 | $makeindex = 'internal mendex %S %B %D'; 4 | sub mendex { 5 | my ($source, $basename, $destination) = @_; 6 | my $dictfile = $basename . ".dic"; 7 | unlink($destination); 8 | system("mendex", "-U", "-f", "-d", $dictfile, "-s", "python.ist", $source); 9 | if ($? > 0) { 10 | print("mendex exited with error code $? (ignored)\n"); 11 | } 12 | if (!-e $destination) { 13 | # create an empty .ind file if nothing 14 | open(FH, ">" . $destination); 15 | close(FH); 16 | } 17 | return 0; 18 | } 19 | add_cus_dep( "glo", "gls", 0, "makeglo" ); 20 | sub makeglo { 21 | return system( "mendex -J -f -s gglo.ist -o '$_[0].gls' '$_[0].glo'" ); 22 | } 23 | -------------------------------------------------------------------------------- /docs/_build/latex/sphinxmessages.sty: -------------------------------------------------------------------------------- 1 | % 2 | % sphinxmessages.sty 3 | % 4 | % message resources for Sphinx 5 | % 6 | \ProvidesPackage{sphinxmessages}[2019/01/04 v2.0 Localized LaTeX macros (Sphinx team)] 7 | 8 | \renewcommand{\literalblockcontinuedname}{continued from previous page} 9 | \renewcommand{\literalblockcontinuesname}{continues on next page} 10 | \renewcommand{\sphinxnonalphabeticalgroupname}{Non-alphabetical} 11 | \renewcommand{\sphinxsymbolsname}{Symbols} 12 | \renewcommand{\sphinxnumbersname}{Numbers} 13 | \def\pageautorefname{page} 14 | 15 | \addto\captionsenglish{\renewcommand{\figurename}{Fig.\@{} }} 16 | \def\fnum@figure{\figurename\thefigure{}} 17 | 18 | \addto\captionsenglish{\renewcommand{\tablename}{Table }} 19 | \def\fnum@table{\tablename\thetable{}} 20 | 21 | \addto\captionsenglish{\renewcommand{\literalblockname}{Listing}} -------------------------------------------------------------------------------- /docs/example.rst: -------------------------------------------------------------------------------- 1 | This is a Title 2 | =============== 3 | That has a paragraph about a main subject and is set when the '=' 4 | is at least the same length of the title itself. 5 | 6 | Subject Subtitle 7 | ---------------- 8 | Subtitles are set with '-' and are required to have the same length 9 | of the subtitle itself, just like titles. 10 | 11 | Lists can be unnumbered like: 12 | 13 | * Item Foo 14 | * Item Bar 15 | 16 | Or automatically numbered: 17 | 18 | #. Item 1 19 | #. Item 2 20 | 21 | Inline Markup 22 | ------------- 23 | Words can have *emphasis in italics* or be **bold** and you can define 24 | code samples with back quotes, like when you talk about a command: ``sudo`` 25 | gives you super user powers! 26 | 27 | Indices and tables 28 | ================== 29 | 30 | * :ref:`genindex` 31 | * :ref:`modindex` 32 | * :ref:`search` -------------------------------------------------------------------------------- /docs/_build/html/_sources/example.rst.txt: -------------------------------------------------------------------------------- 1 | This is a Title 2 | =============== 3 | That has a paragraph about a main subject and is set when the '=' 4 | is at least the same length of the title itself. 5 | 6 | Subject Subtitle 7 | ---------------- 8 | Subtitles are set with '-' and are required to have the same length 9 | of the subtitle itself, just like titles. 10 | 11 | Lists can be unnumbered like: 12 | 13 | * Item Foo 14 | * Item Bar 15 | 16 | Or automatically numbered: 17 | 18 | #. Item 1 19 | #. Item 2 20 | 21 | Inline Markup 22 | ------------- 23 | Words can have *emphasis in italics* or be **bold** and you can define 24 | code samples with back quotes, like when you talk about a command: ``sudo`` 25 | gives you super user powers! 26 | 27 | Indices and tables 28 | ================== 29 | 30 | * :ref:`genindex` 31 | * :ref:`modindex` 32 | * :ref:`search` -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /doc/realtimedatawarehouse.md: -------------------------------------------------------------------------------- 1 | ### 实时数仓 2 | 3 | 数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持管理和决策。数据仓库的数据来源一般有两类:一种是日志类型的数据源,主要 4 | 通过客户端埋点上报,从而收集用户的行为日志,以及一些后端日志。另一种是业务数据库,主要通过Canal收集binlog,然后放入到消息队列当中,最终再导出到 5 | HDFS中。这两类数据源最终都会将数据落地到HDFS的ODS层,也就是操作数据层(或者叫贴源数据层),它的数据与原始数据源中的数据基本是一致的。一般会对ODS中 6 | 的数据进行清洗加工、归一化,从而得到DWD明细层数据,这些数据既包含指标类型的数据,也包含统一的维度数据。在DWD明细层之上,一般会根据分析场景、分析实 7 | 体去组装数据,形成一些分主题的汇总数据DWS,这样的汇总属于轻度汇总数据。在DWS层之上还会按照应用场景再次进行开发,从而得到能够直接提供给业务使用的数 8 | 据,这样的数据是高度汇总的、能直接被使用的,一般存放在HBase、Redis、Kylin甚至是MySql中。一般还会对其中的一部分数据封装成统一的接口提供给业务方 9 | 使用。当然,也会有一些报表工具、基于OLAP的产品等。这就是传统的离线数仓的整个架构,以及应用场景。 10 | 11 | 传统的离线数仓架构时效性较差,一般都是T+1的数据,周期性较长。为了提升效率,Storm的作者提出了一种Lambda的架构,它采用离线+实时的方式来产生实时的 12 | 数据。之所以这样,主要是因为早期的实时计算框架如Storm等都不是很完善,无法保证计算结果的精确性。因此采用在原有的离线数仓架构的基础上增加实时部分,再 13 | 用离线数据修正实时数据,从而在保证数据的最终精确性的同时,也将原来的T+1的数据产出时间缩短到了分钟级,甚至是秒级。它存在的缺陷是,实时部分和离线部分 14 | 的数据逻辑基本一致,但是却必须同时维护两套,这对于开发和运维的成本来说,都是比较高的。基于这样的问题存在,于是产生了Kappa架构。 15 | 16 | Kappa架构将离线数仓移除,其数据生产全部使用实时数仓,其消费消息队列,并将消息队列作为了一种数据的暂存。如果需要重刷历史数据,可以直接重新消费消息队 17 | 列中的数据。当然这会涉及到大量实时数据的处理,为了减轻这些处理的复杂性,也可以考虑将聚合、分析、计算交由OLAP引擎来处理,从而使数据仓库的处理更加轻 18 | 量简单。 -------------------------------------------------------------------------------- /doc/idlestate.md: -------------------------------------------------------------------------------- 1 | ### Idle State Retention Time 2 | 3 | 在Flink SQL的开发中,新手开发者特别容易遇到的一个问题就是状态的无限制暴增,并由此导致的checkpoint慢甚至是失败问题, 4 | 以及容器化场景下的内存超用被kill的问题。在Flink Streaming的开发中,从Flink 1.6开始可以通过State TTL机制来设置 5 | 状态的过期时间和策略,虽然目前来说该设置不一定能及时的清除失效的状态,而且目前也只支持Processing Time时间(Event 6 | Time时间的目前已经在规划中),但是总的来说还是特别有效的。那么在Flink SQL场景下,该怎么办呢?这就要说到Flink 提供 7 | 的另一个失效状态清除机制--Idle State Retention Time。 8 | 9 | 它目前主要针对的是Table API和SQL模式下的持续查询和聚合语句,主要是通过借助于Query Configuration配置项来使其生效。 10 | 我们知道如果一个持续查询语句没有时间窗口的定义,那么从理论上来说,它是会无限的计算下去的,由此可能导致的问题就是随着时 11 | 间的不断增长,其内存中的状态也会越来越多,就会导致快照越来越大,同时占用的内存也会越来越多,最终超出超出内存上限作业被 12 | kill。如果我们在存放每个状态时就能给其设置一个过期时间和定时器,如果在状态存放之后每次被访问过时就更新其过期时间,如果 13 | 一直未被访问,就表明该状态已不再需要,在定时器触发时对其进行清理,这样不久可以确保无效的状态都可以得到及时的清理吗?当然 14 | 这个方案存在的问题就是过期时间不太好确定。 15 | 16 | FLink SQL中通过StreamQueryConfig的withIdleStateRetentionTime()方法,来为其设置最长和最短的清理时间,这样就可以 17 | 保证状态最终都被被清除,最长和最短清理时间的差距最少为5分钟,这样能够避免大量状态数据在同一瞬间过期,从而对系统造成压力。 18 | 19 | 在程序中可以通过StreamQueryConfig来设置状态的过期时间,但默认情况下其设置并不是全局的,因此在设置了清理周期后,需要在 20 | StreamTableEnvironment类调用toAppendStream()或toRetractStream()将Table转为DataStream时,显式传入QueryConfig 21 | 对象作为参数,该功能才会生效。 22 | 23 | -------------------------------------------------------------------------------- /docs/_build/text/jobSubmit.txt: -------------------------------------------------------------------------------- 1 | 任务提交 2 | ******** 3 | 4 | Flink任务在被提交到Yarn上后会经过如下流程,具体如下: 5 | 6 | [image] 7 | 8 | 1. Client从客户端代码生成的StreamGraph提取出JobGraph; 9 | 10 | 2. 上传JobGraph和对应的jar包; 11 | 12 | 3. 启动App Master; 13 | 14 | 4. 启动JobManager; 15 | 16 | 5. 启动ResourceManager; 17 | 18 | 6. JobManager向ResourceManager申请slots; 19 | 20 | 7. ResourceManager向Yarn ResourceManager申请Container; 21 | 22 | 8. 启动申请到的Container; 23 | 24 | 9. 启动的Container作为TaskManager向ResourceManager注册; 25 | 26 | 10. ResourceManger向TaskManager请求slot; 27 | 28 | 11. TaskManager提供slot给JobManager,让其分配任务执行. 29 | 30 | 31 | 任务提交源码解析 32 | ================ 33 | 34 | Subtitles are set with '-' and are required to have the same length of 35 | the subtitle itself, just like titles. 36 | 37 | Lists can be unnumbered like: 38 | 39 | * Item Foo 40 | 41 | * Item Bar 42 | 43 | [image] 44 | 45 | Or automatically numbered: 46 | 47 | 1. Item 1 48 | 49 | 2. Item 2 50 | 51 | 52 | Inline Markup 53 | ============= 54 | 55 | Words can have *emphasis in italics* or be **bold** and you can define 56 | code samples with back quotes, like when you talk about a command: 57 | "sudo" gives you super user powers! 58 | -------------------------------------------------------------------------------- /doc/spi.md: -------------------------------------------------------------------------------- 1 | ### Java SPI 2 | 3 | API是站在应用的角度定义了功能如何实现,SPI是系统为第三方专门开发的扩展规范以及动态加载扩展点的机制。下图反映了API和SPI之间的不同: 4 | ![API和SPI](../images/apiandspi.jpg "API和SPI") 5 | 6 | 当作为服务提供方利用SPI机制时,需要遵循SPI的约定: 7 | * 先编写好服务接口的实现类,即服务提供类; 8 | * 在classpath的META-INF/services目录下创建一个以接口全限定名命名的UTF-8文本文件,并在该文件中写入实现类的全限定名(多个实现类以换行符分隔); 9 | * 调用JDK中的java.util.ServiceLoader组件中的load()方法,根据上述文件发现并加载具体的服务实现; 10 | 11 | LazyIterator是一个懒加载服务提供类的迭代器(ServiceLoader本身也实现了Iterable接口),维护在lookupIterator中。在实际应用中,我们需要调用 12 | ServiceLoader#iterator()方法获取加载到的服务提供类的结果,它返回一个标准的迭代器,先从缓存的providers容器中获取,如果获取不到再通过lookupIterator 13 | 进行懒加载。 14 | 15 | 内部类LazyIterator的hasNextService()方法负责在上述SPI定义文件中逐个寻找对应的服务提供类并加载资源,而nextService()方法则通过反射创建服务提 16 | 供类的实例并缓存下来,直到完成整个发现与注入的流程,所以是懒加载的。由此也可得知,SPI机制内部一定会遍历所有的扩展点并将它们全部加载(这也是其主要缺点)。 17 | 18 | 下面以JDBC和Flink为例来分析SPI的实际应用。 19 | 20 | JDBC为用户通过Java访问数据库提供了统一的接口,而数据库的类型多种多样,并且其类型还会不断的增加,因此借助于SPI机制可以灵活的实现数据库驱动的插件化。 21 | 在使用旧版JDBC时,必须首先通过调用类似Class.forName("com.mysql.jdbc.Driver")方法,通过反射的方式来手动加载数据库驱动。新版的JDBC只需直接调 22 | 用DriverManager.getConnection()方法即可获得数据库连接。如果加载了多个JDBC驱动类,则获取数据库连接时会遍历所有已经注册的驱动实例,逐个调用其 23 | connect()方法尝试是否能够成功建立连接,并返回第一个成功的连接。 24 | 25 | SPI机制在Flink的Table模块中也有广泛的应用,因为Flink Table的类型也有很多种,TableFactory就是Flink提供的SPI工厂接口。通过分析TableFactoryService#findSingleInternal() 26 | 方法,可以看到discoverFactories()方法用来发现并加载Table的服务提供类,filter()方法则用于过滤出满足当前应用需要的服务提供类。 27 | 28 | 以上就是SPI及其在JDBC和Flink中的一些应用了,今天锻炼了半个小时,实在是太困了。 29 | -------------------------------------------------------------------------------- /docs/dataDeliver.rst: -------------------------------------------------------------------------------- 1 | 数据传递 2 | =============== 3 | 本节主要介绍数据在各个节点之间是如何进行传递的,以及如何在数据传递的过程中进行自然的反压. 4 | 5 | 数据传递流程 6 | ---------------- 7 | .. image:: image/shuffle.png 8 | 9 | 整体实现步骤如下: 10 | 11 | #. 在M1/M2处理完数据后,本地需要ResultPartition RS1/RS2来临时存储数据; 12 | #. 通知JobManager,上游有新的数据产生; 13 | #. JobManager通知和调度下游节点可以消费新的数据; 14 | #. 下游节点向上游请求数据; 15 | #. 通过Channel在各个TaskManager之间传递数据. 16 | 17 | 数据在节点之间传递的具体流程如下图: 18 | 19 | .. image:: image/shuffle-data.png 20 | 21 | * 数据在operator处理完成后,先交给RecordWriter,每条记录都要选择一个下游节点,所以要经过ChannelSelector; 22 | * 在每个channel都有一个serializer,把这条Record序列化为ByteBuffer; 23 | * 接下来数据被写入ResultPartition下的各个ResultSubPartition里,此时该数据已经存入MemorySegment; 24 | * 单独的线程控制数据的flush速度,一旦触发flush,则通过Netty的NIO通道向对端写入; 25 | * 接收端的Netty Client收到数据后,进行decode操作,把数据拷贝到Buffer里,然后通知InputChannel; 26 | * 当InputChannel中有可用的数据时,下游算子从阻塞醒来,从InputChannel取出Buffer,再反序列化成Record,并将其交给算子执行相应的用户代码 27 | 28 | 数据传递源码 29 | ---------------- 30 | 31 | 首先,将数据流中的数据交给RecordWriter. 32 | 然后,选择序列化器并序列化数据,写入到相应的Channel. 33 | 当输出缓冲中的字节数超过了高水位值,则Channel.isWritable()会返回false.当输出缓存中的字节数又掉到低水位值以下,则Channel.isWritable()会重新返回true. 34 | 核心发送方法中如果channel不可写,则会跳过发送.当channel再次可写后,Netty会调用该Handle的handleWritabilityChanged方法,从而重新出发发送函数. 35 | 36 | Flink通过Credit实现网络流控,即下游会向上游发送一条credit message,用以通知其目前可用信用额度,然后上游会根据这个信用消息来决定向下游发送多少数据.当 37 | 上游把数据发送给下游时,它就从下游信用上划走相应的额度. 38 | 39 | 在上游通过Channel发送数据后,下游通过decodeMsg来获取数据. 40 | 41 | Flink其实做阻塞和获取数据的方式非常自然,利用了生产者和消费者模型,当获取不到数据时,消费者自然阻塞;当数据被加入队列,消费者被notify。 42 | Flink的背压机制也是借此实现。 43 | 至此,Flink数据在节点之间传递的过程便介绍完成。 -------------------------------------------------------------------------------- /docs/_build/html/_sources/dataDeliver.rst.txt: -------------------------------------------------------------------------------- 1 | 数据传递 2 | =============== 3 | 本节主要介绍数据在各个节点之间是如何进行传递的,以及如何在数据传递的过程中进行自然的反压. 4 | 5 | 数据传递流程 6 | ---------------- 7 | .. image:: image/shuffle.png 8 | 9 | 整体实现步骤如下: 10 | 11 | #. 在M1/M2处理完数据后,本地需要ResultPartition RS1/RS2来临时存储数据; 12 | #. 通知JobManager,上游有新的数据产生; 13 | #. JobManager通知和调度下游节点可以消费新的数据; 14 | #. 下游节点向上游请求数据; 15 | #. 通过Channel在各个TaskManager之间传递数据. 16 | 17 | 数据在节点之间传递的具体流程如下图: 18 | 19 | .. image:: image/shuffle-data.png 20 | 21 | * 数据在operator处理完成后,先交给RecordWriter,每条记录都要选择一个下游节点,所以要经过ChannelSelector; 22 | * 在每个channel都有一个serializer,把这条Record序列化为ByteBuffer; 23 | * 接下来数据被写入ResultPartition下的各个ResultSubPartition里,此时该数据已经存入MemorySegment; 24 | * 单独的线程控制数据的flush速度,一旦触发flush,则通过Netty的NIO通道向对端写入; 25 | * 接收端的Netty Client收到数据后,进行decode操作,把数据拷贝到Buffer里,然后通知InputChannel; 26 | * 当InputChannel中有可用的数据时,下游算子从阻塞醒来,从InputChannel取出Buffer,再反序列化成Record,并将其交给算子执行相应的用户代码 27 | 28 | 数据传递源码 29 | ---------------- 30 | 31 | 首先,将数据流中的数据交给RecordWriter. 32 | 然后,选择序列化器并序列化数据,写入到相应的Channel. 33 | 当输出缓冲中的字节数超过了高水位值,则Channel.isWritable()会返回false.当输出缓存中的字节数又掉到低水位值以下,则Channel.isWritable()会重新返回true. 34 | 核心发送方法中如果channel不可写,则会跳过发送.当channel再次可写后,Netty会调用该Handle的handleWritabilityChanged方法,从而重新出发发送函数. 35 | 36 | Flink通过Credit实现网络流控,即下游会向上游发送一条credit message,用以通知其目前可用信用额度,然后上游会根据这个信用消息来决定向下游发送多少数据.当 37 | 上游把数据发送给下游时,它就从下游信用上划走相应的额度. 38 | 39 | 在上游通过Channel发送数据后,下游通过decodeMsg来获取数据. 40 | 41 | Flink其实做阻塞和获取数据的方式非常自然,利用了生产者和消费者模型,当获取不到数据时,消费者自然阻塞;当数据被加入队列,消费者被notify。 42 | Flink的背压机制也是借此实现。 43 | 至此,Flink数据在节点之间传递的过程便介绍完成。 -------------------------------------------------------------------------------- /doc/datadeliver.md: -------------------------------------------------------------------------------- 1 | 数据传递 2 | ---------------- 3 | 4 | 本节主要介绍数据在各个节点之间是如何进行传递的,以及如何在数据传递的过程中进行自然的反压. 5 | 6 | ### 数据传递流程 7 | 8 | ![Shuffle](../images/shuffle.png "Shuffle") 9 | 10 | 整体实现步骤如下: 11 | 12 | 1. 在M1/M2处理完数据后,本地需要ResultPartition RS1/RS2来临时存储数据; 13 | 14 | 2. 通知JobManager,上游有新的数据产生; 15 | 16 | 3. JobManager通知和调度下游节点可以消费新的数据; 17 | 18 | 4. 下游节点向上游请求数据; 19 | 20 | 5. 通过Channel在各个TaskManager之间传递数据. 21 | 22 | 数据在节点之间传递的具体流程如下图: 23 | 24 | ![Shuffle-data](../images/shuffledata.png "Shuffle-data") 25 | 26 | * 数据在operator处理完成后,先交给RecordWriter,每条记录都要选择一个下游节点,所以要经过ChannelSelector; 27 | * 在每个channel都有一个serializer,把这条Record序列化为ByteBuffer; 28 | * 接下来数据被写入ResultPartition下的各个ResultSubPartition里,此时该数据已经存入MemorySegment; 29 | * 单独的线程控制数据的flush速度,一旦触发flush,则通过Netty的NIO通道向对端写入; 30 | * 接收端的Netty Client收到数据后,进行decode操作,把数据拷贝到Buffer里,然后通知InputChannel; 31 | * 当InputChannel中有可用的数据时,下游算子从阻塞醒来,从InputChannel取出Buffer,再反序列化成Record,并将其交给算子执行相应的用户代码 32 | 33 | ### 数据传递源码 34 | 35 | 首先,将数据流中的数据交给RecordWriter. 36 | 然后,选择序列化器并序列化数据,写入到相应的Channel. 37 | 当输出缓冲中的字节数超过了高水位值,则Channel.isWritable()会返回false.当输出缓存中的字节数又掉到低水位值以下,则Channel.isWritable()会重新返回true. 38 | 核心发送方法中如果channel不可写,则会跳过发送.当channel再次可写后,Netty会调用该Handle的handleWritabilityChanged方法,从而重新出发发送函数. 39 | 40 | Flink通过Credit实现网络流控,即下游会向上游发送一条credit message,用以通知其目前可用信用额度,然后上游会根据这个信用消息来决定向下游发送多少数据.当 41 | 上游把数据发送给下游时,它就从下游信用上划走相应的额度. 42 | 43 | 在上游通过Channel发送数据后,下游通过decodeMsg来获取数据. 44 | 45 | Flink其实做阻塞和获取数据的方式非常自然,利用了生产者和消费者模型,当获取不到数据时,消费者自然阻塞;当数据被加入队列,消费者被notify。 46 | Flink的背压机制也是借此实现。 47 | 48 | 至此,Flink数据在节点之间传递的过程便介绍完成。 -------------------------------------------------------------------------------- /doc/partitioner.md: -------------------------------------------------------------------------------- 1 | ### 分区器Partitioner 2 | 3 | 在Flink中通过流分区器StreamPartitioner来控制DataStream中的元素流向下游算子的哪些分区中,其类图如下所示: 4 | ![Flink分区器](../images/partitioner.png "Flink分区器") 5 | 6 | StreamPartitioner继承自ChannelSelector接口,这个channel表示的是下游算子的并发实例(也即物理分区),除BroadcastPartitioner外的所有StreamPartitioner 7 | 的子类都要实现selectChannel()方法,用于选择逻辑分区号。下面分别来看一下Flink提供的分区器: 8 | * BinaryHashPartitioner:用于BinaryRow的hash分区器,它先使用通用的Hash函数,然后使用MurmurHash对传入的记录计算哈希值,再按总分区数取模 9 | 来得到下游算子的分区号; 10 | 11 | * BroadcastPartitioner:流广播专用的分区器,它总是会将数据输出到下游算子的所有并发中去,所以并没有任何实现; 12 | 13 | * CustomPartitionerWrapper:自定义的分区逻辑,可以用过继承Partitioner接口自己实现其中的partition()方法,并将其传给partitionCustorm() 14 | 方法; 15 | 16 | * GlobalPartitioner:类似于Storm中的全局分组,它也只会将数据输出到下游算子的第一个实例,非常的简单; 17 | 18 | * ForwardPartitioner:其实现与GlobalPartitioner类相同,它将数据数据输出到本地运行的下游算子的第一个实例,而不是全局的第一个实例,如果上下 19 | 游算子的并行度相同,默认采用ForwardPartitioner,否则默认采用RebalancePartitioner; 20 | 21 | * KeyGroupStreamPartitioner:keyBy()算子底层所采用的流分区器,逻辑是先从记录上提取出key的值,然后在key值的基础上经过了两重哈希得到key 22 | 对应的哈希值,第一重是Java自带的hashCode(),第二重则是MurmurHash。然后将哈希值乘以算子并行度,并除以最大并行度,得到最终的分区ID; 23 | 24 | * RebalancePartitioner:先随机选择一个下游算子的实例,然后使用轮询的方式从该实例开始循环输出,该方式能保证完全的下游负载均衡,所以常用于 25 | 处理有倾斜的原数据流; 26 | 27 | * RescalePartitioner:看代码实现有点类似于上面介绍的RebalancePartitioner,实则不然,从StreamingJobGraphGenerator类的connect()方法 28 | 里可以看到,如果分区逻辑是RescalePartitioner或ForwardPartitioner,那么采用的分布式模式是POINTWISE模式连接上下游顶点,而对于其它模式是 29 | ALL_TO_ALL模式连接。前者在中间结果传递给下游节点时会根据并行度比值来轮询分配给下游算子实例的子集,且会优先选择本地算子;而后者则是真正的全局轮询 30 | 分配,结果更加均衡但会产生节点间数据交换的开销; 31 | 32 | * ShufflePartitioner:非常之简单,就是将数据随机输出到下游算子的并发实例中; -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Android Lint 12 | 13 | 14 | 15 | 16 | Android 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 1.7 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /doc/sql.md: -------------------------------------------------------------------------------- 1 | ### Flink SQL 2 | 3 | Flink SQL是Flink内部最高级的API,使用者可以通过SQL语句执行流批任务,屏蔽了底层DataStream/DataSet的细节,从而降低了使用门槛。 4 | 那么一条Flink SQL语句究竟是如何转化为可执行的任务的呢?就让我们来深入的看一看吧。 5 | 6 | 当然,在此之前有些前置知识需要先介绍一下,这就是Apache Calcite和Blink Planner。 7 | Flink使用了通用的SQL解析与优化引擎Apache Calcite,Calcite在Flink中主要承担以下任务: 8 | * 解析:将SQL语句转化为抽象语法树(AST),即SqlNode树; 9 | * 验证:根据Catalog中的元数据进行语法检查; 10 | * 逻辑计划:根据AST和元数据构造出逻辑计划,即RelNode树; 11 | * 逻辑计划优化:按照预定义的优化规则RelOptRule优化逻辑计划。Calcite中的优化器RelOptPlanner有两种,一是基于规则优化(RBO)的HepPlanner, 12 | 二是基于代价优化(CBO)的VolcanoPlanner; 13 | * 物理计划:将优化的逻辑计划翻译成对应执行逻辑的物理计划; 14 | 15 | 在物理计划之后,还需要通过代码生成(code generation)将SQL转化为能够直接执行的DataStream/DataSet API程序。Flink Table/SQL体系中的Planner 16 | (即查询处理器)是沟通Flink与Calcite的桥梁,为Table/SQL API提供完整的解析、优化和执行环境。它根据流处理作业和批处理作业的不同,分别提供了StreamPlanner 17 | 和BatchPlanner两种实现,这两种Planner的底层共享了基类PlannerBase的很多源码,并最终负责将作业翻译成基于DataStream Transformation API的 18 | 执行逻辑(即将批处理视为流处理的特殊情况)。 19 | 20 | Planner(查询处理器)是Flink Table/SQL体系中沟通Flink与Calcite的桥梁,为Table/SQL API提供了完整的解析、优化和执行的环境,Blink Planner从 21 | 1.11版本开始成为默认的Planner并替换掉原有的Flink Planner。Blink Planner是真正的流批一体处理的实现,它会根据作业所属类型的不同,分别提供StreamPlanner 22 | 和BatchPlanner两种实现方式,这两种实现方式底层共用了PlannerBase的许多源代码,并最终翻译为基于DataStream Transformation API的执行逻辑(将批 23 | 处理视为了流处理的特殊情况)。 24 | 25 | 在Flink中通过TableEnvironment.explainSql()方法可以直接以文本形式获取到SQL语句的查询计划,包括:抽象语法树、优化的逻辑计划和物理执行计划三部分。 26 | 首先,SQL执行的入口方法是TableEnvironmentImpl.sqlQuery()方法,在这个方法中会调用Parser.parse()方法来对SQL进行解析,它会最终调用到CalciteParser.parse() 27 | 方法,也就是说它直接调用了Calcite的SQL解析器SqlParser进行解析,解析返回的结果是SqlNode类型。由于Flink的SQL方言与标准SQL相比还是有一定的差距, 28 | 所以在构造SqlParser的配置类SqlParser.Config时,传入了解析器工厂SqlParserImplFactory,从而实现Flink SQL专用的解析器。 -------------------------------------------------------------------------------- /doc/sourceandsink.md: -------------------------------------------------------------------------------- 1 | ### Source and Sink 2 | 3 | Flink是批流一体的实时计算框架,其既可以做流处理,也可以做批处理,但是不管是哪种处理方式,都要有数据来源(输入)和数据汇集(输出),前者叫做Source, 4 | 后者叫做Sink,Flink已经默认包含了最基本的Source和Sink(如,文件、Socket等)。另外,也还有一些常用的与其它第三方组件交互的Source和Sink,这些 5 | 叫做连接器,如与HDFS、Kafka、ElasticSearch等连接的连接器。 6 | 7 | SourceFunction是定义Flink Source所有实现的根接口,其中定义的run()方法用于源源不断的产生源数据,所以重写的时候一般都会写成循环,然后用标志位 8 | 控制是否结束,cancel()方法则是用来打断run()方法中的循环,终止产生数据。 9 | 10 | SourceFunction中还在内部嵌套定义了SourceContext接口,它表示这个Source对应的上下文,用于发射数据。其中起主要作用的是前三个方法: 11 | * collect():发射一个不带自定义时间戳的元素。如果程序的时间特征(TimeCharacteristic)是处理时间,则元素没有时间戳;如果是摄入时间(IngestionTime), 12 | 元素会附带系统时间;如果是事件时间(EventTime),那么初始时没有时间戳,但一旦要做与时间戳相关的操作(如,window)时,就必须用TimestampAssigner 13 | 给它设定一个; 14 | 15 | * collectWithTimestamp():发射一个带有自定义时间戳的元素。该方法对于时间特征为事件时间的程序是绝对必须的,如果为处理时间就会被直接忽略,如果 16 | 为摄入时间就会被系统时间覆盖; 17 | 18 | * emitWatermark():发射一个水印,仅对于事件时间有效。一个带有时间戳t的水印表示不会有任何t'<=t的事件再发生,如果真的发生,会被当做迟到事件忽略掉; 19 | 20 | * markAsTemporarilyIdle():用于通知系统当前的source会暂停发射数据一段时间,它只有在摄入时间和事件事件才会生效,允许下游算子在source空闲时提前 21 | 他们的水位从而提前触发计算(这个功能对于那些数据流比较稀疏的场景下还是比较有用); 22 | 23 | SourceFunction有一些其他实现,如:ParallelSourceFunction表示该Source可以按照设置的并行度并发执行;RichSourceFunction是继承自RichFunction, 24 | 表示该Source可以感知到运行时上下文(RuntimeContext,如Task、State及并行度的信息),以及可以自定义初始化和销毁逻辑(通过open/close方法)。RichParallelSourceFunction 25 | 则表示的是以上两者的结合。 26 | 27 | 如同SourceFunction是定义Flink Source所有实现的根接口,SinkFunction是定义Flink Sink所有实现的根接口。但是它的定义比SourceFunction要简单, 28 | 只有一个invoke()方法,对收集来的每条数据都会用它来进行处理。SinkFunction也有对应的上下文对象Context,可以从中获得当前处理时间、当前水印和时间 29 | 戳,而且它也有衍生出来的RichSinkFunction函数版本。Flink内部提供了一个最简单的实现DiscardingSink。顾名思义,它就是将所有汇集的数据全部丢弃。 30 | 31 | 我们在[Flink源码阅读6:两阶段提交](./2pc.md)中提到Flink中通过基于checkpoint提供的hook来实现的两阶段提交模式保证了Sink端的Exactly once,但这 32 | 种保证需要第三方写组件的支持,所以目前对于外部Exactly once的写支持只提供了两种Sink,分别是KafkaSink和HdfsSink,它们主要应用在实时数仓、topic拆 33 | 分、基于时间的分析处理等领域。 34 | -------------------------------------------------------------------------------- /doc/latency.md: -------------------------------------------------------------------------------- 1 | ### 全链路延迟测量 2 | 3 | Flink Job端到端延迟是一个重要的指标,用来衡量Flink任务的整体性能和响应延迟(低延迟)。一些低延时的处理场景,例如用户登录、下单规则检测、实时预测 4 | 等,需要一个可度量的Metric指标来实时观测、监控集群全链路时延情况。 5 | 6 | 我们可以设想,最简单的设计就是在每个记录的source上附加一个摄取时间(ingestion time)时间戳,方案实现简单,但是存在的一个明显问题是:如果某个用户 7 | 不需要使用这个延迟监控功能,那这种设计就带来了额外的开销(每个元素+每个元素上的System.currentTimeMilis()需要8个字节)。因此,Flink采用了另外一 8 | 种实现方式,即通过定期发送特殊事件(LatencyMarker)的方式来实现此功能,这类似于通过拓扑发送水印watermark。 9 | 10 | 这个特殊的事件(LatencyMarker)在source上可以配置发送间隔,并由任务Task进行转发。Sink最后接收到LatencyMarks后,将比较LatencyMarker的时间戳 11 | 与当前系统时间,以确定延迟。 12 | 13 | LatencyMarker不会增加作业的延迟,但是它与常规记录类似,可以被delay阻塞(例如出现背压时),因此LatencyMarker的延迟与StreamRecord延迟近似,实际 14 | 上Flink的上游算子除了会向下游算子传递StreamRecord常规记录外,还会传递WaterMark、StreamStatus、LatencyMarker等同样继承了StreamElement抽象 15 | 类的特殊数据类型。 16 | 17 | 上述建议期望所有的任务管理器TaskManager上的时钟是同步的,否则测量的延迟也包含TaskManager时钟之间的偏移,可以通过JobManager作为计时服务中心来 18 | 缓解这个问题,这样TM就可以通过定期查询JM的当前时间,来确定其时钟的偏移量,这个偏移量会包含TM和JM之间的网络延迟,但是已经能较好的测量时延。 19 | 20 | ![FLink LatencyMarker](../images/latency.jpg "FLink LatencyMarker") 21 | 22 | LatencyMarker按默认的配置时间间隔从源发出,这个时间间隔默认是0毫秒,即不触发(配置项在ExecutionConfig#latencyTrackingInterval,名称 23 | metrics.latency.interval),例如可以配置为2000毫秒触发一次LatencyMarker发送。 24 | 25 | LatencyMarker不能"多于"常规元素,这确保了测量的延迟接近于常规流元素的端到端延迟,常规操作符Operator(不包括那些参与迭代的Operator)如果不是Sink, 26 | 就会转发延迟标记LatencyMarker。 27 | 28 | 具有多个输出channel的Operator,随机选择一个channel通道,将LatencyMarker发送给它。这可以确保每个LatencyMarker标记在系统中只存在一次,并且重新 29 | 分区步骤不会导致传输的LatencyMarker数量激增。 30 | 31 | 在具体实现上,RecordWriterOutput#emitLatencyMarker()方法会被StreamSource、AbstractStreamOperator调用,分别实现source和中间operator的 32 | 延迟标记下发。 33 | 34 | 如果operator是Sink,它将维护每个已知source实例的最后512个LatencyMarker信息。每个已知operator的最小/最大/平均值/p50/p95/p99时延都在Sink的 35 | LatencyStats对象中,进行汇总(如果没有任何输出operator,就是sink)。 36 | 37 | 每个中间operator以及sink都会统计自己与source节点的链路延迟,延迟粒度可以细分到Task,用来排查哪台机器的Task时延偏高,可以用于对比分析和运维排查, 38 | 但是如果硬件时钟不正确,则时延测量将不准确。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flink源码阅读目录: 2 | 3 | * [Flink源码阅读1:任务提交](../master/doc/jobsubmit.md) 4 | 5 | * [Flink源码阅读2:数据传递](../master/doc/datadeliver.md) 6 | 7 | * [Flink源码阅读3:StreamOperator](../master/doc/streamoperator.md) 8 | 9 | * [Flink源码阅读4:StreamTask](../master/doc/streamtask.md) 10 | 11 | * [Flink源码阅读5:Java泛型类型擦除和Flink类型暗示](../master/doc/typeerasureandtypehint.md) 12 | 13 | * [Flink源码阅读6:两阶段提交](../master/doc/2pc.md) 14 | 15 | * [Flink源码阅读7:分布式快照](../master/doc/chandylamport.md) 16 | 17 | * [Flink源码阅读8:窗口触发器](../master/doc/triggerandevictor.md) 18 | 19 | * [Flink源码阅读9:流分区器](../master/doc/partitioner.md) 20 | 21 | * [Flink源码阅读10:定时器](../master/doc/timer.md) 22 | 23 | * [Flink源码阅读11:IO模型及Flink中的异步IO](../master/doc/nio.md) 24 | 25 | * [Flink源码阅读12:背压](../master/doc/backpressure.md) 26 | 27 | * [Flink源码阅读13:Source和Sink](../master/doc/sourceandsink.md) 28 | 29 | * [Flink源码阅读14:内存管理](../master/doc/memory.md) 30 | 31 | * [Flink源码阅读15:窗口Window](../master/doc/window.md) 32 | 33 | * [Flink源码阅读16:Flink数据通信](../master/doc/conn.md) 34 | 35 | * [Flink源码阅读17:FlinkKafkaConsumer](../master/doc/flinkkafkaconsumer.md) 36 | 37 | * [Flink源码阅读18:事件时间和水位线](../master/doc/eventtimeandwatermark.md) 38 | 39 | * [Flink源码阅读19:State存储](../master/doc/statestore.md) 40 | 41 | * [Flink源码阅读20:Flink SQL](../master/doc/sql.md) 42 | 43 | * [Flink源码阅读21:调度器](../master/doc/scheduler.md) 44 | 45 | * [Flink源码阅读22:类加载](../master/doc/loadclass.md) 46 | 47 | ### 扩展阅读 48 | 49 | * [Flink扩展阅读1:全链路延迟测量](../master/doc/latency.md) 50 | 51 | * [Flink扩展阅读2:分布式系统中的CAP和BASE理论](../master/doc/capandbase.md) 52 | 53 | * [Flink扩展阅读3:分布式共识算法Raft](../master/doc/raft.md) 54 | 55 | * [Flink扩展阅读4:数据去重](../master/doc/datadeduplication.md) 56 | 57 | * [Flink扩展阅读5:问题实战](../master/doc/thinking.md) 58 | 59 | * [Flink扩展阅读6:数据仓库](../master/doc/realtimedatawarehouse.md) 60 | 61 | * [Flink扩展阅读7:Java SPI](../master/doc/spi.md) -------------------------------------------------------------------------------- /docs/_build/latex/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx LaTeX output 2 | 3 | ALLDOCS = $(basename $(wildcard *.tex)) 4 | ALLPDF = $(addsuffix .pdf,$(ALLDOCS)) 5 | ALLDVI = $(addsuffix .dvi,$(ALLDOCS)) 6 | ALLXDV = 7 | ALLPS = $(addsuffix .ps,$(ALLDOCS)) 8 | ALLIMGS = $(wildcard *.png *.gif *.jpg *.jpeg) 9 | 10 | # Prefix for archive names 11 | ARCHIVEPREFIX = 12 | # Additional LaTeX options (passed via variables in latexmkrc/latexmkjarc file) 13 | export LATEXOPTS = 14 | # Additional latexmk options 15 | LATEXMKOPTS = 16 | # format: pdf or dvi (used only by archive targets) 17 | FMT = pdf 18 | 19 | LATEX = latexmk -dvi 20 | PDFLATEX = latexmk -pdf -dvi- -ps- 21 | 22 | 23 | %.png %.gif %.jpg %.jpeg: FORCE_MAKE 24 | extractbb '$@' 25 | 26 | %.dvi: %.tex FORCE_MAKE 27 | $(LATEX) $(LATEXMKOPTS) '$<' 28 | 29 | %.ps: %.dvi 30 | dvips '$<' 31 | 32 | %.pdf: %.tex FORCE_MAKE 33 | $(PDFLATEX) $(LATEXMKOPTS) '$<' 34 | 35 | all: $(ALLPDF) 36 | 37 | all-dvi: $(ALLDVI) 38 | 39 | all-ps: $(ALLPS) 40 | 41 | all-pdf: $(ALLPDF) 42 | 43 | zip: all-$(FMT) 44 | mkdir $(ARCHIVEPREFIX)docs-$(FMT) 45 | cp $(ALLPDF) $(ARCHIVEPREFIX)docs-$(FMT) 46 | zip -q -r -9 $(ARCHIVEPREFIX)docs-$(FMT).zip $(ARCHIVEPREFIX)docs-$(FMT) 47 | rm -r $(ARCHIVEPREFIX)docs-$(FMT) 48 | 49 | tar: all-$(FMT) 50 | mkdir $(ARCHIVEPREFIX)docs-$(FMT) 51 | cp $(ALLPDF) $(ARCHIVEPREFIX)docs-$(FMT) 52 | tar cf $(ARCHIVEPREFIX)docs-$(FMT).tar $(ARCHIVEPREFIX)docs-$(FMT) 53 | rm -r $(ARCHIVEPREFIX)docs-$(FMT) 54 | 55 | gz: tar 56 | gzip -9 < $(ARCHIVEPREFIX)docs-$(FMT).tar > $(ARCHIVEPREFIX)docs-$(FMT).tar.gz 57 | 58 | bz2: tar 59 | bzip2 -9 -k $(ARCHIVEPREFIX)docs-$(FMT).tar 60 | 61 | xz: tar 62 | xz -9 -k $(ARCHIVEPREFIX)docs-$(FMT).tar 63 | 64 | clean: 65 | rm -f *.log *.ind *.aux *.toc *.syn *.idx *.out *.ilg *.pla *.ps *.tar *.tar.gz *.tar.bz2 *.tar.xz $(ALLPDF) $(ALLDVI) $(ALLXDV) *.fls *.fdb_latexmk 66 | 67 | .PHONY: all all-pdf all-dvi all-ps clean zip tar gz bz2 xz 68 | .PHONY: FORCE_MAKE -------------------------------------------------------------------------------- /doc/scheduler.md: -------------------------------------------------------------------------------- 1 | ### 调度器 2 | 3 | 调度器是Flink作业执行的核心组件,它负责管理作业执行的所有相关过程,是作业执行、异常处理等的核心,它至少应该包含以下几个能力: 4 | * 作业生命周期的管理:如作业开始调度、挂起、取消等; 5 | * 作业执行资源的管理:如作业执行资源的申请、分配、释放等; 6 | * 作业状态的管理:如作业发布过程中的状态变化和作业异常时的Failover等; 7 | * 作业信息的管理:如对外提供作业的详细信息等; 8 | 9 | 目前,Flink中有两种调度器的实现,分别是DefaultScheduler和LegacyScheduler。其中,LegacyScheduler属于遗留的调度器,从Flink1.12开始,已经 10 | 将该调度器从代码中移除,因此对该调度器不再赘述。而DefaultScheduler使用SchedulingStrategy来实现调度。 11 | 12 | SchedulingStrategy是一个接口,其中定义了调度的行为,一共包含四种行为: 13 | * startScheduling():调度的入口,触发调度器的调度行为; 14 | * restartTasks():执行失败的Task的重启,一般都是Task执行异常导致的; 15 | * onExecutionStateChange():当Execution的状态发生改变时触发的行为; 16 | * onPartitionConsumable():当IntermediateResultPartition中的数据可以被消费时触发; 17 | 18 | 目前,Flink中实现了三种类型的调度,分别是:Eager调度、分阶段调度、分阶段Slot重用调度。Eager调度(EagerSchedulingStrategy)主要用于流式计算, 19 | 它会一次性申请作业运行所需要的所有资源,如果资源无法被满足,则调度失败。分阶段调度(LazyFromSourcesSchedulingStrategy)主要适用于批处理,它从 20 | Source Task开始进行分阶段调度,在申请资源时,只会申请本阶段所需要的所有资源。在上游Task执行完毕后开始执行下游Task,读取上游的结果数据,开始执行 21 | 本阶段的计算任务,在本阶段执行完成后再调度后一个阶段的Task,依次进行调度直到作业完成。分阶段Slot重用调度(PipelinedRegionSchedulingStrategy), 22 | 它也主要用于批处理作业,它与分阶段调度的不同之处在于它可以在资源不足的情况下执行作业,但需要保证本阶段作业的执行中没有Shuffle行为。 23 | 24 | 执行模式指定了程序在数据交换方面的执行方式,Flink中主要有两类执行模式,分别为:Pipelined模式和Batch模式,它们在实现上又细分成了 25 | 四类具体的执行模式: 26 | * Pipelined:以流水线方式执行作业,如果可能会出现数据交换的死锁,则将数据交换以Batch方式执行(当数据流被多个下游分支处理,且处理的结果再进行JOIN 27 | 时就可能出现数据交换死锁); 28 | * Pipelined_forced:与Pipelined模式类似,但不同之处在于即使可能会出现数据交换死锁,也不会切换为Batch方式; 29 | * Pipelined_With_Batch_Fallback:首先使用Pipelined启动作业,如果可能出现死锁,则使用Pipelined_Forced启动,当作业异常退出时,使用Batch 30 | 模式重新执行作业(目前未实现); 31 | * Batch:对于所有的Shuffle和Broadcast以Batch方式执行,仅对于本地数据交换使用Pipelined方式; 32 | * Batch_forced:与Batch模式类似,不同之处在于即使本地数据交换也使用Batch方式; 33 | 34 | 执行模式的不同也决定了数据交换行为的不同,Flink在ResultPartitionType中定义了四种类型的数据分区模式,这些数据分区模式与执行模式一起完成批流在数 35 | 据交换层面的统一,它们分别是: 36 | * BLOCKING:适用于批处理,它会等待数据完全处理完毕,然后交给下游进行处理,在上游处理完毕之前,不会与下游进行数据交换; 37 | * BLOCKING_PERSISTENT:类似于BLOCKING,但生命周期由用户指定; 38 | * PIPELINED:适用于流计算和批处理,数据处理结果只能被一个消费者消费一次,当数据被消费之后即自动销毁,它可以在运行中保留任意数量的数据,如内存无 39 | 法容纳时可以写入磁盘; 40 | * PIPELINED_BOUNDED:它带有一个有限大小的本地缓冲池; -------------------------------------------------------------------------------- /doc/capandbase.md: -------------------------------------------------------------------------------- 1 | ### CAP and BASE 2 | 3 | 相比于SQL领域Mysql的风靡来说,NoSQL(Not only SQL)领域的发展简直是各领风骚啊,其按照存储模式都可以分为键值、列族、文档和图等四种,例如比较 4 | 常见的键值对数据库如redis,列族数据库HBase,文档数据库MongoDB等。虽然这些非关系型的数据库在自己擅长的领域各展所长,但是由于在分布式系统上实 5 | 现ACID太难,所以它们几乎都没有提供ACID的事务保证,它们的共同基础是分布式领域的两个著名理论,即CAP和BASE。 6 | 7 | 还是先来回顾一下关系型数据库事务的原则ACID: 8 | * A(Atomicity):原子性,指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生; 9 | 10 | * C(Consistency):一致性,指事务前后数据的完整性必须保持一致,数据库的完整性约束没有被破坏; 11 | 12 | * I(Isolation):指多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离; 13 | 14 | * D(Durability):指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响; 15 | 16 | 再来看一下CAP理论,其内容是:在分布式系统中,不可能同时满足一致性(consistency)、可用性(availability)、分区容忍性性(partition tolerance) 17 | 三大特征,最多只能同时满足其中二者。其中: 18 | * 一致性:指对于客户端向分布式系统的读请求,要么读到最新的数据,要么失败; 19 | 20 | * 可用性:指对分布式系统的请求一定会得到非错误的响应,但不保证响应一定包含最新写入的数据; 21 | 22 | * 分区容忍性:指即使分布式系统内部节点之间的通信出了问题,系统仍然能正常运作; 23 | 24 | 由于在分布式系统中,网络延迟、丢包等现象几乎肯定会出现,因此CAP中的分区容忍性必须要保证,所以对于一致性和可用性就必须要有所牺牲。这个意思并不 25 | 是说分布式系统中一定就无法保证一致性或可用性了,而是说当分布式系统由于网络丢包或延迟或其它问题而产生网络分区时,此时需要对一致性和可用性作出取 26 | 舍。因为出现网络分区情况时,如果优先保证一致性,也就是所有分区都必须返回相同的结果,那么由于网络分区之间暂时无法进行数据同步,那么就必须对这期 27 | 间所有的请求返回错误或不返回,否则就会返回不一样的结果而违反一致性原则。同理,如果优先保证可用性,也就是部分节点可用,由于网络分区的存在,各个 28 | 分区间的数据必定出现不一致。所以CAP原则并不是说一定只能三选二,而是指在出现异常导致分区P的情况下,是优先保证A服务可用,还是优先保证C数据一致。 29 | 30 | 就我们比较熟悉的HBase数据库来说,它是选择了CP,也就是当出现网络分区时,优先保证一致性而不是可用性,这也很好理解,毕竟HBase更多的时候是作为一 31 | 个数据库而存在,而数据库最重要的就是数据一致性,不然这个数据库有什么用呢?所以它会在出现异常时,对异常的RegionServer进行WAL重放且在此过程中 32 | 无法访问。当然了,HBase 2.0+版本支持的Region Replica特性可以使其在出现网络分区时,优先保证可用性而不是一致性。 33 | 34 | 很明显,CAP理论有时候太过于狭窄和一刀切,实际上很多时候我们既想满足可用性需求,也可以忍受分布式系统暂时的不一致性,只要它能在经过一定的时间后 35 | 恢复到一致的状态就是可以接受的。另外,可用性要求所有的节点都能响应请求,有时侯这只是一种理想的状态,在真实的世界中由于各种不可预计的原因,是很 36 | 难满足这样苛刻的条件的。 37 | 38 | 于是,BASE理论出现了,它的含义是: 39 | * BA(basically available):基本可用,指分布式系统在出现故障时,允许损失部分可用性,以保证系统基础运转仍然正常; 40 | 41 | * S(soft state):软状态,指允许系统中的数据存在中间状态,并且该中间状态不影响系统的可用性; 42 | 43 | * E(eventually consistent):最终一致性,指系统中的所有副本在经过一段时间的同步之后,最终总能够达到一致的状态; 44 | 45 | BASE理论的约束比CAP更加松散且易于实现,对于NoSQL来说,它们全部都符合BASE理论,即舍弃强一致性而追求最终一致性。其实保证最终一致性的手段很多, 46 | 重量级的方案比如直接引入分布式事务,如2PC、3PC、TCC等,轻量级的方案可以在业务中自己实现,如重试+幂等性保证、状态机、重做日志等。 -------------------------------------------------------------------------------- /doc/eventtimeandwatermark.md: -------------------------------------------------------------------------------- 1 | ### EventTime and Watermark 2 | 3 | 我们知道,Flink使用WaterMark来对"迟到"的数据进行处理。"迟到"是只有EventTime事件时间处理时才会出现的概念,它是指由于网络延迟或其它原因,导致 4 | 进入系统处理的事件的顺序并不是事件真正发生的顺序,而是发生了改变。但是这种"迟到"是很难提前预知的,为了能够准确地表达事件时间的处理进度,就必须用 5 | 到水印。在Flink中,水印是一种特殊的元素,每个水印都携带有一个时间戳,当时间戳为T的水印出现时,表示事件时间t<=T的数据都已经到达,即水印后面应该 6 | 只能流入事件时间t>T的数据。所以,水印再结合窗口,可以在一定程度上保证数据处理的顺序性(窗口之间有序,窗口内部不一定严格有序)。 7 | 8 | Flink提供了统一的DataStream.assignTimestampsAndWatermarks()方法来提取事件时间并同时产生水印,这个方法接受的参数类型有AssignerWithPeriodicWatermarks 9 | 和AssignerWithPunctuatedWatermarks两种,分别对应周期性水印和打点水印。 10 | 11 | 周期性水印使用AssignerWithPeriodicWatermarks产生,默认周期为200ms,也能通过ExecutionConfig.setAutoWatermarkInterval()方法来指定新的 12 | 周期。正常的流程上来讲,我们需要先通过实现extractTimestamp()方法来抽取事件时间,然后实现getCurrentWatermark()方法产生水印,不过Flink已经 13 | 默认提供了3种内置的实现类,我们可以直接使用来抽取事件事件并产生水印: 14 | * AscendingTimestampExtractor:它产生的时间戳和水印必须是单调非递减的,我们通过覆写extractAscendingTimestamp()方法抽取时间戳。如果产 15 | 生了递减的时间戳,就要使用名为MonotonyViolationHandler的组件处理异常,有三种方式进行处理:IgnoringHandler直接忽略、FailingHandler抛出 16 | RuntimeException、LoggingHandler打印警告日志(默认行为)。一般来说单调递增的事件时间并符合现实中的情况,所以用得不是不多; 17 | 18 | * BoundedOutOfOrdernessTimestampExtractor:它产生的时间戳和水印允许一定的乱序,构造它时传入的参数maxOutOfOrderness就是允许乱序区间的 19 | 长度,而实际发射的水印为通过覆写extractTimestamp()方法提取出来的时间戳减去乱序区间,相当于让水印时间往前调一些。乱序区间的长度需要根据实际 20 | 情况谨慎设定,设定得太短会丢较多的数据,设定得太长会导致窗口触发延迟,导致实时性变弱; 21 | 22 | * IngestionTimeExtractor:它基于系统当前时钟生成时间戳和水印,其实就是IngestionTime摄入时间; 23 | 24 | 另一种打点水印比周期性水印的应用要少很多,Flink内也没有默认实现,一般适用于需要依赖于事件本身的某些属性决定是否发射水印的情况,使用它需要实现 25 | checkAndGetNextWatermark()方法来产生水印,并且产生的时机完全由用户控制。 26 | 27 | 不管是使用周期性水印,还是使用打点水印,都需要注意不能过于频繁。因为Watermark对象是会全部流向下游的,并在整个DAG图中流动,也会实打实地占用内存, 28 | 过多的水印会造成系统性能下降。另外,水印的生成要尽量早,一般建议在接入Source之后就产生,或者在Source经过简单的变换(如,map、filter等)之后产生。 29 | 30 | 此外,水印的乱序区间只能够保证一部分迟到数据不被丢弃,但是乱序区间往往不会太长(影响实时性),那么对于那些真正迟到了的数据该怎么办呢?Flink提供了 31 | WindowedStream.allowedLateness()方法来设定窗口允许的最大延迟。正常情况下,窗口触发计算完成之后就会被销毁,但是如果设定了允许延迟后,窗口会 32 | 等待allowedLateness的时长后再销毁。在此期间迟到数据仍然可以进入窗口中,并触发新的计算。当然,由于窗口非常耗费内存和CPU资源,所以allowedLateness 33 | 的值也要进行仔细斟酌。那么如果在允许延迟后到来的数据该怎么办呢?此时窗口已经被销毁了,肯定也无法触发窗口的计算了,这个时候就要介绍侧输出这个概念了。 34 | 侧输出(side output)是Flink的分流机制,迟到数据本身也可以当做特殊的流,我们通过调用WindowedStream.sideOutputLateData()方法将迟到数据发送到 35 | 指定OutputTag的侧输出流里去,再进行下一步处理(比如,存到外部存储或消息队列等)。 -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'flink源码阅读' 21 | copyright = '2019, zlzhang0122' 22 | author = 'zlzhang0122' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = 'v0.1' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | ] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # List of patterns, relative to source directory, that match files and 40 | # directories to ignore when looking for source files. 41 | # This pattern also affects html_static_path and html_extra_path. 42 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 43 | 44 | 45 | # -- Options for HTML output ------------------------------------------------- 46 | 47 | # The theme to use for HTML and HTML Help pages. See the documentation for 48 | # a list of builtin themes. 49 | # 50 | #html_theme = 'alabaster' 51 | html_theme = 'sphinx_rtd_theme' 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | html_static_path = ['_static'] 57 | 58 | master_doc = 'contents' -------------------------------------------------------------------------------- /doc/triggerandevictor.md: -------------------------------------------------------------------------------- 1 | ### 窗口触发器Trigger和剔除器Evictor 2 | 3 | Trigger用于决定何时触发窗口计算操作。 4 | 这个,是不是很奇怪?为何要触发窗口的计算,当时间到了窗口的结束时间时直接进行计算并输出不就行了吗,这样就可以直接由窗口的滑动时间来控制窗口触 5 | 发的频率了,为什么非得多此一举的弄出来一个Trigger?实际上,这个想法理论上可以,但是在实际的生产环境中是不行的。由于生产环境的复杂性,可能或 6 | 者说一定存在数据的延迟到达的情况,此时我们就必须通过设置一些参数来允许这些数据也能被处理。此时,窗口的结束时间就必须要加上这个允许的滞后时间, 7 | 窗口输出的间隔就被大大的拉大了。此时如果我们由想要结果能够尽快的被输出的话,那么就需要用到Trigger窗口触发器。 8 | 9 | Trigger定义了何时开始对窗口进行计算,每个窗口都有一个默认的Trigger,当然也可以为窗口指定一个自定义的Trigger。其中有5个方法,允许其对不同 10 | 的事件进行处理: 11 | * onElement():每个元素进入窗口都会被调用; 12 | 13 | * onProcessingTime():当使用触发上下文设置的处理时间定时器timer被触发时调用; 14 | 15 | * onEventTime():当使用触发上下文设置的事件时间定时器timer被触发时调用; 16 | 17 | * onMerge():当使用WindowAssigner将多个窗口合并为一个窗口时被调用,如会话窗口的合并; 18 | 19 | * clear():当窗口被清除时清理指定窗口的状态; 20 | 21 | 前三个方法被调用后会返回TriggerResult,它决定着接下来的处理行为: 22 | * CONTINUE:不做任何处理; 23 | 24 | * FIRE_AND_PURGE:触发计算之后执行purge操作; 25 | 26 | * FIRE:触发计算; 27 | 28 | * PURGE:清除所有window中的元素,并且该window被废弃; 29 | 30 | 为了简便使用,Flink中也定义了一些内置的触发器: 31 | * EventTimeTrigger:根据EventTime和WaterMark机制来判断是否触发计算; 32 | 33 | * ProcessingTimeTrigger:根据ProcessingTime判断是否触发计算; 34 | 35 | * CountTrigger:窗口中的元素超过预先设定的maxCount限制值时触发计算; 36 | 37 | * PurgingTrigger:它是一个trigger的包装类,如果被包装的trigger触发返回FIRE,则PurgingTrigger将返回修改为FIRE_AND_PURGE,其他的 38 | 返回值不做任何处理; 39 | 40 | * DeltaTrigger:根据传入的DeltaFunction和阈值决定是否触发,DeltaFunction的逻辑需要用户自己定义。该函数比较上一次触发计算的元素,和 41 | 目前到来的元素,比较结果为一个double类型阈值。如果阈值超过DeltaTrigger配置的阈值,则触发计算; 42 | 43 | 44 | Evictor同样是作用于窗口,它的作用是在Flink进行计算之前移除元素。Flink中每个window中的数据可以理解为以key-value对的形式存储在HeapListState 45 | 中的CopyOnWriteStateTable中。key为window对象本身,value为该window中的数据。window的合并运算是将window进行并集运算,同时合并value集合中 46 | 的数据,合并之后的window所含数据很可能存在2小时之前的数据,此时就以利用Evictor将一些不满足要求或异常的数据在窗口计算之前或计算之后从窗口中剔除 47 | 出去。其中主要有两个重要方法: 48 | * evictBefore():计算操作执行之前执行evict操作,被剔除的元素不会参与计算; 49 | 50 | * evictAfter():计算操作执行之后执行evict操作,被剔除的元素会参与计算; 51 | 52 | 一般操作window的类是WindowOperator,它没有evictor,一旦为窗口指定Evictor,这个窗口就会由EvictWindowOperator负责处理。其中,evictBefore() 53 | 和evictAfter()都是在emitWindowContents()方法中被调用,userFunction.process()执行了用户定义的计算逻辑,而before和after就是相对于这个方法 54 | 的调用来说的,在其之前执行的是evictBefore()方法,在其之后执行的是evictAfter()方法。 55 | 56 | Flink中,Evictor是一个接口,有三个实现: 57 | * CountEvictor:以元素计数为标准,决定元素是否会被移除,它会保留最后maxCount个元素,清除其余的元素,可以理解为保留最近多个个元素; 58 | 59 | * DeltaEvictor:通过计算DeltaFunction的值(用元素值和最后一个元素值进行delta),并将计算结果与threshold进行比较,如果结果不小于threshold, 60 | 则移除元素; 61 | 62 | * TimeEvictor:表示以时间为判断标准,决定元素是否会被移除,它会清除所有时间戳小于截止时间的数据,可以理解为保留最近多久的元素,截止时间即 63 | 为时间戳最大的元素的时间减去保留时间得到; -------------------------------------------------------------------------------- /doc/streamtask.md: -------------------------------------------------------------------------------- 1 | StreamTask 2 | ------------- 3 | 4 | 在[Flink源码阅读3:StreamOperator](./streamoperator.md)中已经说到StreamOperator上层是由StreamTask调用的,在Flink中将StreamTask称之为 5 | Invokable,它是一个抽象类。 6 | 7 | StreamTask的层级结构如下图: 8 | 9 | ![StreamTask](../images/streamtask.png "StreamTask") 10 | 11 | AbstractInvokable是一个抽象类,代表最顶层的Invokable,它是所有能够被TaskManager执行的task的抽象基类,流式任务和批处理任务都实现了该类,在这个 12 | 抽象类里面声明了最重要的方法invoke,可以认为其是task执行的起点,当执行一个task时,TaskManager会调用invoke方法,并且task所有的操作包括启动输入 13 | 输出的读写流等都发生在这个方法;此外还声明了与checkpoint相关的triggerCheckpointAsync/triggerCheckpointOnBarrier/abortCheckpointOnBarrier 14 | /notifyCheckpointCompleteAsync方法; 15 | 16 | StreamTask是AbstractInvokable的基本抽象实现类,实现了invoke、triggerCheckpoint等方法,另外声明了init、run等StreamTask生命周期的抽象方法,其 17 | 具体实现类有: 18 | * SourceStreamTask代表源(StreamSource)的Invokable; 19 | * OneInputStreamTask代表有一个输入流的Invokable; 20 | * TwoInputStreamTask代表有两个输入流的Invokable; 21 | 22 | 当然,还有其它关于批处理(BatchTask)、迭代流(StreamIterationHead/StreamIterationTail)等相关的Invokable。 23 | 24 | StreamTask是一个基础的Invokable,它会去调用一个或是多个StreamOperators,前提是这些StreamOperators组成了一个operator chain,被chain在一起的 25 | operator在一个线程内被同步的执行并且因此运行在同一个数据分区。在invoke方法内会调用这个operator的生命周期方法,invoke方法的执行可以分成以下步骤: 26 | * 基本的初始化工作:创建基础工具(如配置等)并且加载operators chain; 27 | * StreamTask的初始化,包括与checkpoint相关的例如Statebackend的创建,并最终调用operator的setup方法。调用task特定的init方法,当然具体实现 28 | 是在其实现类里,主要完成StreamTwoInputProcessor的初始化来进行读取数据相关的处理; 29 | * StreamOperator的初始化:进行operator的状态的初始化,包括调用initializeState、openAllOperators方法,initializeState会调用到StreamOperator 30 | 的initializeState方法,完成状态的初始化过程,openAllOperators方法会调用StreamOperator的open方法,调用与用户相关的初始化过程; 31 | * 执行过程:主要就是调用run()方法,具体的实现也是在对应的实现类里面,对于SourceStreamTask就是生产数据,对于OneInputStreamTask/TwoInputStreamTask 32 | 主要就是执行读取数据与之后的数据处理流程,这个在正常情况下会一直执行下去 33 | * 资源释放:任务正常结束或是异常停止都会执行的操作,包括closeAllOperators、disposeAllOperators、cleanup等,我们在userFunction里面涉及到的 34 | 资源链接一定要在close里面执行资源的释放 35 | 36 | 在StreamTask里面除了invoke方法的实现,还有checkpoint的相关实现方法triggerCheckpointAsync/triggerCheckpoint/triggerCheckpointOnBarrier 37 | /abortCheckpointOnBarrier/notifyCheckpointCompleteAsync等。 38 | 39 | 另外,StreamTask实现了AsyncExceptionHandler接口,这个接口内包含了一个handleAsyncException方法,该方法在StreamTask的实现是使当前的StreamTask 40 | 失败,在Flink里面,窗口定时触发或者是ProcessFunction的onTimer方法等相对于上面提到的run方法是一个异步过程,也就是说是由其它线程来执行的,如果这个异 41 | 步执行的线程抛出的异常我们希望主线程也能捕获到并进行相应的处理,那么AsyncExceptionHandler就是完成这个功能的。 42 | 43 | StreamTask中通过getCheckpointLock获取锁对象,定时调用与checkpoint执行都会使用这个lock对象完成同步功能,保证数据的一致性。比如在定时调用onTimer 44 | 方法内可能会涉及到对状态的操作,但是处理方法processElement里面也会对状态进行操作,状态对于这两个线程是共享资源,如果不使用锁进行同步可能会导致状态数据 45 | 的不一致。 -------------------------------------------------------------------------------- /docs/_build/latex/sphinxcyrillic.sty: -------------------------------------------------------------------------------- 1 | %% CYRILLIC IN NON-CYRILLIC DOCUMENTS (pdflatex only) 2 | % 3 | % refs: https://tex.stackexchange.com/q/460271/ 4 | \ProvidesPackage{sphinxcyrillic}% 5 | [2018/11/21 v2.0 support for Cyrillic in non-Cyrillic documents] 6 | \RequirePackage{kvoptions} 7 | \SetupKeyvalOptions{prefix=spx@cyropt@} % use \spx@cyropt@ prefix 8 | \DeclareBoolOption[false]{Xtwo} 9 | \DeclareBoolOption[false]{TtwoA} 10 | \DeclareDefaultOption{\@unknownoptionerror} 11 | \ProcessLocalKeyvalOptions* % ignore class options 12 | 13 | \ifspx@cyropt@Xtwo 14 | % original code by tex.sx user egreg: 15 | % https://tex.stackexchange.com/a/460325/ 16 | % 159 Cyrillic glyphs as available in X2 TeX 8bit font encoding 17 | % This assumes inputenc loaded with utf8 option, or LaTeX release 18 | % as recent as 2018/04/01 which does it automatically. 19 | \@tfor\next:=% 20 | {Ё}{Ђ}{Є}{Ѕ}{І}{Ј}{Љ}{Њ}{Ћ}{Ў}{Џ}{А}{Б}{В}{Г}{Д}{Е}{Ж}{З}{И}{Й}% 21 | {К}{Л}{М}{Н}{О}{П}{Р}{С}{Т}{У}{Ф}{Х}{Ц}{Ч}{Ш}{Щ}{Ъ}{Ы}{Ь}{Э}{Ю}% 22 | {Я}{а}{б}{в}{г}{д}{е}{ж}{з}{и}{й}{к}{л}{м}{н}{о}{п}{р}{с}{т}{у}% 23 | {ф}{х}{ц}{ч}{ш}{щ}{ъ}{ы}{ь}{э}{ю}{я}{ё}{ђ}{є}{ѕ}{і}{ј}{љ}{њ}{ћ}% 24 | {ў}{џ}{Ѣ}{ѣ}{Ѫ}{ѫ}{Ѵ}{ѵ}{Ґ}{ґ}{Ғ}{ғ}{Ҕ}{ҕ}{Җ}{җ}{Ҙ}{ҙ}{Қ}{қ}{Ҝ}{ҝ}% 25 | {Ҟ}{ҟ}{Ҡ}{ҡ}{Ң}{ң}{Ҥ}{ҥ}{Ҧ}{ҧ}{Ҩ}{ҩ}{Ҫ}{ҫ}{Ҭ}{ҭ}{Ү}{ү}{Ұ}{ұ}{Ҳ}{ҳ}% 26 | {Ҵ}{ҵ}{Ҷ}{ҷ}{Ҹ}{ҹ}{Һ}{һ}{Ҽ}{ҽ}{Ҿ}{ҿ}{Ӏ}{Ӄ}{ӄ}{Ӆ}{ӆ}{Ӈ}{ӈ}{Ӌ}{ӌ}% 27 | {Ӎ}{ӎ}{Ӕ}{ӕ}{Ә}{ә}{Ӡ}{ӡ}{Ө}{ө}\do 28 | {% 29 | \begingroup\def\IeC{\protect\DeclareTextSymbolDefault}% 30 | \protected@edef\@temp{\endgroup\next{X2}}\@temp 31 | }% 32 | \else 33 | \ifspx@cyropt@TtwoA 34 | % original code by tex.sx user jfbu: 35 | % https://tex.stackexchange.com/a/460305/ 36 | % 63*2+1=127 Cyrillic glyphs as found in T2A 8bit TeX font-encoding 37 | \@tfor\@tempa:=% 38 | {ae}{a}{b}{chrdsc}{chvcrs}{ch}{c}{dje}{dze}{dzhe}{d}{erev}{ery}{e}% 39 | {f}{ghcrs}{gup}{g}{hdsc}{hrdsn}{h}{ie}{ii}{ishrt}{i}{je}% 40 | {kbeak}{kdsc}{kvcrs}{k}{lje}{l}{m}{ndsc}{ng}{nje}{n}{otld}{o}{p}{r}% 41 | {schwa}{sdsc}{sftsn}{shch}{shha}{sh}{s}{tshe}{t}{ushrt}{u}{v}% 42 | {ya}{yhcrs}{yi}{yo}{yu}{y}{zdsc}{zhdsc}{zh}{z}\do 43 | {% 44 | \expandafter\DeclareTextSymbolDefault\expandafter 45 | {\csname cyr\@tempa\endcsname}{T2A}% 46 | \expandafter\uppercase\expandafter{\expandafter 47 | \def\expandafter\@tempa\expandafter{\@tempa}}% 48 | \expandafter\DeclareTextSymbolDefault\expandafter 49 | {\csname CYR\@tempa\endcsname}{T2A}% 50 | }% 51 | \DeclareTextSymbolDefault{\CYRpalochka}{T2A}% 52 | \fi\fi 53 | \endinput 54 | -------------------------------------------------------------------------------- /doc/typeerasureandtypehint.md: -------------------------------------------------------------------------------- 1 | ### Java泛型类型擦除和Flink类型暗示 2 | 3 | 我们知道,泛型在实现中一般有两种方式: 4 | * 代码共享:也就是对同一个原始类型下的泛型类型只生成同一份目标代码,此时就会出现类型擦除。比如Java中就采取了这种方式,它对List类型,不管 5 | 是List,还是List都只生成List.class这一份字节码; 6 | 7 | * 代码特化:也就是对每一个泛型类型都生成不同的目标代码,如果是这种实现就不会出现类型擦除的问题,但是很明显的会出现代码膨胀的问题,C++就是 8 | 采取的这种方式; 9 | 10 | Java泛型所使用的类型擦除虽然避免了代码膨胀的问题,节约了JVM的资源,但是却加重了编译器的工作量,使它不得不在运行期之前就进行类型检查,禁止模 11 | 糊的或是不合法的泛型使用方式。当然了,在使用extends和super来对将来指向容器的参数类型做限制时,Java的类型擦除也会根据限制的最左侧的界限来 12 | 进行擦除和替换。 13 | 14 | 在简介完Java中的泛型类型擦除后,再来分析Flink中的类型及类型暗示。如下图所示时Flink支持的各种类型: 15 | ![Flink类型](../images/type.png "Flink类型") 16 | 17 | 上图的各种类型的继承关系如下,它们是一一对应的关系: 18 | ![Flink类型继承关系](../images/typeinfo.png "Flink类型继承关系") 19 | 20 | TypeInformation类是Flink类型系统的公共基类,由于Flink中的类型信息会随着作业的提交而被传递到各个执行节点,所以它及它的所有子类都必须是可 21 | 序列化的。在处理数据类型和序列化时,Flink会按照自带的类型描述,一般类型提取和类型序列化框架的顺序进行处理。它会尝试推断出在分布式计算过程中 22 | 被交换和存储的数据类型的信息,实现方式是通过TypeExtractor类利用方法签名、子类继承信息等方式,自动提取和恢复类型信息,其提供了对map、flatmap、 23 | fold、mapPartition、AggregateFunction等多个方法获取其返回类型或累加类型的函数。 24 | 25 | 如果Flink成功推断出了类型信息,就能够非常便捷的完成很多事情,如: 26 | * 使用POJO类型时,通过引用它们的字段名称对数据进行分组、连接、聚合等操作(如我们常用到的dataStream.keyBy("id")就用到了类型推断),并提前 27 | 对类型进行检测,以判断拼写错误或类型是否兼容等; 28 | 29 | * 由于Flink所需要处理的数据需要进行网络传输和存储在内存中,数据类型信息越多,序列化和内存存储就越紧凑,压缩效率就越高; 30 | 31 | * 使得用户可以尽量少的关心序列化框架和类型的注册; 32 | 33 | 最常见的需要用户进行数据类型处理的场景有以下几个: 34 | * 注册子类型:如果函数只描述了父类型,但是执行时实际上却是子类型,此时一方面Flink需要识别这些子类型可能会造成性能下降,另一方面子类型的某些 35 | 独特的特性可能无法识别,此时就需要通过StreamExecutionEnvironment或者ExecutionEnvironment调用其registerType(clazz)方法来注册子类型; 36 | 37 | * 注册自定义序列化器:Flink会将自己不能处理的类型转交给Kryo序列化器,但是也并不是所有的类型都会被Kryo完美的处理,也就是说Flink并不能处理 38 | 所有的类型,此时就需要为出问题的数据类型注册额外的序列化类,具体做法是在StreamExecutionEnvironment或者ExecutionEnvironment调用 39 | getConfig().addDefaultKryoSerializer(clazz, serializer)来实现。当然啦,也可以强制使用Avro序列化器来代替Kryo,实现办法是通过 40 | StreamExecutionEnvironment或者ExecutionEnvironment调用其getConfig().enableForceAvro(); 41 | 42 | * 类型提示:尽管FLink提供的推断方法已经很多,但是由于上面介绍的的Java泛型类型擦除,自动提取类型的方式仍然并不总是有效,如果Flink尝试了 43 | 前述的各种办法仍然无法推断出泛型,用户就必须通过TypeHint来辅助进行推断,通过调用returns()方法声明返回类型。returns()方法接受三种类型的 44 | 参数:字符串描述的类名(如"String")、用于泛型类型的参数TypeHint、Java原生Class(例如String.class)等; 45 | 46 | * 手动创建TypeInformation:Flink提供的TypeInformation及其子类已经包含了很多常用类型的信息,但有时可能还是不够,所以手动创建有时是必须的, 47 | 如果是非泛型数据类型,直接通过传递Class对象到TypeInformation.of()即可,否则可能需要TypeHint匿名内部类来捕获泛型类型的信息然后通过 48 | TypeInformation.of(new TypeHint(){})来保存该信息,在运行时TypeExtractor即可通过getGenericSuperclass().getActualTypeArguments() 49 | 方法来获取保存的实际类型; 50 | 51 | 从类型信息到内存块,需要经历以下步骤,这样数据才能被内存进行有效的管理,这也是Flink类型信息产生作用的过程: 52 | ![Flink类型信息到内存块](../images/typeinfotomemory.png "Flink类型信息到内存块") -------------------------------------------------------------------------------- /docs/jobSubmit.rst: -------------------------------------------------------------------------------- 1 | 任务提交 2 | =============== 3 | Flink任务在被提交到Yarn上后会经过如下流程,具体如下: 4 | 5 | .. image:: image/flink-submit-to-yarn.png 6 | 7 | 8 | #. Client从客户端代码生成的StreamGraph提取出JobGraph; 9 | #. 上传JobGraph和对应的jar包; 10 | #. 启动App Master; 11 | #. 启动JobManager; 12 | #. 启动ResourceManager; 13 | #. JobManager向ResourceManager申请slots; 14 | #. ResourceManager向Yarn ResourceManager申请Container; 15 | #. 启动申请到的Container; 16 | #. 启动的Container作为TaskManager向ResourceManager注册; 17 | #. ResourceManger向TaskManager请求slot; 18 | #. TaskManager提供slot给JobManager,让其分配任务执行. 19 | 20 | 上面的流程主要包含Client,JobManager,ResourceManager,TaskManager共四个部分.接下来就对每个部分进行详细的分析. 21 | 22 | 生成StreamGraph 23 | ---------------- 24 | 在用户编写一个Flink任务之后是怎么样一步步转换成Flink的第一层抽象StreamGraph的呢?本节将会对此进行详细的介绍. 25 | 26 | StreamGraph生成的主要流程如下: 27 | 28 | * 用户对DataStream声明的每个操作都会将该操作对应的Transformation添加到Transformations列表:List 29 | * 用户程序中调用env.execute后(batch调用print方法类似),Flink将从List的Sink开始自底向上进行遍历,这也是为何Flink一定要写Sink的原因,没有Sink就无法生成StreamGraph. 30 | * 如果上游Transformation还没有进行处理,会先对上游的Transformation进行处理,处理即包装成一个StreamNode,再通过Edge建立上下游StreamNode的联系. 31 | * StreamGraphGenerator.generate()方法会最终生成一个完整的StreamGraph 32 | 33 | 其中,addSink的大致流程为:生成Operator -> 生成Transformation -> 加入Transformations中.具体操作如下: 34 | 35 | #. 对用户函数进行序列化,并转化成Operator 36 | #. clean进行闭包操作,如使用了哪些外部变量,会对所有字段进行遍历,并将它们的引用存储在闭包中 37 | #. 完成Operator到SinkTransformation的转换,由DataStream和Operator共同构建一个SinkTransformation 38 | #. 将SinkTransformation加入到transformations中 39 | 40 | 其实Transformation包含许多种类型,除了上面的SinkTransformation,还有SourceTransformation,OneInputTransformation,TwoInputTransformaion,PartitionTransformaion, 41 | SelectTransformation等等.具体的使用场景如下: 42 | 43 | * PartitionTransformation:如果用户想要对DataStream进行keyby操作,得到一个KeyedStream,即需要对数据重新分区.首先,用户需要设置根据什么key进行 44 | 分区,即KeySelector.然后在生成KeyedStream的过程中,会得到一个PartitionTransformation.在PartitionTransformation中会对这条记录通过key进行计算, 45 | 判断应该发往下游哪个节点,KeyGroup可以由maxParallism进行调整. 46 | * TwoInputTransformaion:指包含两个输入流,如inputStream1和inputStream2,加上这个Transformation的输出,及Operator即可得到一个完整的TwoInputTransformation. 47 | 48 | 以上过程得到了transformations的List,接下来就可以通过StreamGraphGenerator生成完整的StreamGraph. 49 | 生成StreamGraph时会遍历Transformation树,逐个对Transformation进行转化,具体的转化由transform()方法完成.transform最终都会调用transformXXX对 50 | 具体的StreamTransformation进行转换.transformPartition则是创建VirtualNode而不是StreamNode. 51 | 52 | 53 | Client 54 | ------------- 55 | Client模块的入口为CliFrontend,用于接收和处理各种命令与请求,如Run和Cancel代表运行和取消任务,CliFrontend在收到对应命令后,根据参数来具体执行命令. 56 | Run命令中必须执行Jar和Class,也可指定SavePoint目录来恢复任务. 57 | 58 | Client会根据Jar来提取出Plan,即DataFlow.然后在此Plan的基础上生成JobGraph.其主要操作是对StreamGraph进行优化,将能chain在一起的Operator进行Chain在一起的操作. 59 | 在得到JobGraph后就会提交JobGraph等内容,为任务的运行做准备. 60 | Operator能chain在一起的条件: 61 | #. 上下游Operator的并行度一致 62 | #. 下游节点的入度为1 63 | #. 上下游节点都在同一个SlotSharingGroup中(默认为default) 64 | #. 下游节点的chain策略是ALWAYS(可以与上下游链接,map/flatmap/filter等默认是ALWAYS) 65 | #. 上游节点的chain策略是ALWAYS或HEAD(只能与下游链接,不能与上游链接,source默认是HEAD) 66 | #. 两个Operator间的数据分区方式是fowward 67 | #. 用户没有禁用chain -------------------------------------------------------------------------------- /docs/_build/html/_sources/jobSubmit.rst.txt: -------------------------------------------------------------------------------- 1 | 任务提交 2 | =============== 3 | Flink任务在被提交到Yarn上后会经过如下流程,具体如下: 4 | 5 | .. image:: image/flink-submit-to-yarn.png 6 | 7 | 8 | #. Client从客户端代码生成的StreamGraph提取出JobGraph; 9 | #. 上传JobGraph和对应的jar包; 10 | #. 启动App Master; 11 | #. 启动JobManager; 12 | #. 启动ResourceManager; 13 | #. JobManager向ResourceManager申请slots; 14 | #. ResourceManager向Yarn ResourceManager申请Container; 15 | #. 启动申请到的Container; 16 | #. 启动的Container作为TaskManager向ResourceManager注册; 17 | #. ResourceManger向TaskManager请求slot; 18 | #. TaskManager提供slot给JobManager,让其分配任务执行. 19 | 20 | 上面的流程主要包含Client,JobManager,ResourceManager,TaskManager共四个部分.接下来就对每个部分进行详细的分析. 21 | 22 | 生成StreamGraph 23 | ---------------- 24 | 在用户编写一个Flink任务之后是怎么样一步步转换成Flink的第一层抽象StreamGraph的呢?本节将会对此进行详细的介绍. 25 | 26 | StreamGraph生成的主要流程如下: 27 | 28 | * 用户对DataStream声明的每个操作都会将该操作对应的Transformation添加到Transformations列表:List 29 | * 用户程序中调用env.execute后(batch调用print方法类似),Flink将从List的Sink开始自底向上进行遍历,这也是为何Flink一定要写Sink的原因,没有Sink就无法生成StreamGraph. 30 | * 如果上游Transformation还没有进行处理,会先对上游的Transformation进行处理,处理即包装成一个StreamNode,再通过Edge建立上下游StreamNode的联系. 31 | * StreamGraphGenerator.generate()方法会最终生成一个完整的StreamGraph 32 | 33 | 其中,addSink的大致流程为:生成Operator -> 生成Transformation -> 加入Transformations中.具体操作如下: 34 | 35 | #. 对用户函数进行序列化,并转化成Operator 36 | #. clean进行闭包操作,如使用了哪些外部变量,会对所有字段进行遍历,并将它们的引用存储在闭包中 37 | #. 完成Operator到SinkTransformation的转换,由DataStream和Operator共同构建一个SinkTransformation 38 | #. 将SinkTransformation加入到transformations中 39 | 40 | 其实Transformation包含许多种类型,除了上面的SinkTransformation,还有SourceTransformation,OneInputTransformation,TwoInputTransformaion,PartitionTransformaion, 41 | SelectTransformation等等.具体的使用场景如下: 42 | 43 | * PartitionTransformation:如果用户想要对DataStream进行keyby操作,得到一个KeyedStream,即需要对数据重新分区.首先,用户需要设置根据什么key进行 44 | 分区,即KeySelector.然后在生成KeyedStream的过程中,会得到一个PartitionTransformation.在PartitionTransformation中会对这条记录通过key进行计算, 45 | 判断应该发往下游哪个节点,KeyGroup可以由maxParallism进行调整. 46 | * TwoInputTransformaion:指包含两个输入流,如inputStream1和inputStream2,加上这个Transformation的输出,及Operator即可得到一个完整的TwoInputTransformation. 47 | 48 | 以上过程得到了transformations的List,接下来就可以通过StreamGraphGenerator生成完整的StreamGraph. 49 | 生成StreamGraph时会遍历Transformation树,逐个对Transformation进行转化,具体的转化由transform()方法完成.transform最终都会调用transformXXX对 50 | 具体的StreamTransformation进行转换.transformPartition则是创建VirtualNode而不是StreamNode. 51 | 52 | 53 | Client 54 | ------------- 55 | Client模块的入口为CliFrontend,用于接收和处理各种命令与请求,如Run和Cancel代表运行和取消任务,CliFrontend在收到对应命令后,根据参数来具体执行命令. 56 | Run命令中必须执行Jar和Class,也可指定SavePoint目录来恢复任务. 57 | 58 | Client会根据Jar来提取出Plan,即DataFlow.然后在此Plan的基础上生成JobGraph.其主要操作是对StreamGraph进行优化,将能chain在一起的Operator进行Chain在一起的操作. 59 | 在得到JobGraph后就会提交JobGraph等内容,为任务的运行做准备. 60 | Operator能chain在一起的条件: 61 | #. 上下游Operator的并行度一致 62 | #. 下游节点的入度为1 63 | #. 上下游节点都在同一个SlotSharingGroup中(默认为default) 64 | #. 下游节点的chain策略是ALWAYS(可以与上下游链接,map/flatmap/filter等默认是ALWAYS) 65 | #. 上游节点的chain策略是ALWAYS或HEAD(只能与下游链接,不能与上游链接,source默认是HEAD) 66 | #. 两个Operator间的数据分区方式是fowward 67 | #. 用户没有禁用chain -------------------------------------------------------------------------------- /doc/operatorchain.md: -------------------------------------------------------------------------------- 1 | ### Operator Chain 2 | 3 | Operator Chain是指将满足一定条件的Operator链在一起,放在同一个Task(也就是线程)中执行,它是Flink任务优化的一种方式。由于所有chain在一起的sub-task 4 | 都会在同一个线程(也就是TM的slot)中运行,所以在同一个Task里面的Operator的数据传输就从网络传输变成了函数调用,这大大的减少了数据传输的过程,减少了 5 | 不必要的数据交换、序列化反序列化的开销以及线程的上下文切换,提升了作业的执行效率。常见的chain,如source->map->filter->sink,这样的任务可以链在一 6 | 起,其是在优化逻辑执行计划的阶段加入的,也就是发生在由StreamGraph生成JobGraph的过程中。那么既然是要满足一定的条件才能chain在一起,那么究竟要满足 7 | 什么样的条件呢,以及chain在一起后如何执行呢? 8 | 9 | 前面在分析任务提交时其实已经分析过chain在一起需要满足的条件了,为了加深印象,我们再来分析一下。 10 | 我们知道,在Flink中有四种图,分别是StreamGraph、JobGraph、ExecutionGraph和物理执行图。其中,前两种是在客户端生成,而ExecutionGraph是在JobMaster中 11 | 生成,最后一种物理执行图是一个虚拟的图,它并不存在相对应的数据结构,它只是运行在每一个TaskManager的一种抽象。 12 | 13 | 由于Flink当前对于流处理的程序是不作优化的,所以我们在Flink程序中通过System.out.println(env.getExecutionPlan())打印出来的Flink执行计划其实就是 14 | StreamGraph。而我们在Flink Web UI中看到的图就是JobGraph。JobGraph相对于StreamGraph,其实就是进行了优化,优化就是将能够chain在一起的Operator 15 | chain在一起,这个chain的过程在客户端生成JobGraph的过程中完成。具体的实现代码在StreamingJobGraphGenerator中,其核心方法是createJobGraph(),该方 16 | 法会首先计算出StreamGraph中各个节点的hashcode作为唯一标识,并创建一个空的Map结构保存需要被chain在一起的算子的hashcode。然后调用setChaining()方法, 17 | 这个方法的实现很简单,就是逐个遍历StreamGraph中的source节点,并调用createChain()方法。createChain()方法是创建operator chain的核心方法,其中定义 18 | 了三个List结构: 19 | * transitiveOutEdges:当前算子在JobGraph中的出边列表,同时它也是createChain()方法最终返回的值; 20 | * chainableOutputs:当前能够chain在一起的StreamGraph边列表; 21 | * nonChainableOutputs:当前不能chain在一起的StreamGraph边列表; 22 | 23 | 然后,就会从source节点开始遍历StreamGraph中当前节点的所有出边,调用isChainable()方法来判断是否可以被chain在一起,如果可以则被放入chainableOutputs 24 | 列表,否则就放入nonChainableOutputs列表。对于chainableOutputs中的边,会以这边边的直接下游作为起点,继续递归调用createChain()方法延展算子链。而对于 25 | nonChainableOutputs中的边,由于当前算子链已经到达尽头,所以就会以这些"断点"作为起点,继续递归调用createChain()方法试图创建新的算子链。这也就是说,逻辑 26 | 计划中整个创建算子链的过程是递归的,实际返回时是从sink端开始返回的。随后判断当前节点是否是算子链的起始节点,如果是则调用createJobVertex()方法来为算子链创 27 | 建一个JobVertex(也就是JobGraph中的起点),最终形成JobGraph。需要注意的是,还需要将各个节点的算子链数据写入各自的StreamConfig中,算子链的起始节点还需要 28 | 额外保存transitiveOutEdges。 29 | 30 | isChainable()方法中描述了上下游算子chain在一起的条件,包括: 31 | * 上下游算子实例处于同一个SlotSharingGroup中; 32 | * 下游算子的链接策略为ALWAYS; 33 | * 上游算子的链接策略为HEAD或ALWAYS; 34 | * 两个算子之间的流分区逻辑是ForwardPartitioner; 35 | * 两个算子间的shuffle方式不是批处理模式; 36 | * 上下游算子实例的并行度相同; 37 | * 没有禁用算子链; 38 | 39 | 所有的StreamTask都会创建OperatorChain,如果一个算子无法进入算子链,也会形成一个只有headOperator的单个算子的OperatorChain。 40 | 41 | 在OperatorChain的构造方法中,首先会遍历算子链整体的所有出边,调用createStreamOutput()方法创建对应的下游输出RecordWriterOutput。然后调用 42 | createOutputCollector()方法创建物理的算子链,并返回chainEntryPoint(流入算子链的起始Output),这个方法从前面写入的StreamConfig中分别取出 43 | 出边和链接边的数据,并创建各自的output,出边的output就是将数据发往算子链之外下游的RecordWriterOutput,而链接边的输出则需要依靠createChainedOperator() 44 | 方法。createChainedOperator()方法递归调用了createOutputCollector()方法,不断延伸output来产生chainedOperator(即算子链中除了headOperator 45 | 之外的算子),并逆序返回(所以allOperators数组中的算子顺序为倒序)。chainedOperator产生后,将它们通过ChainingOutput连接起来,最终形成如下图 46 | 的结构: 47 | ![OperatorChain](../images/operatorchain.png "OperatorChain") 48 | 49 | ChainingOutput.collect()方法输出数据流的方式非常简单,它通过调用链接算子的processElement()方法,直接将数据推给下游进行处理。这也就是说, 50 | OperatorChain完全可以视作是一个由headOperator和streamOutputs组成的单个算子,其内部的chainedOperator和ChainingOutput都被黑盒所遮蔽,同 51 | 时也没有引入任何overhead。 52 | -------------------------------------------------------------------------------- /doc/streamoperator.md: -------------------------------------------------------------------------------- 1 | StreamOperator 2 | ------------- 3 | 4 | StreamOperator是流式operators的基础接口,是任务执行过程中的实际处理类,其上层由StreamTask调用,下层调用用户所实现的具体方法,它的 5 | 实现类在创建算子处理数据时是实现OneInputStreamOperator或TwoInputStreamOperator接口中的一种,分别表示处理一个输入、两个输入的Operator, 6 | 在这两个接口中包含了processElement/processWatermark/processLatencyMarker方法。 7 | 8 | OneInputStreamOperator实现类StreamMap、WindowOperator、KeyedProcessOperator等单流处理Operator,TwoInputStreamOperator实现 9 | 类CoStreamMap、KeyedCoProcessOperator、IntervalJoinOperator等多流处理Operator。StreamSource表示的source端的operator,既没有 10 | 实现OneInputStreamOperator接口也没有实现TwoInputStreamOperator接口,因为其就是流处理的源头,不需要接受输入。 11 | 12 | AbstractStreamOperator是StreamOperator的基础抽象实现类,所有的operator都必须继承该抽象类,它为生命周期和属性方法提供了默认的实现。 13 | AbstractUdfStreamOperator是继承AbstractStreamOperator的抽象实现类,其内部包含了userFunction,在Task的生命周期都会调用userFunction中 14 | 对应的方法。 15 | 16 | StreamOperator层级结构如下图: 17 | 18 | ![StreamOperator](../images/streamoperator.png "StreamOperator") 19 | 20 | StreamOperator继承的接口有: 21 | * CheckpointListener接口,其中的notifyCheckpointComplete方法表示checkpoint完成后的回调函数; 22 | * KeyContext接口,当前key的切换,用于KeyedStream中state的key的设置; 23 | * Disposable接口,其中的dispose方法主要用于对象销毁和资源释放 24 | * Serializable序列化接口 25 | 26 | 列举一些常见的StreamOperator: 27 | * env.addSource对应StreamSource; 28 | * dataStream.map对应StreamMap; 29 | * dataStream.window对应WindowOperator; 30 | * dataStream.addSink对应StreamSink; 31 | * dataStream.keyBy(...).process对应KeyedProcessOperator; 32 | 33 | 34 | AbstractStreamOperator和AbstractUdfStreamOperator 35 | ------------- 36 | 37 | 前面提到,AbstractStreamOperator是StreamOperator的基础抽象实现类,而AbstractUdfStreamOperator则是面向userFunction调用,接下来就 38 | 具体分析一下。它们用于初始化或者资源的释放等操作,其中大部分方法都是被StreamTask触发调用,从invoke方法作为入口分析: 39 | 40 | 1. initializeState状态初始化,会调用到StreamOperator的initializeState方法,初始化operatorStateBackend和keyedStateBackend状态后端, 41 | 定时器恢复初始化,对于keyedState来说会自动初始化恢复,但是operatorState则需要手动初始化恢复,所以在其继承的AbstractUdfStreamOperator会 42 | 调用userFunction的initializedState方法,前提条件是该userFunction必须实现CheckpointedFunction接口; 43 | 44 | 2. open初始化方法,在AbstractStreamOperator中是一个空实现,通常可以在userFunction重写open方法完成一些用户初始化工作; 45 | 46 | 3. run方法(最新版本中是mailboxProcessor.runMailboxLoop()方法),如果任务正常则一直会执行这个方法,根据收到的的不同数据类型调用AbstractStreamOperator的不同方法: 47 | 48 | * 如果是watermark,会调用其processWatermark方法,做一些定时触发的判断与调用; 49 | 50 | * 如果是LatencyMarker,表示的是一个延时标记,用于统计数据从source到下游operator的耗时,会调用processLatencyMarker方法,上报Histogram 51 | 类型的metric,默认关闭; 52 | 53 | * 如果是StreamRecord,也就是需要处理的业务数据,首先会调用setKeyContextElement方法,用于切换KeyedStream类型的statebackend的当前key, 54 | 然后调用processElement具体的数据处理流程; 55 | 56 | * 如果是CheckpointBarrier,表示的是需要进行checkpoint,首先会调用prepareSnapshotPreBarrier方法。在AbstractStreamOperator中是一个 57 | 空的实现,然后调用snapshotState方法。在AbstractUdfStreamOperator中会调用userFunction的snapshotState方法,前提是该userFunction必须 58 | 实现CheckpointedFunction接口; 59 | 60 | 4. close方法,任务正常结束调用的方法,在AbstractStreamOperator中是一个空的实现,通常可以在userFunction中重写close方法完成一些资源的 61 | 释放; 62 | 63 | 5. dispose方法,任务正常结束或异常结束调用的方法,异常结束时会调用close方法,正常结束不会重复调用close方法,在其中完成一些状态最终资源的 64 | 释放; 65 | 66 | 其它方法: 67 | 68 | 6. setup方法,初始化做一些参数的配置; 69 | 70 | 7. notifyCheckpointComplete方法,在checkpoint完成时调用的方法,面向用户实现的userFunction需要实现CheckpointListener接口. 71 | -------------------------------------------------------------------------------- /docs/_build/latex/sphinxhowto.cls: -------------------------------------------------------------------------------- 1 | % 2 | % sphinxhowto.cls for Sphinx (http://sphinx-doc.org/) 3 | % 4 | 5 | \NeedsTeXFormat{LaTeX2e}[1995/12/01] 6 | \ProvidesClass{sphinxhowto}[2018/12/23 v2.0 Document class (Sphinx howto)] 7 | 8 | % 'oneside' option overriding the 'twoside' default 9 | \newif\if@oneside 10 | \DeclareOption{oneside}{\@onesidetrue} 11 | % Pass remaining document options to the parent class. 12 | \DeclareOption*{\PassOptionsToClass{\CurrentOption}{\sphinxdocclass}} 13 | \ProcessOptions\relax 14 | 15 | % Default to two-side document 16 | \if@oneside 17 | % nothing to do (oneside is the default) 18 | \else 19 | \PassOptionsToClass{twoside}{\sphinxdocclass} 20 | \fi 21 | 22 | \LoadClass{\sphinxdocclass} 23 | 24 | % Set some sane defaults for section numbering depth and TOC depth. You can 25 | % reset these counters in your preamble. 26 | % 27 | \setcounter{secnumdepth}{2} 28 | \setcounter{tocdepth}{2}% i.e. section and subsection 29 | 30 | % Change the title page to look a bit better, and fit in with the fncychap 31 | % ``Bjarne'' style a bit better. 32 | % 33 | \newcommand{\sphinxmaketitle}{% 34 | \noindent\rule{\textwidth}{1pt}\par 35 | \begingroup % for PDF information dictionary 36 | \def\endgraf{ }\def\and{\& }% 37 | \pdfstringdefDisableCommands{\def\\{, }}% overwrite hyperref setup 38 | \hypersetup{pdfauthor={\@author}, pdftitle={\@title}}% 39 | \endgroup 40 | \begin{flushright} 41 | \sphinxlogo 42 | \py@HeaderFamily 43 | {\Huge \@title }\par 44 | {\itshape\large \py@release \releaseinfo}\par 45 | \vspace{25pt} 46 | {\Large 47 | \begin{tabular}[t]{c} 48 | \@author 49 | \end{tabular}\kern-\tabcolsep}\par 50 | \vspace{25pt} 51 | \@date \par 52 | \py@authoraddress \par 53 | \end{flushright} 54 | \@thanks 55 | \setcounter{footnote}{0} 56 | \let\thanks\relax\let\maketitle\relax 57 | %\gdef\@thanks{}\gdef\@author{}\gdef\@title{} 58 | } 59 | 60 | \newcommand{\sphinxtableofcontents}{% 61 | \begingroup 62 | \parskip \z@skip 63 | \sphinxtableofcontentshook 64 | \tableofcontents 65 | \endgroup 66 | \noindent\rule{\textwidth}{1pt}\par 67 | \vspace{12pt}% 68 | } 69 | \newcommand\sphinxtableofcontentshook{} 70 | \pagenumbering{arabic} 71 | 72 | % Fix the bibliography environment to add an entry to the Table of 73 | % Contents. 74 | % For an article document class this environment is a section, 75 | % so no page break before it. 76 | % 77 | \newenvironment{sphinxthebibliography}[1]{% 78 | % \phantomsection % not needed here since TeXLive 2010's hyperref 79 | \begin{thebibliography}{#1}% 80 | \addcontentsline{toc}{section}{\ifdefined\refname\refname\else\ifdefined\bibname\bibname\fi\fi}}{\end{thebibliography}} 81 | 82 | 83 | % Same for the indices. 84 | % The memoir class already does this, so we don't duplicate it in that case. 85 | % 86 | \@ifclassloaded{memoir} 87 | {\newenvironment{sphinxtheindex}{\begin{theindex}}{\end{theindex}}} 88 | {\newenvironment{sphinxtheindex}{% 89 | \phantomsection % needed because no chapter, section, ... is created by theindex 90 | \begin{theindex}% 91 | \addcontentsline{toc}{section}{\indexname}}{\end{theindex}}} 92 | -------------------------------------------------------------------------------- /doc/memory.md: -------------------------------------------------------------------------------- 1 | ### 内存管理 2 | 3 | TaskManager作为Master/Slave架构中的Slave,为Flink任务的执行提供了所需的环境和资源,其非常重要且极为复杂,因此Flink的内存管理也主要是指TaskManager的内存管理。 4 | 5 | 先来看一下Flink中TaskManager的内存布局: 6 | ![TaskManager内存布局](../images/memorymanage.png "TaskManager内存布局") 7 | 8 | Flink为了减少对象存储开销,会先将对象进行序列化后再进行存储,序列化存储的最小单位是MemorySegment,底层的实现是数组,大小由taskmanager.memory.segment-size 9 | 配置项指定,默认32KB。下面分别进行介绍: 10 | * 网络缓存(Network Buffer):用于进行网络传输及网络相关的操作(如shuffle、broadcast等)的内存块,由MemorySegment组成,自Flink 1.5版本开始,网络缓存固定分 11 | 配在堆外,这样可以充分利用零拷贝等技术; 12 | 13 | * 托管内存:用于Flink内部所有算子逻辑的内存分配,及中间数据的存储,同样是由MemorySegment组成,通过Flink的MemoryManager组件进行管理。默认堆内分配,如果开启 14 | 堆外内存分配的开关,也可以在堆外、堆内同时进行分配。相比于Spark,它没有对存储内存和执行内存进行区分,所以也更加的灵活; 15 | 16 | * 空闲内存:实际上用于存储用户代码和数据结构,固定分配在堆内,一般可以认为堆内内存减去托管内存后剩下的就是空闲内存; 17 | 18 | YARN部署的per job集群的启动调用的是YarnClusterDescriptor.deployJobCluster()方法,其中的ClusterSpecification对象持有该集群的3个基本参数:JobManager内存 19 | 大小、TaskManager内存大小、每个TaskManager的slot数量,deployInternal()方法在其中调用YarnClusterDescriptor.validateClusterResources()方法对资源进行校 20 | 验。clusterSpecification.getMasterMemoryMB()返回的是JobManager的内存,默认是768MB,可以通过参数jobmanager.heap.size设置。clusterSpecification.getTaskManagerMemoryMB() 21 | 返回的是TaskManager的内存,默认是1024MB,可以通过参数taskmanager.heap.size设置。 22 | 23 | 以上是Flink1.10版本之前的内存管理方案,其实际上的配置比较复杂,有很多隐藏的细节需要注意,并且对于批处理作业和流处理作业分别有一套不同的配置方法,因此社区一直在探究 24 | 并提出了一套新的统一的内存模型和配置,并在Flink 1.10版本中进行了发布。下面就让我们来分析下吧,先来看下新的内存布局: 25 | ![新版TaskManager内存布局](../images/memorynew.png "新版TaskManager内存布局") 26 | 27 | 由上图可以看出,与老版本的内存模型还是有很大的区别的,下面分别进行介绍: 28 | * Flink总内存:TM所占用的所有与Flink相关的内存,包括位于堆内和堆外的Flink框架内存、位于堆外的托管内存、位于堆外的网络缓存、位于堆内和堆外的任务内存,由参数 29 | taskmanager.memory.flink.size设置; 30 | 31 | * Flink框架内存:位于堆内和堆外,它是Flink运行时占用的内存,一般不需要调整,由参数taskmanager.memory.framework.heap.size控制堆内部分的大小,参数 32 | taskmanager.memory.framework.off-heap.size控制堆外部分的大小,它们的默认值都是128MB; 33 | 34 | * 托管内存:仅位于堆外,由MemoryManager进行管理,主要用于缓存中间数据和RocksDB状态后端的数据存储,既可以通过参数taskmanager.memory.managed.fraction 35 | 控制其占Flink总内存的比例来控制其大小(默认0.4),也可以直接通过参数taskmanager.memory.managed.size直接指定其大小; 36 | 37 | * 网络缓存:仅位于堆外,主要用于TM之间及与外部组件的数据交换,可以通过参数taskmanager.memory.network.fraction控制其占Flink总内存的大小比例,还可以通过参数taskmanager.memory.network.min 38 | 和taskmanager.memory.network.max控制其大小上下限,以免分配得过多或过少; 39 | 40 | * 任务内存:位于堆外和堆外,被用户代码和自定义数据结构所使用,堆内部分通过参数taskmanager.memory.task.heap.size设置,堆外部分通过参数taskmanager.memory.task.off-heap.size设置; 41 | 42 | * TaskManager总内存:它是Flink总内存、JVM元空间与JVM额外内存大小之和,也就是容器本身的内存大小,由参数taskmanager.memory.process.size设置; 43 | 44 | * JVM元空间:也就是JDK1.8以前的HotSpot JVM的方法区,或者叫做永久代,由参数taskmanager.memory.jvm-metaspace.size设置; 45 | 46 | * JVM额外开销:预留的其他本地内存,主要用于线程栈、代码缓存等,避免出现异常; 47 | 48 | 相比于老版本的内存管理,新版使用位于堆外的托管内存来管理RocksDB状态后端,并使用MemoryManager进行分配管理,有效的避免了State过大引起的频繁GC影响吞吐的问题,并且 49 | 还解决了托管内存只覆盖DataSet API,而DataStream API则主要使用JVM的堆内存,两者的管理方式不一致导致流式场景下需要更多的调优参数且内存消耗更难把控的问题。 50 | 51 | 虽然经过了Flink 1.10版本的优化,看上去还是有一大堆参数需要设置啊,完全记不住怎么办?没关系,其实要设置的很少。一般来说,如果部署方式是Standalone,就需要通过参数 52 | taskmanager.memory.flink.size指定Flink的总内存,如果是k8s或是yarn、Mesos模式部署,就需要通过参数taskmanager.memory.process.size指定TM进程的总内存。如果 53 | 需要对参数进行调优,可以考虑先通过参数taskmanager.memory.network.fraction指定网络缓存的占比大小,然后通过参数taskmanager.memory.managed.fraction指定托管 54 | 内存占比大小,其余的参数都可以不设置。否则,硬性设置全部的参数容易出现考虑不周引发配额被抢占引起运行失败等异常情况! 55 | -------------------------------------------------------------------------------- /doc/window.md: -------------------------------------------------------------------------------- 1 | ### 窗口Window 2 | 3 | Window可以理解为Flink中流数据的一种分组方式,它其中只定义了一个方法maxTimestamp(),表示该window时间跨度所能包含的最大时间点(用时间戳表示)。 4 | Window类有两个子类,分别是GlobalWindow和TimeWindow。前者是全局窗口,该窗口通过单例模式保证其只存在一个实例。而后者则是定义了明确起止时间的 5 | 时间窗口,它其中还定义了一些Window的计算方法,比如判断是否有交集intersects()方法,求并集的cover()等方法。 6 | 7 | 如果对一个流进行window操作,流中的元素会以它们的key(由keyBy函数指定)和它们所属的window进行分组,位于相同key和相同窗口的一组元素称之为窗格。 8 | 在Flink中,window和window中的数据以key-value对的形式存放(形成windowState,它以HeapListState的方式储存,在WindowOperator中定义)。 9 | 每次Flink接收到一个元素,都会通过一个特定的方法获取到该元素应该属于的window集合(也就是assignWindows方法),并将该元素加入到状态表中。WindowAssigner 10 | 的主要作用之一就是通过assignWindows()方法规定应该如何根据一个元素来确定它所属的窗口集合。此外,它还包含窗口的触发机制(也就是应该何时计算窗口内的 11 | 元素)、窗口的序列化器和是否EventTime时间类型。 12 | 13 | WindowAssigner类是一个抽象类,其中定义的方法在其中都没有具体的实现,而GlobalWindows是其的一个子类,该类用于将所有的元素分配给同一个GlobalWindow。 14 | 它的应用场景之一是为CountWindow分配元素,即每累计n个元素触发一次计算。除了GlobalWindows,还有三类主要窗口,它们在实际的生产场景中用的非常之多, 15 | 分别是滚动窗口、滑动窗口和Session窗口,它们针对ProcessingTime和EventTime都有其对应的实现。 16 | 17 | GlobalWindows是GlobalWindow的分配器,它负责为元素分配所属的Window(也就是GlobalWindow)。它的一个典型的应用场景是CountWindow,即每累计够n个 18 | 元素就触发计算的窗口。 19 | 20 | TumblingEventTimeWindows是基于事件时间的滚动窗口,其具有两个属性,分别是窗口的大小和偏移量(这个偏移主要用于控制窗口的起始时间),相邻窗口之间 21 | 没有重叠,一个元素一定会也只可能会属于一个窗口。而SlidingEventTimeWindows是基于事件时间的滑动窗口,其具有三个属性,分别是窗口大小、窗口每次滑动的 22 | 距离以及窗口的偏移量。在滑动窗口的场景下,一个元素可以属于多个窗口(只要窗口滑动的距离小于窗口的大小),也可以不属于任何窗口(只要窗口滑动的距离大于窗 23 | 口的大小)。 24 | 25 | MergingWindowAssigner扩展了WindowAssigner,它能对窗口进行合并。在所有SessionWindow的实现中,都会扩展MergingWindowAssigner类并对mergeWindows() 26 | 方法进行实现,以支持对窗口的合并操作。该方法包含有一个MergeCallback对象,用于在合并窗口的时候给出通知,执行一些额外的逻辑。SessionWindow都具有 27 | 一个属性:sessionTimeout,它表示属于同一个SessionWindow的元素,其任意两个相邻的元素之间的时间差都不比它小,否则后到的元素就会进入新的窗口之中。 28 | EventTimeSessionWindows是MergingWindowAssigner基于事件时间的扩展,从它的assignWindows()方法中可以看到,每当一个元素到来时都会创建一个新 29 | 的窗口,后面会再调用mergeWindows()方法对有重合的窗口进行合并。合并逻辑在TimeWindow.mergeWindows()方法中实现,也不是很复杂:首先对所有待合并的 30 | 窗口按照起始时间进行排序,然后创建两个变量,变量currentMerge用于存储当前合并的窗口(存放方式是一个二元组,第一个元素存放合并后的窗口,第二个元素存放 31 | 被合并的所有窗口),另一个变量merged用于存放多组合并结果(前面合并的窗口的集合)。然后对排序后的每个窗口,如果当前合并窗口为空,则不需要处理,直接放入 32 | 变量。如果已合并的窗口和待处理的窗口在时间上有交集,就取这两个窗口时间的并集,并将其赋值给currentMerge二元祖的第一个参数,将待处理窗口放入到二元组 33 | 的第二个参数集合中去。如果没有交集则将合并窗口currentMerge放入合并窗口集合merged中,其余的处理与合并窗口为空时的处理一致。如果合并窗口集合merged 34 | 中的被合并窗口数多于一个,表明窗口需要进行合并,此时需要通知窗口进行真正的合并操作。 35 | 36 | DynamicEventTimeSessionWindows也扩展了MergingWindowAssigner类,但它的不同之处在于它的sessionTimeout是动态的,在每次调用assignWindows() 37 | 方法动态分配窗口时会先调用其内部维护的SessionWindowTimeGapExtractor类对象的extract()方法来根据进入窗口的元素和sessionTimeout的函数关系获取 38 | sessionTimeout的取值。ProcessingTime的各种窗口分配类的处理方式与针对event time的处理方式类似,只是在assignWindows()方法中会调用WindowAssignerContext 39 | 类的getCurrentProcessingTime()获取当前的处理时间,其余的处理逻辑基本类似。 40 | 41 | 对于窗口,其实还有一个关于窗口的开始和结束时间的问题。在最新版本的FLink 1.12中,对于滚动窗口,无论是基于事件时间还是处理时间,都新增加了一个WindowStagger 42 | 的枚举型配置,它能够对滚动窗口的起始时间进行设置,默认情况下的取值是WindowStagger.ALIGNED,也就是各个分区的所有窗格中的数据会同时被触发,很难以 43 | 理解,那就举个例子来说:如果设置了一个十分钟的滚动窗口,且偏移为0,那么窗口的时间范围都将是[0, 10)、[10, 20)...也就是说,即使现在是12:04,但是 44 | 时间依然会进入[12:00, 12:10)的这个窗口,而不是[12:04, 12:14)的这个窗口。从Flink 1.12开始,新增了WindowStagger.RANDOM和WindowStagger.NATURAL 45 | 这两个类型。前者表示它会在第一个事件到来时随机产生一个偏移并由此设置窗口的范围,在上面的例子中就是表示,第一个窗口的起始时间可能是12:00至12:09这之间 46 | 的10个时间中的任意一个。后者表示它会按照第一个事件到达算子的时间作为窗口的起始时间偏移设置窗口,对于上面的例子,窗口将会是[12:04,12:14)、[12:14,12:24)... 47 | 通过增加WindowStagger.RANDOM和WindowStagger.NATURAL这两种新模式,也使得窗口的应用范围更加广泛和灵活。 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /docs/_build/html/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../fonts/fontawesome-webfont.eot");src:url("../fonts/fontawesome-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff") format("woff"),url("../fonts/fontawesome-webfont.ttf") format("truetype"),url("../fonts/fontawesome-webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} 2 | -------------------------------------------------------------------------------- /doc/ttl.md: -------------------------------------------------------------------------------- 1 | ### 状态生成时间(State TTL) 2 | 3 | 为了避免状态数据无限增长引发的OOM问题,必须有一个机制能够给状态进行瘦身,从而清除掉无效状态给系统带来的影响,Flink从1.6开始引入了TTL(状态的生存 4 | 时间)机制,它支持KeyedState的自动过期,并自动清除掉过期的State,但仅对时间特征为处理时间生效,对事件时间是无效。 5 | 6 | 从StateTtlConfig类来分析TTL机制的底层实现,该类中有5个成员属性,它们也是用户需要指定的参数配置。包括: 7 | * ttl表示用户设定的状态生存时间; 8 | * updateType表示状态时间戳的更新方式,它的取值类型是一个枚举; 9 | * stateVisibility表示已过期的状态数据的可见性,取值也是一个枚举; 10 | * ttlTimeCharacteristic表示对应的时间特征,取值同样是一个枚举,但实际上现阶段只支持一种时间特征,也就是处理时间; 11 | 12 | CleanupStrategies内部类用于规定过期状态的清理策略,可以在构造StateTtlConfig时通过调用其中的方法来指定,常见的方法如下: 13 | * cleanupFullSnapshot():当对状态做全量快照时清理过期数据,这对于开启增量快照的RocksDB状态后端没有作用,它对应于源码中的EmptyCleanupStrategy 14 | 策略。由于它只能保证状态后端持久化时不包含过期数据,而对于TM本身的过期状态则不作任何清理,因此无法从根本上解决状态过大的问题; 15 | * cleanupIncrementally():增量清理过期数据,默认是在每次访问状态时进行清理,如果设置第二个参数runCleanupForEveryRecord为true则也会在每 16 | 次写入/删除时清理。第一个参数cleanupSize指定每次触发清理时检查的状态数量,它仅对基于堆的状态后端生效,对应于源码中的IncrementalCleanupStrategy 17 | 策略; 18 | * cleanupInRocksdbCompactFilter():通过FLink定制的过滤器过滤掉过期状态数据,它有一个参数queryTimeAfterNumEntries表示在写入多少条状态 19 | 数据后,通过状态时间戳来判断是否过期。仅对于RocksDB状态后端生效,对应于源码中的RocksdbCompactFilterCleanupStrategy策略; 20 | 21 | getOrCreateKeyedState()方法用于创建并记录状态实例,它定义在所有Keyed State状态后端的抽象基类AbstractKeyedStateBackend中,该函数的主要逻 22 | 辑是调用TtlStateFactory.createStateAndWrapWithTtlIfEnabled()方法来创建State,TtlStateFactory就是产生TTL状态的工厂类。由此可见,如果 23 | 我们为StateDescriptor加入了TTL,那么就会调用TtlStateFactory.createState()方法创建带有TTL的状态实例;否则,就会调用StateBackend.createInternalState() 24 | 创建一个普通的状态实例。 25 | 26 | 主要分析带有TTL的状态实例的创建方法TtlStateFactory.createState()。stateFactories是一个Map结构,维护着各种状态描述符及与之对应的产生该类状 27 | 态对象的工厂方法映射。所有的工厂方法都被包装成了SupplierWithException(这是Java 8提供的Supplier的函数式接口),所以在上述createState()方法 28 | 中,可以通过SupplierWithException.get()方法来实际执行create*State()工厂方法,从而获得新的带TTL的状态实例,它们其实就是普通状态类名加上Ttl 29 | 前缀,只不过没有公开给用户使用。此外,在生成Ttl*State时,还会通过createTtlStateContext()方法生成TTL状态的上下文。 30 | 31 | TtlStateContext的本质是对下面实例的封装: 32 | * 原始State:通过StateBackend.createInternalState()方法创建得到; 33 | * StateTtlConfig:状态TTL的配置; 34 | * TtlTimeProvider:用于提供判断状态过期标准的时间戳,目前的实现比较简单,就是代理了System.currentTimeMillis(); 35 | * State的序列化器:通过StateDescriptor.getSerializer()方法取得; 36 | * Runnable类型的回调方法:通过registerTtlIncrementalCleanupCallback()方法产生,用于状态数据的增量清理; 37 | 38 | State的相关类图如下所示: 39 | ![Ttl类图](../images/ttl.png "Ttl类图") 40 | 从图中可以看到,所有的Ttl*State都是AbstractTtlState的子类,而AbstractTtlState又是装饰器AbstractTtlDecorator的子类,AbstractTtlDecorator 41 | 类是基本的TTL逻辑的封装,其成员属性除了前面在TtlStateContext已经提到过的外,还有表示在读取状态值时也更新时间戳的updateTsOnRead,以及表示即使 42 | 状态过期在状态真正删除前也返回状态值的returnExpired。而状态值与TTL的包装(封装成TtlValue,它有两个属性,userValue表示状态值,lastAccessTimestamp 43 | 表示最近访问的时间戳)和过期检测实际上都是由工具类TtlUtils负责处理的,实现上也比较简单。 44 | 45 | AbstractTtlDecorator类的核心方法是获取状态值的getWrappedWithTtlCheckAndUpdate(),它接受三个参数: 46 | * getter:可以抛出异常的Supplier,用于获取状态的值; 47 | * updater:可抛出异常的Consumer,用于更新状态的时间戳; 48 | * stateClear:可抛出异常的Runnable,用于异步删除过期状态; 49 | 50 | 这说明,在默认情况下,后台清理过期状态的策略是惰性的,即只有当状态值被读取时,才会对状态做过期检测,并异步清除过期的状态。这种机制带来的问题是对于那 51 | 些实际上已经过期但没有再次被访问的状态来说将无法被清除,从而导致状态大小无限增长,这点应该引起注意。 52 | 53 | 以TtlMapState为例来分析下TTL状态的实现,从其源码中可以看出,TtlMapState的增删改查操作都是在原MapState上进行,只是通过装饰器模式增加了TTL的相 54 | 关逻辑,非常简单。但是有一点需要注意,所有的增删改查操作之前都需要执行accessCallback.run()方法。该方法会在启用增量清理策略时通过在状态数据上维护 55 | 一个全局迭代器向前清理过期数据,如果没有启用增量清理策略,则accessCallback为空。 56 | 57 | 最后分析到RocksDB压缩过滤清理策略,Flink会通过维护一个RocksDbTtlCompactFiltersManager的实例来管理FlinkCompactionFilter过滤器,这个过滤 58 | 器并不是在Flink工程中维护的,而是位于Data-Artisans中为Flink专门维护的FRocksDB库内,主要是C++代码,通过JNI调用。 59 | -------------------------------------------------------------------------------- /doc/2pc.md: -------------------------------------------------------------------------------- 1 | ### 两阶段提交(2PC) 2 | 3 | 两阶段提交(2PC)是最基础的分布式一致性协议,Flink主要用它来实现exactly once,完成事务性写入。 4 | 5 | 首先,我们必须知道,在分布式环境中,为了让每个节点都能感知其它节点上事务的执行情况,需要引入一个中心节点来统一协调处理所有节点的执行逻辑,这 6 | 个中心节点就叫做协调者,而其它被中心节点协调的节点就是参与者。 7 | 8 | 而两阶段提交,故名思义,就是将分布式事务划分成了两个阶段,分别是提交请求(表决)阶段和提交(执行正常或异常)阶段。其中,协调者会根据参与者对第一 9 | 阶段,也就是提交请求/表决阶段的相应来决定是否真正的执行事务,也就是第二步执行提交/执行正常阶段或是执行提交/执行异常阶段。 10 | 11 | 下面分别对这两个阶段进行介绍。 12 | 13 | 第一阶段,提交请求(表决)阶段: 14 | * 协调者向所有参与者发送prepare请求与事务内容,询问是否可以准备事务的提交,并等待参与者的相应; 15 | 16 | * 参与者执行事务中包含的操作,并记录用于回滚的undo日志和用于重放的redo日志,但是并不真正的执行提交; 17 | 18 | * 参与者向协调者返回事务操作的执行结果,执行成功返回yes,否则返回no; 19 | 20 | 第二阶段,提交(执行)阶段分为正常和异常两种情况: 21 | * 如果所有的参与者都返回yes表示事务可以被提交:协调者向所有的参与者发送提交请求,参与者收到协调者发送的提交请求后,真正的进行事务的提交操作, 22 | 并释放其占用的事务资源,向协调者返回确认信息,协调者收到所有的参与者返回的确认信息,分布式事务成功完成; 23 | 24 | * 如果有参与者返回no或者是超时未返回,说明事务中断,需要回滚:协调者向所有参与者发送回滚请求,参与者收到回滚请求后,根据undo日志回滚到事务执行 25 | 前的状态,释放占用的事务资源,并向协调者返回ack,协调者收到所有参与者的ack信息,事务回滚完成; 26 | 27 | 从上面的介绍可以看出,2PC的实现非常简单,当然也存在一定的缺陷: 28 | * 协调者存在单点故障,如果协调者宕机,则整个2PC逻辑就完全不能运行; 29 | 30 | * 整个执行过程完全同步,参与者在等待其它参与者响应时都处于阻塞状态,高并发场景下存在性能问题; 31 | 32 | * 依然可能存在不一致的风险,比如如果在第二阶段由于网络或其它原因,只有部分参与者收到了commit请求,此时就会造成部分参与者进行了事务的提交而其它 33 | 参与者未提交的情况; 34 | 35 | Flink作为一款时下最火爆的流式处理引擎,能够提供exactly once的语义保证,仅仅flink内部是精确一次的实际上没有多大的意义,因此人们提出的端到端的 36 | exactly once语义保证,即输入、处理程序、输出这个三个部分协同作用,共同实现整个流程的精确一次语义。端到端的exactly once的实现需要数据的输入、 37 | 处理和输出都能支持exactly once。从数据的输入端角度来看就是必须能支持一定时间内的消息重放和事务性提交,在大数据实时处理领域,使用最多的数据输 38 | 入端应该是kafka了,因此它也需要支持端到端的精确一致语义,在kafka 0.11版本之前,其实它是不支持的,此时就只能通过at least once语义配合下游的 39 | 消息幂等处理来间接实现exactly once,但是此时由于需要下游的支持所以存在一定的局限性,从0.11版本开始,kafka通过引入TransactionCoordinator 40 | 来支持了事务写入以支持Flink端到Kafka端的Exactly once。对于处理程序来说,Flink内部是通过检查点机制(checkpoint)和分布式快照来实现exactly once 41 | 的。同时,在Flink中提供了基于2PC的SinkFunction,叫做TwoPhaseCommitSinkFunction来对输入端的事务性写入提供基础性的支持,这是个抽象类,所 42 | 有需要保证exactly once的Sink逻辑都需要继承这个类。 43 | 44 | TwoPhaseCommitSinkFunction类的继承体系如下图所示。 45 | ![TwoPhaseCommitSinkFunction继承体系](../images/2pc.png "TwoPhaseCommitSinkFunction继承体系") 46 | 47 | 其中有四个抽象方法与2pc的过程相关,分别是: 48 | * beginTransaction():开始一个事务,返回事务信息的句柄; 49 | 50 | * preCommit():预提交阶段(提交请求)的逻辑; 51 | 52 | * commit():正式提交阶段的逻辑; 53 | 54 | * abort():取消事务; 55 | 56 | 由此可见,输出端也必须能对事务性写入提供支持,当然如果输出sink也是kafka 0.11及以上版本肯定是没问题的,如果是其它的输出端也需要其支持事务或 57 | 实现了写入的幂等才行。以kafka来看,在FlinkKafkaProducer011类中实现了beginTransaction()方法。当要求支持exactly once语义时,每次都会 58 | 调用createTransactionalProducer()来生成包含事务ID的producer。而preCommit()方法的实现就很简单了,就是直接调用了producer的flush()方法, 59 | 它是在Sink算子进行snapshot操作时被调用的。 60 | 61 | 下图是官方给出的两阶段提交中的提交请求阶段的解释图: 62 | ![两阶段提交-预提交](../images/cpand2pcprepare.png "两阶段提交-预提交") 63 | 64 | 从图中可以看到,每次进行checkpoint时,JM会在数据流中插入一个barrier,这个barrier会随着DAG图向下游传递,每经过一个算子都会触发checkpoint将 65 | 状态快照写入状态后端,当这个barrier到达kafka sink时,通过KafkaProducer.flush()方法将数据刷写到磁盘,但是此时还未真正提交。 66 | 67 | FlinkKafkaProducer011类中的commit()方法的实现也是调用了KafkaProducer的commitTransaction()方法来向Kafka提交事务。这个方法是在 68 | TwoPhaseCommitSinkFunction类中的notifyCheckpointComplete()方法和recoverAndCommit()中被调用的,也就是它会在本次checkpoint涉及到的 69 | 所有的检查点都完成后或是在失败恢复时被调用。 70 | 71 | 下图是官方给出的两阶段提交中的提交阶段的解释图: 72 | ![两阶段提交-提交](../images/cpand2pccommit.png "两阶段提交-提交") 73 | 74 | 由此可见,只有在所有的检查点都已经成功完成的情况下,写入checkpoint才会成功。对比两阶段提交的理论,可知这也符合两阶段提交的流程。其中,JM作为了 75 | 协调者的角色,而各个operator作为了参与者的角色(虽然只有sink这个参与者会真正执行提交)。如果有检查点失败,则分布式快照无法完成,如果最终重试也失败 76 | 则会调用abort()回滚事务。 77 | 78 | 以上,也解释了为何kafka中,对于事务性的producer不需要调用flush()函数,这是因为事务producer在提交事务之前,会将缓冲的数据flush到磁盘,这样 79 | 就可以确保那些在开启事务之前发送的消息能在该事务被提交之前完成。 80 | 81 | 82 | -------------------------------------------------------------------------------- /doc/raft.md: -------------------------------------------------------------------------------- 1 | ### 分布式共识算法Raft 2 | 3 | 在分布式系统中,其实有两种共识算法比较常见,一种是今天要分析的Raft算法,另一种是最出名的Paxos算法。相对来说,Raft算法比较容易理解,也比较好 4 | 实现,因此在真实世界中,其应用要比Paxos算法广泛,比如etcd、Kudu等就利用了Raft算法。而Paxos算法唯一比较被熟知的实现是其变种--Zookeeper中 5 | 用到的zookeeper atomic broadcast算法(ZAB)。 6 | 7 | 在实现上,Raft将复杂的分布式共识问题拆分为了leader election、log replication和safety三个子问题,并分别予以解决;另外,它还对状态空间进 8 | 行压缩,减少因状态过多引入的不确定性。 9 | 10 | 在共识算法中,所有的服务节点都包含一个有限状态自动机,也就是复制状态机。每个节点都维护着一个复制日志队列,复制状态机按序输入并执行该队列中的 11 | 请求,执行状态转换并输出结果。如果能够保证各个节点中日志的一致性那么状态机的状态转换和输出也就都一致。其执行过程如下: 12 | * 某个节点的共识模块(包含共识算法的实现)从客户端接收请求; 13 | 14 | * 共识模块将请求写入自身的日志队列,并与其它节点的共识模块交互,保证每个节点的日志都相同; 15 | 16 | * 复制状态机处理日志中的请求; 17 | 18 | * 将输出结果返回客户端; 19 | 20 | 我们知道,如果集群中超过半数的节点可用时,就能正确的处理分布式事务,因此在共识算法中的集群几乎总是使用奇数个节点(偶数个浪费机器资源且可能产生 21 | 脑裂),使用ZAB协议的zookeeper集群也是这样。 22 | 23 | 在Raft中,任意节点只能处于leader、follower、candidate三种状态之一,每个节点都有一个倒计时器,时间随机在150ms-300ms之间,只有在收到选举 24 | 请求或收到leader的心跳时才会重置这个倒计时时间。在集群启动时,所有的节点都是follower节点,如果follower在经过这个倒计时时间之后发现仍然没有 25 | leader节点,就会切换为candidate节点并发起选举,如果得到超过半数票数就会成为leader节点。如果发现了更新的leader节点,原leader节点就会重新 26 | 变为follower节点。leader节点负责从客户端接收请求,复制到follower节点,并告诉follower节点何时可以处理请求。如果leader节点故障或失联则会 27 | 重新进行选举。由此可见,leader节点是大家投票选出来的,每个leader工作一段时间,然后选出新的leader继续负责,因此每个leader都有履职期也叫做 28 | 任期term。任期从选举开始,然后经过一段或长或短的稳定工作时期,且任期是递增的,这样才能发现更新的leader节点。此外也可能没选出leader就结束了, 29 | 就需要发起新的选举。 30 | 31 | 上面提到过,如果follower在选举超时时间内没有收到leader的心跳,就会主动发起选举。具体过程如下: 32 | * 增加节点本地的当前任期,并切换到candidate候选者状态; 33 | 34 | * 投票给自己; 35 | 36 | * 并行的给其它节点发送请求投票的RPC请求; 37 | 38 | * 等待其它节点的回复; 39 | 40 | 根据其它节点的回复内容不同,可能出现三种情况: 41 | 1. 包含自己的票数在内收到了超过半数的投票,赢得选举,称为leader; 42 | 43 | 2. 被告知已有别的节点当选为leader,重新切换为follower; 44 | 45 | 3. 一段时间内仍未收到超过半数的投票,也没有其它节点当选,保持candidate状态,重新发起选举; 46 | 47 | 对于第一种情况,如果赢得选举则会立即给其它节点发消息以避免重复选举,那么选举到底如何进行呢?在一次选举中,每个节点最多只能投一票,并且投票先到 48 | 先得,也就是谁最先给该节点发请求投票的RPC请求,该节点的票就会投给谁;对于第三种情况,为了避免同时有多个candidate而出现平票现象,Raft引入了随 49 | 机选举超时机制,也就是超时时长较短的节点会最终赢得选举。 50 | 51 | 52 | 在选举出leader节点后,就会开始处理客户端请求,客户端的所有请求都会发送给leader,并由leader调度这些请求的顺序,保证leader和follow状态的一致 53 | 性。Raft会将请求及请求的顺序告知follower,并以相同的顺序来执行请求,保证状态一致。在Raft中,leader将客户端请求封装到了一个个的log entry中, 54 | 再将这些log entries复制到所有的follower节点,以相同的顺序执行这些log entry中的命令,以保证状态一致。从leader节点的视角来看,请求的完整流程 55 | 如下: 56 | * leader节点收到来自客户端的请求,将请求封装到log entry; 57 | 58 | * leader节点并行的发送AppendEntries RPC请求,并等到follower的响应; 59 | 60 | * 当收到大多数的回复后,将请求应用到复制状态机,并通知follower节点也应用请求; 61 | 62 | 这个过程与两阶段提交(2PC)有点类似,但是Raft只需要大多数节点的确认,而不像2PC需要全部节点的确认。此外,由于网络等原因,leader节点和follower 63 | 节点的日志并不完全相同,但leader节点会不断重发AppendEntries RPC请求,直到所有节点的日志都达到一致以保证最终一致性。 64 | 65 | 66 | 分布式系统中可能面临着各种复杂的情况,在这样复杂的环境中,如何保证日志的最终一致性呢? 67 | Raft协议保证以下五种属性: 68 | 1. 选举安全性(election safety):每个任期内只允许选出最多一个领导,如果集群中有多于一个领导,就发生了脑裂(ES中常见的一种异常情况)。Raft中 69 | 通过:一个节点在一个任期内最多只能投一票和只有获得多数票的节点才能称为leader节点保证了这个属性; 70 | 71 | 2. 领导者只追加(leader append-only):客户端发出的请求都是插入领导者日志队列的尾部,没有修改或删除的操作; 72 | 73 | 3. 日志匹配(log matching):如果两个节点的日志队列中,某两个log entry具有相同的下标log index和任期值term,那么它们携带的客户端请求以及 74 | 它们之前的所有的log entry都相同。Raft中通过:领导节点只追加和每条AppendEntries都会包含最新entry之前那个entry的下标与任期值,如果跟随节 75 | 点在对应下标找不到对应任期的日志,就会拒绝接受并告知领导节点; 76 | 77 | 4. 领导者完全性(leader completeness):如果有一条日志在某个任期被提交了,那么它一定会出现在所有任期更大的领导者日志里。Raft中通过:一个 78 | 日志被成功复制到大多数节点才算是提交和一个节点只有得到大多数节点的投票才能称为leader,而节点B投票给节点A的前提之一是节点A的日志必须比节点B 79 | 的日志更新; 80 | 81 | 5. 状态机安全性(state machine safety):如果一个节点已经向其复制状态机应用了一条日志中的请求,那么对于其他节点的同一下标的日志,不能应用 82 | 不同的请求。Raft不允许领导者在当选后提交"前任"的日志,而是通过日志匹配原则,在处理"现任"日志时将之前的日志一同提交。具体方法是:在领导者任 83 | 期开始时,立刻提交一条空的日志,并同时将"前任"的日志提交; 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /doc/thinking.md: -------------------------------------------------------------------------------- 1 | 前面已经写过了太多的Flink源码分析的东西,可能讲得比较偏理论,今天呢,就来分享一个实际工作中碰到的Flink场景下的问题(但是实际上除了跟Flink流分区有点 2 | 关系外,跟Flink其余部分的关系也不是很大),也是自己编码过程中的一个教训。 3 | 4 | 先来简单描述一下场景,实际的场景比较复杂,这里就简单的抽象一下。 5 | 在电商业务中,如果我们需要对每一笔订单进行统计来计算销售额,比如说,618和双十一时的订单金额统计。对于每一个用户来说,他可能先在电商网站上下一个单, 6 | 这个订单会包含多个所要购买的商品,此时就需要将其金额加入到实时销售额里面,而用户同时可以对这个订单进行不同的操作,比如修改物流地址、取消订单等。那么, 7 | 在电商大促实时大屏上就应该根据用户对其的实际操作增减销售额,这就需要保证对每个订单的顺序才能保证处理的准确性,如果取消订单的消息的处理早于下单和修改 8 | 物流地址,那么最终的处理结果就可能不正确。那么如何保证实时数据处理的可靠性? 9 | 10 | 假设我们使用的是kafka接入订单流,然后进行处理,并且处理完成的消息写回kafka,其实就变成了两个问题:如何保证kafka接入数据的顺序、如何保证flink处理的 11 | 顺序? 12 | 13 | 首先,如何保证kafka接入数据的顺序?emmmmmm....这是个在实时计算面试中经常会问的问题,但是请一定注意,这是个陷阱问题,因为kafka根本就无法保证全局 14 | 消息的有序性,在它的各个分区间的消息一定是无法保证顺序的,但是kafka各个分区内的数据是有序的,那么我们就可以利用上这个特性。如果我们在使用kafka接 15 | 入数据时,将相同订单的消息放到同一分区,那么就能保证订单的创建消息一定早于订单的取消的消息,即第一个问题解决。 16 | 17 | 在解答第二个问题之前,先要说一下,此处的乱序与flink的EventTime事件乱序并不是很一样,在此处由于数据接入到kafka的分区时保证了相同订单的消息的有序, 18 | 所以只需要保证相同的订单的消息能够被同样的flink消费线程消费,并且在整个flink的处理流程中,所有相同的订单的消息也都能被每个算子的相同子分区处理即可。 19 | 如果算子之间的流分区是forward、global、hash或是上下游算子chain在一起都是可以保证相同订单的消息的处理顺序,否则就需要进行自定义分区,确保相同订单 20 | 的消息被分配到相同的分区处理。 21 | 22 | 终于说到了重点,在实现自定义分区时,可以通过Partitioner实例的自定义partition方法将记录输出到下游,代码如下: 23 | new Partitioner(){ 24 | @Override 25 | public int partition(String key, int numPartitions){ 26 | int hashCode = key.hashCode(); 27 | int result = hashCode % numPartitions; 28 | return result; 29 | } 30 | } 31 | 32 | 在上面的代码中,将订单id作为key传入,获取其hash值,而numPartitions会自动赋值为下游算子的并行度,取模后所得的值表示的是数据被传递到下游算子的哪一个 33 | 分区中。将上面的这个类的实例传给partitionCustom()方法作为参数,一个简单的自定义分区器就完成了。看上去很easy,对不对? 34 | 35 | 满心欢喜的进行测试,发现会时不时的报分区取值不能为负值,然后程序就会自动重启,导致消息处理失败。这就奇怪了,哪里会有负值呢?由于报错信息并不明确,所以 36 | 起初以为自定义分区实现方式不对或是改动的其它地方产生了什么影响,但是也没找出个中原因,于是删掉上述自定义分区代码,发现报错没有了,确定是自定义分区代码 37 | 问题。但是这个代码这么简单,貌似也看不出什么问题来,偶然间想到java的取模计算的一个问题,就是取模的结果的符号与被模数一致,而不是自动变成正数,也就是 38 | -10%3=-1,而10%(-3)=1,这与很多其它语言的实现不太一样,但是由于并不经常碰到这种场景,所以一直将其忘记了。于是将代码调整为一下: 39 | new Partitioner(){ 40 | @Override 41 | public int partition(String key, int numPartitions){ 42 | int hashCode = key.hashCode(); 43 | int hashCodeAbs = Math.abs(hashCode); 44 | int result = hashCodeAbs % numPartitions; 45 | return result; 46 | } 47 | } 48 | 49 | 心想这下应该没问题了吧,测试环境测试貌似也很成功,跑了两天没出问题,于是上线到线上,本以为事情就这么顺利的解决了,没想到过了半个月左右,发现程序又突然间 50 | 重启,导致数据的处理出现积压,看异常还是分区不能为负数,这就奇怪了,不是已经解决了吗,而且跑了这么久也没出过问题,怎么突然就有问题了,看线上版本也没有被 51 | 人修改,反编译代码发现先前的调整也在,这说明就是这个现有的代码还是有问题,可是查看代码,hash结果已经经过了取绝对值,这结果应该肯定是正数或0才对,不应该 52 | 冒出一个负值。百思不得其解,让人无比郁闷!没办法,只能加上调试代码,将result在负值时的key取值和hashCode及hashCodeAbs全部打印出来,发现hashCode和 53 | hashCodeAbs的取值都是Integer.MIN_VALUE,于是写程序简单验证Math.abs(Integer.MIN_VALUE)的结果,居然真的是Integer.MIN_VALUE,于是点进去 54 | Math.abs()方法的源码,发现这样一段话:注意,如果参数的值为Integer.MIN_VALUE,这个整型数据所能表示最小的负值,那么将返回原值,且是一个负值。其实很好 55 | 理解,Integer.MIN_VALUE的取值是-2147483648,其取绝对值就是+2147483648,但一个32位整数可以表示的最大值是+2147483647,+2147483648超出了范围。于是 56 | 被"翻转"成了-2147483648,也就是原值。问题终于找到了答案,于是再次将代码调整为: 57 | new Partitioner(){ 58 | @Override 59 | public int partition(String key, int numPartitions){ 60 | int hashCode = key.hashCode(); 61 | int hashCodeAbs = Math.abs(hashCode); 62 | int result = (hashCodeAbs & 0x7FFFFFFF) % numPartitions; 63 | //int result = hashCodeAbs % numPartitions; 64 | //result = (result < 0 ? (result + numPartitions) : result); 65 | return result; 66 | } 67 | } 68 | 这下终于彻底将这个问题解决,再也没有出现问题了。 69 | 70 | 写在最后:负数取模的结果因语言而异,并不要依赖于特定实现。如果说非要记的话,就是被取模数如果与模数符号相同,则所有语言的实现都倾向于使商尽可能 71 | 小;如果被取模数与取模数符号不同,则Java/C++等倾向于使商尽可能大,而python等新型语言则倾向于使商尽可能小。 -------------------------------------------------------------------------------- /doc/nio.md: -------------------------------------------------------------------------------- 1 | ### IO模型及NIO、AIO 2 | 3 | 我们知道在Unix网络编程中有五种IO模型,它们分别是: 4 | * 阻塞IO(BIO) 5 | 6 | * 非阻塞IO(NIO) 7 | 8 | * IO多路复用(IO multiplexing) 9 | 10 | * 信号驱动IO(signal driven IO) 11 | 12 | * 异步IO(asynchronous IO) 13 | 14 | 但是实际上除了AIO外,其余的都是同步IO(同步IO不一定是阻塞IO),之所以称它们是同步IO,是因为在读写事件就绪后它们都需要自己负责进行读写,也就是 15 | 说整个读写过程是阻塞的。由于在实际生活中,信号驱动IO非常少用到,所以主要介绍其余的四种。 16 | 17 | 先来看一下IO的过程,对于一个网络IO,主要涉及两个交互,一是应用调用IO进程,等待数据准备完成;二是数据准备好并从内核态拷贝到用户进程中。 18 | 19 | 在阻塞IO的场景下,每一个步骤都是阻塞的,所以如果应用程序调用了IO进程,内核就必须开始准备数据,如果此时数据还没有完全到来,那么内核就必须先等待 20 | 数据全部到达。在数据完全到来后,将数据从内核态拷贝到用户态,内核返回并处理其它请求,应用程序从阻塞状态恢复并重新运行。纵观整个过程,我们发现在 21 | IO的两个阶段都被阻塞了。 22 | 23 | 双阻塞明显不是一个好的设计,它的效率极端低下,实际上在应用程序发起IO调用后,如果内核中的数据还没有准备好,此时并不需要阻塞用户进程,而是可以直 24 | 接返回一个未就绪的状态。然后每隔一段时间就再次发起一次IO调用,什么时候内核中的数据准备就绪了,此时再收到应用程序的IO调用时就能立马将数据从内核态 25 | 拷贝到用户态。此时,用户程序并没有因为数据未就绪而阻塞,在这个过程中它能干一些其它的事情,这就是非阻塞IO。 26 | 27 | 非阻塞IO的性能相比于阻塞IO已经提升了很多,但是它仍然需要每个应用程序都定时去发起IO调用以检测内核中的数据是否就绪,实际上我们可以用一个代理来实 28 | 现同样的功能,并且这个代理不仅可以处理某个应用程序的请求,而是可以处理多个应用程序的IO请求,其中任意一个数据就绪了就通知应用程序处理,这明显的解 29 | 放了应用程序的性能。这就是所谓的IO多路复用,它有三种不同的实现方式(这些都是Unix系统所支持的): 30 | * select:最早期的实现,它的缺点是需要无差别的轮询所有等待在其上的流,从而找出能读出数据或写入数据的流,因此其时间复杂度为O(n); 31 | 32 | * poll:本质上与select没啥区别,优点在于解决了select原有的单进程可监视的fd的数量限制,采用链表的方式,所以没有最大连接数限制; 33 | 34 | * epoll:其有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式。在LT模式下只要fd还有数据可读,每次epoll_wait都会返回它的事件,提醒用户程序去 35 | 操作,而ET模式中只会提示一次,直到下次再有数据流入之前都不会再提示; 36 | 37 | 最后介绍异步IO,其与上面介绍的几种方式都不太一样。它的读取操作会通知内核进行读取并将数据拷贝至进程中,完成后通知进程整个操作全部完成,而读取 38 | 操作会立刻返回,应用可以进行其它的操作,而所有的数据读取、拷贝工作全部都由内核去做,完成后通知进程,进程调用绑定的回调函数来处理数据。 39 | 40 | 总结一下这些不同类型的IO操作: 41 | * 阻塞IO:调用后一直等待远程数据就绪后再返回,直到读取结束; 42 | 43 | * 非阻塞IO:无论在什么情况下都会立即返回,不会被阻塞,但是它仍要求进程不断地主动询问内核是否准备好数据,也需要进程主动地再次调用读取方法来将 44 | 数据拷贝到用户内存; 45 | 46 | * 同步IO:一直阻塞进程,直到I/O操作结束,BIO、NIO、IO多路复用、信号驱动IO都是同步IO; 47 | 48 | * 异步IO:不会阻塞调用者,而是在完成后通过回调函数通知应用数据拷贝结束; 49 | 50 | Flink从1.2版本开始引入了异步IO机制,专门用于解决Flink计算过程中与外部系统的交互,它提供了一个能够异步请求外部系统的客户端,也就是AsyncWaitOperator, 51 | 由AsyncDataStream.addOperator()/orderedWait()/unorderedWait()产生,是异步IO的核心。在其构造函数中传入了AsyncFunction类,这是个执行异步操作 52 | 的类,用户需要覆写其asyncInvoke()方法实现异步操作完成后的逻辑。StreamElementQueue是一个包含StreamElementQueueEntry的队列,底层实现是ArrayDeque, 53 | 也就是数组实现的双端队列。StreamElementQueueEntry是对StreamElement的简单封装,而StreamElement则是Flink中的基础概念,它可以是Watermark、 54 | StreamStatus、Record或LatencyMarker等的简单封装(简单的说,就是Flink中流动的流数据的类型的简单封装),通过CompletableFuture实现了异步的返回。可以 55 | 通过一张图来进行一下简单的了解: 56 | ![AsyncIO](../images/asyncio.png "AsyncIO") 57 | 58 | 从上图可以看出,上游的StreamElement进入AsyncWaitOperator的StreamElementQueue,并被封装为StreamElementQueueEntry的实例,然后AsyncWaitOperator 59 | 会调用传入的AsyncFunction的asyncInvoke()方法,这个方法与外部系统进行异步的交互。在异步操作完成后,asyncInvoke()方法会调用ResultFuture.complete() 60 | 方法将结果返回(ResultFuture就是CompletableFuture的代理接口),如果出现异常则会调用completeExceptionally()方法处理。从队列中移除未完成队列的元素,添 61 | 加元素到已完成队列,然后将已完成队列中的元素通过TimestampedCollector发送出去。 62 | 63 | AsyncDataStream.orderedWait()/unorderedWait()的输出是需要考虑顺序性的,如果调用的是orderedWait()方法,则会创建OrderedStreamElementQueue队列, 64 | 保持请求的顺序与输出结果的顺序一致,也就是先进先出;如果是采用unorderedWait()方法,则创建的是UnorderedStreamElementQueue队列,不保证顺序。如果使用 65 | 的是处理时间则先返回的结果会先输出,而采用事件时间时,还是需要额外保证水印的边界不错乱。 66 | * 有序是最简单的情况,只需要将元素按照到来的顺序放入OrderedStreamElementQueue,只有当队列中的队头请求异步操作返回了结果,才会触发输出,后面的请求先 67 | 返回也只能等待; 68 | 69 | * 处理时间的无序也不太复杂,它是在UnorderedStreamElementQueue中维护两个子队列,一个是未完成请求的队列,一个是已完成请求的队列,所有请求都先进入 70 | 未完成请求的队列中并执行异步操作,并按照操作完成的顺序进到已完成队列中,再从已完成队列中拉取并输出结果即可; 71 | 72 | * 事件时间的无序是比较复杂的一种情况,它允许两个水印之间的元素乱序,但是水印不能乱序,因此在使用两个队列的同时,未完成队列中还必须存储水印,这就是上面的 73 | WatermarkQueueEntry的由来。在水印之间存储的也不再是单个StreamElementQueueEntry,而是它们的集合。只有当未完成队列中的队头集合中有元素的异步操作返回 74 | 时才能将其移动到已完成队列里面。这样就可以保证在通过某个水印之前,它前面的所有异步请求都完成。 75 | 76 | 异步I/O的检查点实现比较简单,由于StreamElementQueue保存的就是尚未完成异步请求的元素,以及已完成异步请求但还没有发送的元素,只要遍历该队列,并将它们都放入 77 | 状态后端就算完成。 78 | 79 | 好啦,终于以Flink中的异步IO为例讲完了AIO的实例! 80 | 81 | 额外说一下,实际工作中由于Linux中的AIO并不完善,所以在Linux下的IO模型更多的是以NIO为主。比如常见的使用IO多路复用技术结合线程池来实现的高并发,这也是常说 82 | 的Reactor,如我们工作中经常遇到的Redis(单Reactor单进程模式)、Nginx(单Reactor多进程模式)、Netty(多Reactor多线程模式)等。C语言编写的系统更喜欢使用进程模 83 | 式(单Reactor下更是如此),而Java及其它的JVM语言更喜欢使用线程模式,因为JVM本身就是一个进程,JVM中有很多线程,业务线程只是其中之一。 84 | 85 | 关于IO模型的介绍到此就结束了。 86 | 87 | 今天是清明节,愿在抗击疫情中牺牲的烈士和逝世的同胞在天之灵能够安息!阿门!!! -------------------------------------------------------------------------------- /doc/backpressure.md: -------------------------------------------------------------------------------- 1 | ### 背压 2 | 3 | 背压是使用流处理系统中经常会面对的问题之一(之所以说是之一,是因为我觉得数据倾斜也时常遇到^-^),它在流计算中指的是job处理数据的速度小于数据流入的速度 4 | 时的处理机制,通常来说,背压出现的时候,数据会迅速堆积,如果处理不当,会导致资源耗尽甚至任务崩溃。背压的场景非常之多,比如在京东或淘宝的618、双十一 5 | 购物节中,当时钟指向0点时,大量的用户开始将喜欢的商品加入购物车并进行结算,此时系统的流量将是平时流量的几倍甚至是十几倍,在这段时间内系统接收到的 6 | 数据将远高于其所能处理的数据量,这就是通常所说的"背压"。如果背压不能得到有效的处理,将会耗尽系统资源甚至导致系统崩溃。当然,如果对延迟的要求不太高或 7 | 是数据量比较小,背压的影响可能不是那么明显,但是对于大数据量的Flink任务来说,背压会严重影响其checkpoint的时长甚至导致快照失败,从而产生稳定性问题, 8 | 而这对于生产环境是十分危险的。 9 | 10 | 背压可以分为针对访问速率的静态背压和针对资源占用的动态背压两种实现方式,静态背压的实现简单但粗暴,就是直接对发往下游的数据进行提前的限流,这就很难实现动 11 | 态调整背压阈值的效果,但是有时又不得不使用,比如当使用了第三方组件且其不支持动态背压时。相比而言,动态限流的逻辑一般实现上会更加的复杂但却可以根据系统 12 | 的资源和当时的流量进行动态的控制,因而能最大化资源的使用且避免了不断的对背压流量阈值进行评估和调整。 13 | 14 | 我们知道目前主要的流处理引擎其实都提供了背压功能,但是具体到每个引擎其实现背压的方式却各不相同。在以前开发运维过的Storm框架中,它采用的是通过监控Bolt 15 | 中接收队列的负载情况,如果超过高水位值就将背压信息写入Zookeeper中,然后Zookeeper会通知所有的worker进入背压状态,最后Spout停止发送数据。新版的Storm 16 | 采用的是引入高性能无锁缓冲队列Disruptor,当这个队列已满时则停止发送数据,最终将背压一级级的向上传导直到Spout,于是Spout停止从kafka拉取数据。当Disruptor 17 | 缓冲队列不再满时,Spout重新从kafka拉取数据,从而实现了整个背压的处理过程。由于Disruptor使用的是环形数组RingBuffer实现,且在读取写入时无锁所以性能 18 | 较高。但是需要注意的时一定要通过maxSpoutPending参数来设置该缓冲队列的长度,否则该队列的长度将会无限长,依然会有背压问题,但是该长度的设置就非常难以确 19 | 定,设置的太短,会导致频繁的背压,影响数据的处理和吞吐量,设置得过长又起不到良好的背压效果,且容易导致worker节点OOM。 20 | 21 | Flink没有继续使用Disruptor这种数据结构,但是其背压的实现依然还是有点类似。具体来讲,Flink中每一个Task都会有一个InputGate和ResultPartition,InputGate 22 | 负责接收数据,ResultPartition负责发送数据(当然Source Task没有InputGate,Sink Task没有ResultPartition),而这两个组件都会有一个对应的LocalBufferPool 23 | (缓冲池),LocalBufferPool中会有一定量的Buffer(这类buffer叫作Exclusive Buffer,其实就是Flink内存管理的单位MemorySegment的包装类,但它是特定Subtask 24 | 独享的)。当缓冲池中已申请的数量达到了上限或没有内存块时(此时,BufferPool中的Floating Buffer也已经被使用完毕),Task就会暂停读取Netty Channel,因此上游发 25 | 送端就会立即响应停止发送并进入背压状态,于是上游的写入也会停止,从而将背压逐级向上传递。这就是Flink基于TCP的背压实现的主要思路。 26 | 27 | 基于TCP的背压有两个明显的弊端:一个是每个TaskManager中可能要执行多个Task,如果多个Task的数据最终都要传输到下游的同一个TaskManager时就会复用同一个 28 | Socket进行传输,此时候如果单个Task产生背压,则会导致复用的Socket整个发生阻塞,其余的Task的数据也无法进行传输,包括Checkpoint Barrier也无法发出导 29 | 致下游执行checkpoint的延迟也增大;二是它依赖于最底层的TCP去做流控,会导致背压传播路径过长,生效的延迟也比较大。因此在Flink 1.5版本开始就引入了新的 30 | 基于Credit的背压机制。它的原理很类似于TCP的Window机制,相信对于熟悉TCP的我们应该很容易就能理清其原理。 31 | 32 | 那么什么是基于Credit的背压机制呢?其实就是在数据的发送端维护对应的数据接收端的credit信息,这个信息表示下游还可以接收credit个buffer的数据。每次向下 33 | 游发送buffer数据的时候,credit就减去buffer的数量。当credit值为0的时候,就停止向下游发送数据。下游在有新的空闲内存的时候会通知上游有新的credit可用。 34 | 上游接收到新增的credit数量之后,更新对应channel的credit数量,即可重新开始向下游发送数据。 35 | 36 | 以上是一些理论上的讲解,概念上的阐述,主要作用是帮助分析,我们都知道"talk is cheap",所以还是来从源码上进行分析会来的更清楚。 37 | 38 | CreditBasedSequenceNumberingViewReader是一个简单的ResultSubpartitionView的简单包装类,其中的属性numCreditsAvailable维护了下游消费端(也就是 39 | 下游RemoteInputChannel)的用于存放数据的可用buffer的数量,它表示了下游还可以接收credits个buffer的数据。每次向下游发送数据时都会调用getNextBuffer() 40 | 来获取待发送的数据,此时会对numCreditsAvailable的值自减。一旦credits的值变为0时即抛出IllegalStateException没有可用的credit异常,于是停止发送。当下 41 | 游RemoteInputChannel中的数据被消费后空闲出内存再通过notifyCreditAvailable通知上游重新开始发送,通知用的方法是notifyCreditAvailable(),这个方法会 42 | 在回收内存方法recycle()、监听器发现有缓存可用方法notifyBufferAvailable()、分配积压任务所需内存方法onSenderBacklog()时调用。通过追溯源码,我们发现 43 | notifyCreditAvailable()方法最终调用了CreditBasedPartitionRequestClientHandler类的notifyCreditAvailable()方法,这个方法最终是通过EventExecutor 44 | 发送出去了一个UserEvent。CreditBasedPartitionRequestClientHandler类的userEventTriggered()方法会得到响应,在userEventTriggered()这个方法中, 45 | 会调用writeAndFlushNextMessageIfPossible()方法尝试对队列中的每一个Input Channel写入还没有上报的可用credits数量并刷新,上报信息会被封装为AddCredit 46 | 的Netty消息,并被PartitionRequestServerHandler的channelRead0()方法读取到,在其中判断消息类型如果是AddCredit类型,则将其放入PartitionRequestQueue 47 | 中,在其中的addCredit()方法中会根据receiverId获取NetworkSequenceViewReader,并调用它的addCredit()方法,由于NetworkSequenceViewReader就是上游的 48 | CreditBasedSequenceNumberingViewReader,因此会直接调用其addCredit()方法将ResultSubpartitionView的可用credits增加,在增加了可用credits数量后,还 49 | 会调用enqueueAvailableReader()方法将reader加入到可用reader列表中,如果可用reader列表之前为空,还需要将reader对应的ResultSubpartitionView的buffer 50 | 发送到下游,在其中会调用getNextBuffer()方法,它会将numCreditsAvailable减一之后判断是否还有可用的credit,如果没有则抛出IllegalStateException异常。 51 | 52 | CreditBasedPartitionRequestClientHandler类的writeAndFlushNextMessageIfPossible()方法会调用RemoteInputChannel类的getAndResetUnannouncedCredit() 53 | 方法获取到未上报的credits数即属性unannouncedCredit并将其值清零。 54 | 55 | 整个Flink基于Credit的背压机制分析到这里也就差不多了,其实现原理经过我们的分析之后其实相当的简单,抽象一下其实就是一个分布式环境下的生产者消费者模型,其核心 56 | 就是credit的上报和通知。 -------------------------------------------------------------------------------- /doc/statestore.md: -------------------------------------------------------------------------------- 1 | ### State存储 2 | 3 | Flink支持有状态的计算,其支持的状态又分为两种:Operator State和Keyed Sate,前者没有当前key的概念,而后者总有一个current key 4 | 与之对应。此外,前者只能存放在堆中,而后者既可以存放在堆中,也可以存放在堆外。Flink的Keyed Sate是由Key Group来组织的,并分布在 5 | Flink算子的哥哥并发实例上,每个算子上的Key Group个数即为最大并发数。 6 | 7 | 当使用MemoryStateBackend和FsStateBackend时,默认情况下会将状态数据保存到CopyOnWriteStateTable中,它是StateTable 8 | 接口的一个实现,其中可以保存多个KeyGroup的状态,每个KeyGroup对应一个CopyOnWriteStateMap。 9 | 10 | CopyOnWriteStateMap的结构类似于HashMap,但是它相比于HashMap支持了两个特别有意思的功能: 11 | * 哈希结构为了保证数据读写的效率都会有宇哥扩容策略,CopyOnWriteStateMap采用的是渐进式rehash策略,它不会一次性将所有数 12 | 据都迁移到新的hash表,二是会逐渐的将数据迁移过去; 13 | 14 | * 支持checkpoint时的异步快照,可以在快照的同时对其中的数据执行修改操作,并能同时保证快照数据的准确性; 15 | 16 | MemoryStateBackend和FsStateBackend的KeyedStateBackend都使用HeapKeyedStateBackend存储数据,HeapKeyedStateBackend 17 | 持有Map> registeredKVStates来存储StateName与具体State的映射关系。registeredKVStates的 18 | key就是StateName,value是具体的State数据,value存储在StateTable中。 19 | 20 | StateTable有两种实现: 21 | * CopyOnWriteStateTable属于Flink定制化的数据结构,在进行checkpoint时支持异步快照; 22 | * NestedMapStateTable直接使用嵌套的双层HashMap存储数据,在进行checkpoint时只能进行同步快照; 23 | 24 | 下面主要就CopyOnWriteStateTable类进行介绍。在StateTable中持有StateMap[] keyGroupedStateMaps真正的存储数据。StateTable 25 | 会为每个KeyGroup的数据都初始化一个StateMap来对KeyGroup做数据隔离。在对状态进行操作时,它会先根据Key找到对应的KeyGroup,从而拿 26 | 到相应的StateMap,这样才能对状态进行操作。而在CopyOnWriteStateTable中就使用CopyOnWriteStateMap存储数据,这是一个数组+链表构 27 | 成的Hash表,其中的数据类型都是StateMapEntry。Hash表的第一层是一个StateMapEntry类型的数组,也就是StateMapEntry[]。在StateMapEntry 28 | 类中有一个StateMapEntry next指针构成的链表。 29 | 30 | 先来介绍下CopyOnWriteStateMap类的渐进式rehash策略,它其中有一个hash表堆外提供服务,但是如果表中的元素太多需要扩容时,就需要将数 31 | 据迁移到一个容量更大的hash表中去。在Java的HashMap扩容时,会将旧Hash表中的所有数据一次性的都移动到大Hash表中,这样的策略存在一定的 32 | 问题:如果当前HashMap中已经存储了1G的数据,那么就需要将1G的数据一次迁移完成,这个过程可能会比较耗时。而CopyOnWriteStateMap在扩容 33 | 时,则不会一次将数据全部迁移,而是在每次操作它时慢慢的将数据迁移到大的Hash表中。 34 | 35 | 具体说来,就是在内存中有两个Hash表,一个是PrimaryTable作为主桶,一个是RehashTable作为扩容期间用的桶,初始阶段只有PrimaryTable, 36 | 当PrimaryTable中的元素个数大于设定的阈值时,就要开始扩容了。在putEntry()方法中判断size()是否大于threshold,若是则调用doubleCapacity() 37 | 方法申请新的Hash表赋值给RehashTable。渐进式rehash策略由于会逐渐的迁移数据,因此一定会涉及到选桶操作,它需要决定是使用PrimaryTable 38 | 还是使用RehashTable:它首先会根据HashCode按位与PrimaryTable的大小减去1的值,从而计算出应该将当前HashCode分配到PrimaryTable的 39 | 哪个桶中去,如果桶编号大于等于已迁移的桶编号rehashIndex(该桶编号用于标记当前rehash的迁移进度,它之前的数据已经从PrimaryTable迁移到 40 | 了RehashTable的桶中),则应该去PrimaryTable中去查找,否则应该去RehashTable中去查找。每次get()、put()、ContainsKey()、remove() 41 | 操作时,都将会调用computeHashForOperationAndDoIncrementalRehash()方法触发迁移操作,这个方法用于检测是否处于rehash过程中,如果是 42 | 就会调用incrementalRehash()方法迁移一波数据,同时它还会计算key和namespace对应的hashCode。 43 | 44 | 下面就重点分析一下incrementalRehash()方法的实现,该函数会先定义两个StateMapEntry类型的结构用于存放待迁移的元素和迁移后的元素,并记录 45 | 下本次迁移了多少个元素,并且它每次迁移会确保至少迁移了MIN_TRANSFERRED_PER_INCREMENTAL_REHASH(默认值是4)个元素到新桶,它会循环遍历原 46 | Map的第rhIdx个桶,从前往后开始遍历,只要该桶中仍有元素,就需要继续遍历,这样才能保证每次迁移都会将桶中的元素迁移完全,而不是某个桶迁移到一半, 47 | 之所以会需要这样的保证,是因为渐进式reHash的过程中,元素依然需要被访问,而确定访问旧桶或是新桶的办法就是根据桶的编号,因此必须保证桶的迁移是 48 | 完全的,否则就无法确定某个元素应该是去旧桶中会新桶中去找了。如果在迁移过程中遇到版本比highestRequiredSnapshotVersion小的元素,则需要拷 49 | 贝一份,这样通过CopyOnWrite的方式保证元素迁移过程中依然能够被访问,将下一个需要迁移的节点保存,并采用头插法将当前元素迁移到新的table的链表 50 | 头部,链表指针后移以便进行下一个元素的迁移,同时将已迁移元素自增1。如果循环结束表示rhIdx之前的桶已经被迁移完成,如果rhIdx与原待迁移桶总数相 51 | 等则表示已经迁移完成,此时将新的Hash表做为主表并回收原表即可。 52 | 53 | 那么既然StateMap保存的是KeyGroup的状态数据,那么就同样需要对其进行快照,传统的办法就是将其中的全量数据深拷贝一份,然后对拷贝来的这份数据做 54 | 快照,而原数据依然可以堆外提供服务。但是这种办法的效率比较低,如果状态数据较大,那么将会耗费较长的时间,为了提高效率,Flink采用的办法是对其中 55 | 的数据做浅拷贝。什么是浅拷贝呢?就是只拷贝引用,而不拷贝数据。 56 | 57 | 如果StateMap不是正在进行扩容,则其Snapshot的流程比较简单,就是创建一个新的snapshotData,然后直接将primaryTable中的数据拷贝到snapshotData 58 | 中即可。如果StateMap正在进行扩容,Snapshot的流程就相对复杂一点,它需要先创建一个新的snapshotData,然后将primaryTable和rehashTable 59 | 中的数据都拷贝到snapshotData中。 60 | 61 | 那么如果StateMap当前正在进行扩容,则其Snapshot的流程就会相对来说复杂一些。需要先创建一个新的snapshotData,将primaryTable和rehashTable 62 | 的数据都拷贝到snapshotData中,snapshotData数组的长度并不等于primaryTable的长度+rehashTable的长度,而是分别计算primaryTable和rehashTable 63 | 中有几个桶中有数据,然后其桶数量为有数据的桶数量之和。 64 | 65 | 浅拷贝的具体流程如下:首先调用CopyOnWriteStateTable类的stateSnapshot()方法对整个StateTable进行快照,CopyOnWriteStateTable中为每个KeyGroup 66 | 维护了一个StateMap到KeyGroupedStateMaps中,其stateSnapshot()方法会创建CopyOnWriteStateTableSnapshot类的对象,而在CopyOnWriteStateTableSnapshot 67 | 类的构造函数中会调用CopyOnWriteStateTable的getStateMapSnapshotList()方法,该方法会调用所有的CopyOnWriteStateMap的stateSnapshot()方 68 | 法来生成CopyOnWriteStateMapSnapshot并保存到list中。 69 | 70 | -------------------------------------------------------------------------------- /doc/jobsubmit.md: -------------------------------------------------------------------------------- 1 | ### 任务提交 2 | 3 | Flink是目前非常火的一款实时流式计算框架,阿里对其的加持也让它在国内成为最受欢迎的流式计算框架,这一方面是由于阿里双十一对其的应用使其经历过 4 | 海量实时计算的场景考验,另一方面也表明了阿里在国内技术领域的话语权,另一个比较显著的例子是阿里提出的中台概念被众多的互联网公司借鉴,虽然我 5 | 个人觉得除了数据中台等少数几个中台确有意义,其余大部分中台的概念可能都会水土不服,但是Flink这个框架确实有很多值得学习和研究的地方。 6 | 7 | 额外的插一句话,数据中的目的(或者说所有中台的目的)都是让数据(或其它资源)持续的使用起来,通过中台提供的工具、方法和运行机制,将数据(或其它) 8 | 资源变成一种能力,让数据(或其它资源)能更方便的为业务所使用。 9 | 10 | 回到Flink,它实际上是Google Dataflow模型的一种实现,其设计与Dataflow模型高度贴合,感兴趣的话可以去研究一下Dataflow,对于理解Flink的 11 | 设计非常有帮助。目前,Flink为了全面对标Spark从而构建属于自己的生态,加上正在和阿里内部使用的Flink版本也就是Blink进行合并,版本正在进行 12 | 着快速的迭代升级,这在给我们不断带来新功能新特性的同时,也给使用它和分析其源码带来了不小的挑战。 13 | 14 | 但是不管它的组件和代码怎么变,其基本组件模型不会变,任务的提交不会变,所以这里也就先从基本组件和任务的提交开始分析。Flink中的组件图及其交互 15 | 如下图: 16 | ![Flink组件](../images/flinkcomponent.png "Flink组件") 17 | 18 | 需要注意的是,Flink中采用的是单进程多线程的执行方式(这与Spark的多进程的执行方式不太一样),因此,当一个TaskManager上有多个Slot时,多个任 19 | 务会共享同一个JVM的资源,比如TCP连接、心跳信息,甚至共享数据集、数据结构,这会降低每个任务的吞吐率。另外,如果某个task在运行时将整个work的 20 | 内存占满,这个异常的任务可能会把整个TM进程kill掉,这样运行在之上的其他任务也都被kill掉了。当一个TaskManager上只有一个Slot时,每个任务就会 21 | 在单独的JVM里执行,可达到应用程序独立部署、资源隔离的目的,防止异常的应用干扰其他无关的应用。因此,在Flink 1.7以后的版本中,一个TaskManager 22 | 默认只会有一个Slot,如果需要设置多个需要手动修改该配置,从而达到任务间资源隔离的目的。 23 | 24 | 再来看一下Flink任务在被提交到Yarn上后会经过的处理流程,具体如下: 25 | 26 | ![Flink提交到yarn](../images/flinksubmittoyarn.png "Flink提交到yarn") 27 | 28 | 1. Client从客户端代码生成的StreamGraph提取出JobGraph; 29 | 30 | 2. 上传JobGraph和对应的jar包; 31 | 32 | 3. 启动App Master; 33 | 34 | 4. 启动JobManager; 35 | 36 | 5. 启动ResourceManager; 37 | 38 | 6. JobManager向ResourceManager申请slots; 39 | 40 | 7. ResourceManager向Yarn ResourceManager申请Container; 41 | 42 | 8. 启动申请到的Container; 43 | 44 | 9. 启动的Container作为TaskManager向ResourceManager注册; 45 | 46 | 10. ResourceManger向TaskManager请求slot; 47 | 48 | 11. TaskManager提供slot给JobManager,让其分配任务执行. 49 | 50 | 上面的流程主要包含Client,JobManager,ResourceManager,TaskManager共四个部分.接下来就对每个部分进行详细的分析. 51 | 52 | ### 生成StreamGraph 53 | 54 | 在用户编写一个Flink任务之后是怎么样一步步转换成Flink的第一层抽象StreamGraph的呢?本节将会对此进行详细的介绍. 55 | 56 | StreamGraph生成的主要流程如下: 57 | 58 | * 用户对DataStream声明的每个操作都会将该操作对应的Transformation添加到Transformations列表:List 59 | * 用户程序中调用env.execute后(batch调用print方法类似),Flink将从List的Sink开始自底向上进行遍历,这也是为何Flink一定要写Sink的原因,没有Sink就无法生成StreamGraph. 60 | * 如果上游Transformation还没有进行处理,会先对上游的Transformation进行处理,处理即包装成一个StreamNode,再通过Edge建立上下游StreamNode的联系. 61 | * StreamGraphGenerator.generate()方法会最终生成一个完整的StreamGraph 62 | 63 | 其中,addSink的大致流程为:生成Operator -> 生成Transformation -> 加入Transformations中.具体操作如下: 64 | 65 | (1). 对用户函数进行序列化,并转化成Operator 66 | (2). clean进行闭包操作,如使用了哪些外部变量,会对所有字段进行遍历,并将它们的引用存储在闭包中 67 | (3). 完成Operator到SinkTransformation的转换,由DataStream和Operator共同构建一个SinkTransformation 68 | (4). 将SinkTransformation加入到transformations中 69 | 70 | 其实Transformation包含许多种类型,除了上面的SinkTransformation,还有SourceTransformation,OneInputTransformation,TwoInputTransformaion,PartitionTransformaion, 71 | SelectTransformation等等.具体的使用场景如下: 72 | 73 | * PartitionTransformation:如果用户想要对DataStream进行keyby操作,得到一个KeyedStream,即需要对数据重新分区.首先,用户需要设置根据什么key进行 74 | 分区,即KeySelector.然后在生成KeyedStream的过程中,会得到一个PartitionTransformation.在PartitionTransformation中会对这条记录通过key进行计算, 75 | 判断应该发往下游哪个节点,KeyGroup可以由maxParallism进行调整. 76 | * TwoInputTransformaion:指包含两个输入流,如inputStream1和inputStream2,加上这个Transformation的输出,及Operator即可得到一个完整的TwoInputTransformation. 77 | 78 | 以上过程得到了transformations的List,接下来就可以通过StreamGraphGenerator生成完整的StreamGraph. 79 | 生成StreamGraph时会遍历Transformation树,逐个对Transformation进行转化,具体的转化由transform()方法完成.transform最终都会调用transformXXX对 80 | 具体的StreamTransformation进行转换.transformPartition则是创建VirtualNode而不是StreamNode. 81 | 82 | 83 | ### Client 84 | 85 | Client模块的入口为CliFrontend,用于接收和处理各种命令与请求,如Run和Cancel代表运行和取消任务,CliFrontend在收到对应命令后,根据参数来具体执行命令. 86 | Run命令中必须执行Jar和Class,也可指定SavePoint目录来恢复任务. 87 | 88 | Client会根据Jar来提取出Plan,即DataFlow.然后在此Plan的基础上生成JobGraph.其主要操作是对StreamGraph进行优化,将能chain在一起的Operator进行Chain在一起的操作. 89 | 在得到JobGraph后就会提交JobGraph等内容,为任务的运行做准备. 90 | Operator能chain在一起的条件: 91 | 92 | 1. 上下游Operator的并行度一致 93 | 94 | 2. 下游节点的入度为1 95 | 96 | 3. 上下游节点都在同一个SlotSharingGroup中(默认为default) 97 | 98 | 4. 下游节点的chain策略是ALWAYS(可以与上下游链接,map/flatmap/filter等默认是ALWAYS) 99 | 100 | 5. 上游节点的chain策略是ALWAYS或HEAD(只能与下游链接,不能与上游链接,source默认是HEAD) 101 | 102 | 6. 两个Operator间的数据分区方式是fowward 103 | 104 | 7. 用户没有禁用chain -------------------------------------------------------------------------------- /docs/_build/html/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | /* sphinx_rtd_theme version 0.4.3 | MIT license */ 2 | /* Built 20190212 16:02 */ 3 | require=function r(s,a,l){function c(e,n){if(!a[e]){if(!s[e]){var i="function"==typeof require&&require;if(!n&&i)return i(e,!0);if(u)return u(e,!0);var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}var o=a[e]={exports:{}};s[e][0].call(o.exports,function(n){return c(s[e][1][n]||n)},o,o.exports,r,s,a,l)}return a[e].exports}for(var u="function"==typeof require&&require,n=0;n"),i("table.docutils.footnote").wrap("
"),i("table.docutils.citation").wrap("
"),i(".wy-menu-vertical ul").not(".simple").siblings("a").each(function(){var e=i(this);expand=i(''),expand.on("click",function(n){return t.toggleCurrent(e),n.stopPropagation(),!1}),e.prepend(expand)})},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),i=e.find('[href="'+n+'"]');if(0===i.length){var t=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(i=e.find('[href="#'+t.attr("id")+'"]')).length&&(i=e.find('[href="#"]'))}0this.docHeight||(this.navBar.scrollTop(i),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",function(){this.linkScroll=!1})},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current"),e.siblings().find("li.current").removeClass("current"),e.find("> ul li.current").removeClass("current"),e.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:e.exports.ThemeNav,StickyNav:e.exports.ThemeNav}),function(){for(var r=0,n=["ms","moz","webkit","o"],e=0;e 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Index — flink源码阅读 v0.1 documentation 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 93 | 94 |
95 | 96 | 97 | 103 | 104 | 105 |
106 | 107 |
108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 | 127 |
    128 | 129 |
  • Docs »
  • 130 | 131 |
  • Index
  • 132 | 133 | 134 |
  • 135 | 136 | 137 | 138 |
  • 139 | 140 |
141 | 142 | 143 |
144 |
145 |
146 |
147 | 148 | 149 |

Index

150 | 151 |
152 | 153 |
154 | 155 | 156 |
157 | 158 |
159 |
160 | 161 | 162 |
163 | 164 |
165 |

166 | © Copyright 2019, zlzhang0122 167 | 168 |

169 |
170 | Built with Sphinx using a theme provided by Read the Docs. 171 | 172 |
173 | 174 |
175 |
176 | 177 |
178 | 179 |
180 | 181 | 182 | 183 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /docs/_build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Search — flink源码阅读 v0.1 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 93 | 94 |
95 | 96 | 97 | 103 | 104 | 105 |
106 | 107 |
108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 | 127 |
    128 | 129 |
  • Docs »
  • 130 | 131 |
  • Search
  • 132 | 133 | 134 |
  • 135 | 136 | 137 | 138 |
  • 139 | 140 |
141 | 142 | 143 |
144 |
145 |
146 |
147 | 148 | 156 | 157 | 158 |
159 | 160 |
161 | 162 |
163 | 164 |
165 |
166 | 167 | 168 |
169 | 170 |
171 |

172 | © Copyright 2019, zlzhang0122 173 | 174 |

175 |
176 | Built with Sphinx using a theme provided by Read the Docs. 177 | 178 |
179 | 180 |
181 |
182 | 183 |
184 | 185 |
186 | 187 | 188 | 189 | 194 | 195 | 196 | 197 | 198 | 199 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /docs/_build/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Flink源码阅读文档! — flink源码阅读 v0.1 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 92 | 93 |
94 | 95 | 96 | 102 | 103 | 104 |
105 | 106 |
107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 |
125 | 126 |
    127 | 128 |
  • Docs »
  • 129 | 130 |
  • Flink源码阅读文档!
  • 131 | 132 | 133 |
  • 134 | 135 | 136 | View page source 137 | 138 | 139 |
  • 140 | 141 |
142 | 143 | 144 |
145 |
146 |
147 |
148 | 149 | 162 | 163 | 164 |
165 | 166 |
167 |
168 | 169 | 175 | 176 | 177 |
178 | 179 |
180 |

181 | © Copyright 2019, zlzhang0122 182 | 183 |

184 |
185 | Built with Sphinx using a theme provided by Read the Docs. 186 | 187 |
188 | 189 |
190 |
191 | 192 |
193 | 194 |
195 | 196 | 197 | 198 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /doc/flinkkafkaconsumer.md: -------------------------------------------------------------------------------- 1 | ### FlinkKafkaConsumer 2 | 3 | FlinkKafkaConsumer是一个流式数据源,用于从Apache Kafka中获取并行数据流。其继承结构如下图所示: 4 | ![FlinkKafkaConsumer继承体系](../images/flinkkafka.png "FlinkKafkaConsumer继承体系") 5 | 6 | 从上图可以看到,FlinkKafkaConsumer继承于FlinkKafkaConsumerBase类,而FlinkKafkaConsumerBase类又实现了RichFunction接口和SourceFunction 7 | 接口(在Flink 1.11版本中进行了重构,实现的是ParallelSourceFunction接口)。由于实现了RichFunction接口,所以我们可以分析下其open()方法和run() 8 | 方法。 9 | 10 | open()方法的实现在FlinkKafkaConsumerBase类中,主要是FlinkKafkaConsumer的初始化逻辑。 11 | 首先设置offset的提交模式,OffsetCommitMode是一个枚举类型,有以下三个取值: 12 | * DISABLED:完全禁用offset的提交; 13 | * ON_CHECKPOINTS:仅在checkpoint完成时提交offset; 14 | * KAFKA_PERIODIC:周期性提交,使用kafka客户端内部的自动提交功能; 15 | 具体判断OffsetCommitMode的逻辑被封装在OffsetCommitModes.fromConfiguration()方法中,该方法会先判断是否启用checkpoint,如果启用且同时启用 16 | 了checkpoint完成时提交offset,则返回ON_CHECKPOINTS;如果未启用checkpoint,同时启用了自动提交则返回KAFKA_PERIODIC,否则在 17 | 其他情况下都返回DISABLED。 18 | 19 | 接着便是创建和启动分区发现工具。createPartitionDiscoverer()方法创建了一个AbstractPartitionDiscoverer类的实例partitionDiscoverer,主要 20 | 用于kafka分区的发现,其中的参数topicsDescriptor描述了consumer根据什么样的规则订阅kafka的topic,有两种规则:一种是topic名称的固定列表fixedTopics, 21 | 另一种是匹配topic名称的正则表达式topicPattern。partitionDiscoverer.open()方法打开了kafka分区发现,并初始化所有需要的kafka连接。 22 | 23 | 初始化subscribedPartitionsToStartOffsets已订阅的分区列表,它被初始化为一个hashmap。 24 | partitionDiscoverer.discoverPartitions()方法用于获取所有fixedTopics和匹配topicPattern的topic包含的所有分区信息。 25 | 26 | 接下来的逻辑分为两个部分,如果consumer是从快照恢复的,则走快照恢复逻辑,否则走直接启动逻辑。 27 | 先分析从快照恢复的逻辑,既然是从快照恢复的,那么restoredState肯定不为空,否则就会为空。如果restoredState中没有某个分区的状态,那么将直接从最早 28 | 的位点开始消费(这个逻辑是写死的,一定要注意)。 29 | 然后对subscribedPartitionsToStartOffsets赋值,它会过滤掉不归该task负责的kafka分区后,将剩余的分区和位点信息放入已订阅的分区列表。此处,注意 30 | 计算的方式:先根据分区所属的topic计算hash值,再乘以31,然后按位与0x7FFFFFFF以保证得到的结果是一个质数(按位与是得到正整数最安全的方法,原因可以参看 31 | [Flink扩展阅读5:问题实战](./thinking.md),里面又详细的介绍和分析),再将结果用算子的并行度取模,再将得到的结果加上分区所属的分区编号后再次用算子 32 | 的并行度取模,得到的值就是将要消费该kafka分区的subtask的编号(这样是为了同一个topic的partition尽量连续地分配给同一个subtask)。然后,判断是否需 33 | 要依照分区发现配置的topic正则表达式过滤分区,如果是的话就会过滤掉topic名称不符合topicPattern的分区。 34 | 35 | 如果consumer不是从快照恢复的,那么restoredState就会为空,consumer就会直接启动,会根据startupMode启动模式走不同的启动逻辑。它也是一个枚举类 36 | 型,有五个枚举值: 37 | * GROUP_OFFSETS:从保存在zookeeper或者是Kafka broker的对应消费者组提交的offset开始消费,这是默认的配置; 38 | * EARLIEST:尽可能从最早的offset开始消费; 39 | * LATEST:从最近的offset开始消费; 40 | * TIMESTAMP:从用户提供的timestamp处开始消费; 41 | * SPECIFIC_OFFSETS:从用户提供的offset处开始消费。 42 | 43 | consumer使用分区发现获取初始分区后,根据StartupMode来设置消费的起始offset。先来看SPECIFIC_OFFSETS的情况,在此种情况下,如果没有配置具体的 44 | 消费位点,将会直接抛出异常。否则,获取每个分区指定的消费起始offset,如果该分区设置了消费起始的offset,则从设置的offset开始消费,否则从消费者所 45 | 属的消费组的位点开始消费。如果采用TIMESTAMP模式,则会在没有配置消费起始timestamp时抛出异常。否则根据timestamp的值获取到对应的offset,并判断 46 | 获取到的offset是否为空,如果不为空就从offset开始消费,否则就从最近的offset开始消费。 47 | 48 | 再来看run()方法,其实现同样是在FlinkKafkaConsumerBase类中,其主要负责从KafkaConsumer消费数据以及向下游发送数据。具体逻辑是:首先检查在open() 49 | 方法中已经初始化和赋值的subscribedPartitionsToStartOffsets是否为null,如果为null则抛出异常,然后设置成功提交位点和失败提交位点的计数器,及获 50 | 取子任务的index索引,并注册提交位点时的回调函数,该函数主要是在成功提交位点时将成功的计数器加1,在提交位点失败时将失败的计数器加1。接下来对subscribedPartitionsToStartOffsets 51 | 集合进行判断是否为空,如果为空则标记数据源状态为空闲(此时会向下游发送一个最大值的时间戳作为水位线)。在获取数据时,会创建一个KafkaFetcher,它会借助 52 | KafkaConsumer API来从Kafka Broker拉取数据。然后会检测running变量的值,如果不是running状态则会直接返回。最后它会根据配置的分区发现的时间间隔 53 | 来确定是否启动分区定时发现任务。如果禁用了分区发现则会直接启动获取数据的任务,否则会调用runWithPartitionDiscovery()方法启动分区发现任务和获取数 54 | 据的任务。 55 | 56 | runWithPartitionDiscovery()方法比较简单,它会直接调用createAndStartDiscoveryLoop()方法启动分区发现的定时任务,然后启动Kafka Broker数据 57 | 获取的任务,然后它会唤醒分区发现任务使得循环能够退出,最后等待分区发现任务执行完毕。createAndStartDiscoveryLoop()方法创建了一个分区发现的线程, 58 | 该线程的内部是一个循环,它会尝试发现新的分区,如果方法抛出异常则退出循环。如果数据源已经关闭,或是没有发现新的分区,就没必要再添加新的分区。最后启动 59 | 该分区发现的定时任务线程。其中最关键的还是partitionDiscoverer.discoverPartitions()方法,该方法就是发现分区的执行过程,它会首先确保数据源没有 60 | 被关闭,也没有被wakeup,然后根据配置来获取分区。如果配置的是fixed topics,则会获取所有这些topic的分区。否则如果配置的是topicPattern则会先获取 61 | 所有的topic分区,然后会排除掉那些名字与topicPattern不匹配的topic。如果剩余的匹配的topic数目不为0,则获取这些topic的分区,否则就将新发现的分区 62 | 设置为null。如果新发现的分区为null,就配出RuntimeException,否则就对新发现的分区进行循环,将它们存入discoveredPartitions集合,并判断该分区 63 | 是否归当前task消费,如果不是就会将其从新发现的分区中移除,最终返回的就是归属于当前task消费的新发现的分区。 64 | 65 | runFetchLoop()方法是获取数据的主入口,它通过一个循环来不断获取Kafka Broker的数据。具体逻辑是:Handover负责在KafkaConsumerThread和KafkaFetcher 66 | 之间传递数据,然后启动Kafka消费线程,定期从KafkaConsumer拉取数据并转交给handover对象。启动一个循环,该循环从handover中获取数据,如果此时KafkaConsumerThread 67 | 暂时尚未将数据转交给handover,则该方法会被阻塞。然后遍历每一个Kafka分区,获取属于该分区的记录,然后将Kafka记录反序列化为bean,如果数据已到流的末 68 | 尾则会停止拉取数据,最后调用emitRecord()方法将反序列化后的数据发送出去,更新位点,并生成timestamp和watermark。emitRecord()方法的逻辑非常简单, 69 | 它直接调用了emitRecordWithTimestamp()方法。该方法首先判断如果记录不为null,并且不需要发送timestamp和watermark,则在对checkpointLock进行 70 | 同步的情况下,调用SourceFunction中SourceContext的collectWithTimestamp()方法将数据发送出去,这个及其后续在[Flink源码阅读16:Flink数据通信](./conn.md) 71 | 中已做过细致的分析。如果需要周期性的发送watermark,则会调用emitRecordWithTimestampAndPeriodicWatermark()方法,这个方法会使用extractTimestamp() 72 | 方法获取timestamp,然后同样调用SourceContext的collectWithTimestamp()方法将数据发送出去,同时更新分区的位点。如果是PunctuatedWatermark, 73 | 则会调用emitRecordWithTimestampAndPunctuatedWatermark(),这个方法会获取timestamp和watermark,它比上一个方法多的就是会获取watermark并 74 | 将watermark发送出去,后续逻辑基本一致。否则,如果记录为空,就只更新分区的位点即可。 75 | 76 | 最后来看下KafkaConsumerThread,它负责在单独的线程中从Kafka中拉取数据到handover。具体逻辑是:如果不是正处于commit的过程中,则获取需要提交的位点 77 | 的值,以及位点提交的回调函数,并在获取完毕后设置其为null以防重复提交。如果获取到的不为null,则调用consumer.commitAsync()开启异步提交位点。然后, 78 | 由于周期性分区发现功能的存在,需要为consumer指定新发现的分区,否则拉取数据时将会报错。最后从consumer拉取数据,拉取时会指定一个pollTimeout,它的 79 | 默认值是100毫秒,可以通过flink.poll-timeout参数进行调整,在这个超时范围内能拉取多少数据就拉取多少数据,最后将数据转交给handover。整个过程大致就 80 | 是这样。 81 | 82 | 最后来分析下checkpoint的大致流程,其主要涉及的是状态的读写,主要分为两步。一是snapshotState过程,代码位于在FlinkKafkaConsumerBase的snapshotState() 83 | 方法中,大致逻辑是:如果KafkaFetcher尚未初始化完毕,则需要保存已订阅的topic以及它们的初始位点。否则,调用fetcher的snapshotCurrentState()方法。如果 84 | offsetCommitMode为ON_CHECKPOINTS类型,还需要将topic和offset写入到pendingOffsetsToCommit集合中。该集合用于checkpoint成功的时候向Kafka broker 85 | 提交offset。当offsetCommitMode不是ON_CHECKPOINTS和DISABLED的时候,将使用自动提交位点的模式。二是notifyCheckpointComplete过程,当所有的operator 86 | 都已经完成快照时,就会向JobManager的CheckpointCoordinator发送确认消息,然后coordinator会通知各个operator checkpoint已经完成。为了保证保证 87 | 数据不会被遗漏和重复消费,ON_CHECKPOINTS模式运行的FlinkKafkaConsumer只能在这个时候提交offset到kafka consumer。因此,在notifyCheckpointComplete 88 | 时通知Kafka Consumer,将checkpoint时保存的各个分区的位点提交给kafka broker,从而保证了数据的一致性。 89 | 90 | -------------------------------------------------------------------------------- /docs/_build/latex/flink.tex: -------------------------------------------------------------------------------- 1 | %% Generated by Sphinx. 2 | \def\sphinxdocclass{report} 3 | \documentclass[letterpaper,10pt,english]{sphinxmanual} 4 | \ifdefined\pdfpxdimen 5 | \let\sphinxpxdimen\pdfpxdimen\else\newdimen\sphinxpxdimen 6 | \fi \sphinxpxdimen=.75bp\relax 7 | 8 | \PassOptionsToPackage{warn}{textcomp} 9 | \usepackage[utf8]{inputenc} 10 | \ifdefined\DeclareUnicodeCharacter 11 | % support both utf8 and utf8x syntaxes 12 | \ifdefined\DeclareUnicodeCharacterAsOptional 13 | \def\sphinxDUC#1{\DeclareUnicodeCharacter{"#1}} 14 | \else 15 | \let\sphinxDUC\DeclareUnicodeCharacter 16 | \fi 17 | \sphinxDUC{00A0}{\nobreakspace} 18 | \sphinxDUC{2500}{\sphinxunichar{2500}} 19 | \sphinxDUC{2502}{\sphinxunichar{2502}} 20 | \sphinxDUC{2514}{\sphinxunichar{2514}} 21 | \sphinxDUC{251C}{\sphinxunichar{251C}} 22 | \sphinxDUC{2572}{\textbackslash} 23 | \fi 24 | \usepackage{cmap} 25 | \usepackage[T1]{fontenc} 26 | \usepackage{amsmath,amssymb,amstext} 27 | \usepackage{babel} 28 | 29 | 30 | 31 | \usepackage{times} 32 | \expandafter\ifx\csname T@LGR\endcsname\relax 33 | \else 34 | % LGR was declared as font encoding 35 | \substitutefont{LGR}{\rmdefault}{cmr} 36 | \substitutefont{LGR}{\sfdefault}{cmss} 37 | \substitutefont{LGR}{\ttdefault}{cmtt} 38 | \fi 39 | \expandafter\ifx\csname T@X2\endcsname\relax 40 | \expandafter\ifx\csname T@T2A\endcsname\relax 41 | \else 42 | % T2A was declared as font encoding 43 | \substitutefont{T2A}{\rmdefault}{cmr} 44 | \substitutefont{T2A}{\sfdefault}{cmss} 45 | \substitutefont{T2A}{\ttdefault}{cmtt} 46 | \fi 47 | \else 48 | % X2 was declared as font encoding 49 | \substitutefont{X2}{\rmdefault}{cmr} 50 | \substitutefont{X2}{\sfdefault}{cmss} 51 | \substitutefont{X2}{\ttdefault}{cmtt} 52 | \fi 53 | 54 | 55 | \usepackage[Bjarne]{fncychap} 56 | \usepackage{sphinx} 57 | 58 | \fvset{fontsize=\small} 59 | \usepackage{geometry} 60 | 61 | % Include hyperref last. 62 | \usepackage{hyperref} 63 | % Fix anchor placement for figures with captions. 64 | \usepackage{hypcap}% it must be loaded after hyperref. 65 | % Set up styles of URL: it should be placed after hyperref. 66 | \urlstyle{same} 67 | \addto\captionsenglish{\renewcommand{\contentsname}{Contents:}} 68 | 69 | \usepackage{sphinxmessages} 70 | \setcounter{tocdepth}{1} 71 | 72 | 73 | 74 | \title{flink源码阅读} 75 | \date{Oct 25, 2019} 76 | \release{v0.1} 77 | \author{zlzhang0122} 78 | \newcommand{\sphinxlogo}{\vbox{}} 79 | \renewcommand{\releasename}{Release} 80 | \makeindex 81 | \begin{document} 82 | 83 | \pagestyle{empty} 84 | \sphinxmaketitle 85 | \pagestyle{plain} 86 | \sphinxtableofcontents 87 | \pagestyle{normal} 88 | \phantomsection\label{\detokenize{index::doc}} 89 | 90 | 91 | 92 | \chapter{This is a Title} 93 | \label{\detokenize{example:this-is-a-title}}\label{\detokenize{example::doc}} 94 | That has a paragraph about a main subject and is set when the ‘=’ 95 | is at least the same length of the title itself. 96 | 97 | 98 | \section{Subject Subtitle} 99 | \label{\detokenize{example:subject-subtitle}} 100 | Subtitles are set with ‘-‘ and are required to have the same length 101 | of the subtitle itself, just like titles. 102 | 103 | Lists can be unnumbered like: 104 | \begin{itemize} 105 | \item {} 106 | Item Foo 107 | 108 | \item {} 109 | Item Bar 110 | 111 | \end{itemize} 112 | 113 | Or automatically numbered: 114 | \begin{enumerate} 115 | \sphinxsetlistlabels{\arabic}{enumi}{enumii}{}{.}% 116 | \item {} 117 | Item 1 118 | 119 | \item {} 120 | Item 2 121 | 122 | \end{enumerate} 123 | 124 | 125 | \section{Inline Markup} 126 | \label{\detokenize{example:inline-markup}} 127 | Words can have \sphinxstyleemphasis{emphasis in italics} or be \sphinxstylestrong{bold} and you can define 128 | code samples with back quotes, like when you talk about a command: \sphinxcode{\sphinxupquote{sudo}} 129 | gives you super user powers! 130 | 131 | 132 | \chapter{任务提交} 133 | \label{\detokenize{jobSubmit:id1}}\label{\detokenize{jobSubmit::doc}} 134 | Flink任务在被提交到Yarn上后会经过如下流程,具体如下: 135 | \begin{quote} 136 | 137 | \noindent\sphinxincludegraphics{{flink-submit-to-yarn}.png} 138 | \begin{enumerate} 139 | \sphinxsetlistlabels{\arabic}{enumi}{enumii}{}{.}% 140 | \item {} 141 | Client从客户端代码生成的StreamGraph提取出JobGraph; 142 | 143 | \item {} 144 | 上传JobGraph和对应的jar包; 145 | 146 | \item {} 147 | 启动App Master; 148 | 149 | \item {} 150 | 启动JobManager; 151 | 152 | \item {} 153 | 启动ResourceManager; 154 | 155 | \item {} 156 | JobManager向ResourceManager申请slots; 157 | 158 | \item {} 159 | ResourceManager向Yarn ResourceManager申请Container; 160 | 161 | \item {} 162 | 启动申请到的Container; 163 | 164 | \item {} 165 | 启动的Container作为TaskManager向ResourceManager注册; 166 | 167 | \item {} 168 | ResourceManger向TaskManager请求slot; 169 | 170 | \item {} 171 | TaskManager提供slot给JobManager,让其分配任务执行. 172 | 173 | \end{enumerate} 174 | \end{quote} 175 | 176 | 177 | \section{任务提交源码解析} 178 | \label{\detokenize{jobSubmit:id2}} 179 | Subtitles are set with '-' and are required to have the same length 180 | of the subtitle itself, just like titles. 181 | 182 | Lists can be unnumbered like: 183 | \begin{quote} 184 | \begin{itemize} 185 | \item {} 186 | Item Foo 187 | 188 | \item {} 189 | Item Bar 190 | 191 | \end{itemize} 192 | 193 | \noindent\sphinxincludegraphics{{flink-submit-to-yarn}.png} 194 | \end{quote} 195 | 196 | Or automatically numbered: 197 | \begin{enumerate} 198 | \sphinxsetlistlabels{\arabic}{enumi}{enumii}{}{.}% 199 | \item {} 200 | Item 1 201 | 202 | \item {} 203 | Item 2 204 | 205 | \end{enumerate} 206 | 207 | 208 | \section{Inline Markup} 209 | \label{\detokenize{jobSubmit:inline-markup}} 210 | Words can have \sphinxstyleemphasis{emphasis in italics} or be \sphinxstylestrong{bold} and you can define 211 | code samples with back quotes, like when you talk about a command: \sphinxcode{\sphinxupquote{sudo}} 212 | gives you super user powers! 213 | 214 | 215 | \chapter{Indices and tables} 216 | \label{\detokenize{index:indices-and-tables}}\begin{itemize} 217 | \item {} 218 | \DUrole{xref,std,std-ref}{genindex} 219 | 220 | \item {} 221 | \DUrole{xref,std,std-ref}{modindex} 222 | 223 | \item {} 224 | \DUrole{xref,std,std-ref}{search} 225 | 226 | \end{itemize} 227 | 228 | 229 | 230 | \renewcommand{\indexname}{Index} 231 | \printindex 232 | \end{document} -------------------------------------------------------------------------------- /docs/_build/html/contents.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Flink源码阅读文档! — flink源码阅读 v0.1 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 93 | 94 |
95 | 96 | 97 | 103 | 104 | 105 |
106 | 107 |
108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 | 127 |
    128 | 129 |
  • Docs »
  • 130 | 131 |
  • Flink源码阅读文档!
  • 132 | 133 | 134 |
  • 135 | 136 | 137 | View page source 138 | 139 | 140 |
  • 141 | 142 |
143 | 144 | 145 |
146 |
147 |
148 |
149 | 150 | 168 | 169 | 170 |
171 | 172 |
173 |
174 | 175 | 181 | 182 | 183 |
184 | 185 |
186 |

187 | © Copyright 2019, zlzhang0122 188 | 189 |

190 |
191 | Built with Sphinx using a theme provided by Read the Docs. 192 | 193 |
194 | 195 |
196 |
197 | 198 |
199 | 200 |
201 | 202 | 203 | 204 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /docs/_build/html/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | This is a Title — flink源码阅读 v0.1 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 | 92 | 93 |
94 | 95 | 96 | 102 | 103 | 104 |
105 | 106 |
107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 |
125 | 126 |
    127 | 128 |
  • Docs »
  • 129 | 130 |
  • This is a Title
  • 131 | 132 | 133 |
  • 134 | 135 | 136 | View page source 137 | 138 | 139 |
  • 140 | 141 |
142 | 143 | 144 |
145 |
146 |
147 |
148 | 149 |
150 |

This is a Title

151 |

That has a paragraph about a main subject and is set when the ‘=’ 152 | is at least the same length of the title itself.

153 |
154 |

Subject Subtitle

155 |

Subtitles are set with ‘-‘ and are required to have the same length 156 | of the subtitle itself, just like titles.

157 |

Lists can be unnumbered like:

158 |
159 |
    160 |
  • Item Foo

  • 161 |
  • Item Bar

  • 162 |
163 |
164 |

Or automatically numbered:

165 |
166 |
    167 |
  1. Item 1

  2. 168 |
  3. Item 2

  4. 169 |
170 |
171 |
172 |
173 |

Inline Markup

174 |

Words can have emphasis in italics or be bold and you can define 175 | code samples with back quotes, like when you talk about a command: sudo 176 | gives you super user powers!

177 |
178 |
179 |
180 |

Indices and tables

181 | 186 |
187 | 188 | 189 |
190 | 191 |
192 |
193 | 194 | 195 |
196 | 197 |
198 |

199 | © Copyright 2019, zlzhang0122 200 | 201 |

202 |
203 | Built with Sphinx using a theme provided by Read the Docs. 204 | 205 |
206 | 207 |
208 |
209 | 210 |
211 | 212 |
213 | 214 | 215 | 216 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /doc/chandylamport.md: -------------------------------------------------------------------------------- 1 | ### 分布式快照 2 | 3 | 所谓分布式快照,就是在特定时间点记录下来的分布式系统的全局状态,这个全局状态既包含所有进程的状态也包括链路的状态,主要用于分布式系统的故障 4 | 恢复、死锁检测和垃圾收集等。由于链路本身只负责传递消息,其状态不容易被记录,所以很难在同一瞬间捕捉所有进程和链路的状态,而Chandy-Lamport 5 | 算法则通过每个进程记录与自己相关的状态,并最终合并出全局状态来达到了同样的效果。 6 | 7 | 该算法可以分成3个阶段,下面分别进行描述: 8 | * 初始化快照:快照发起进程记录自己的状态,并通过其所有的流出链路向其它进程发送特殊标记信息,同时通过所有流入链路监听流入信息; 9 | 10 | * 扩散快照:对于所有的进程(包括快照发起进程),如果在其某个流入链路收到了特殊标记信息。如果该进程还没有记录过自己的状态,则记录自己的状态, 11 | 并通过所有流出链路向其它进程发送特殊标记信息,通过所有流入链路监听流入信息;如果该进程已经记录过自己的状态,则记录流入链路上监听到的信息, 12 | 直到收到特殊标记为止; 13 | 14 | * 完成快照:如果所有进程都收到了特殊标记信息,并记录下了自己的状态,也记录下了流入链路信息,表示快照已经成功完成。将这些状态传输到稳定的 15 | 存储位置即可。 16 | 17 | 这就是Chandy-Lamport分布式快照的实现原理。 18 | 19 | Flink以该算法为基础,实现了异步屏障快照(ABS)算法。简单的说,Flink的JobManager会周期性的向每个SourceTask发送一条包含一个新checkpointId 20 | 的消息,间隔时间由配置env.enableCheckpointing(间隔时间毫秒)控制,以初始化一个checkpoint。当SourceTask收到这条消息时就会停止向下游发送 21 | 消息,广播一种特殊的记录checkpoint barrier(作用类似于Chandy-Lamport算法中的特殊标记信息),并在StateBackend生成一个包含本地状态的checkpoint。 22 | 23 | CheckpointBarrier类有三个成员变量: 24 | * id:它与checkpointId对应,并保持严格递增,因此值越大表明checkpoint越新;如果是standalone模式则是原子变量,否则如果是HA模式则使用 25 | zookeeper分布式集群Curator的分布式整型int计数器SharedCount来确保跨JobManager的严格递增; 26 | 27 | * timestamp:记录checkpoint barrier产生的时间,ScheduledTrigger这个线程的run方法调用triggerCheckpoint触发checkpoint时传入的是 28 | 系统当前的时间,并将其作为checkpoint的timestamp的值; 29 | 30 | * checkpointOptions:进行checkpoint操作时的选项,包括checkpoint的类型及保存位置的设置; 31 | 32 | 我们来看一下整个source端快照触发的过程,一个非常长的调用链... 33 | 34 | CheckpointCoordinator类用于协调算子和状态的分布式快照的逻辑,其会启动一个定时器定时调用ScheduledTrigger的run()方法,这个run()方法内会 35 | 调用triggerCheckpoint()方法,它会调用startTriggeringCheckpoint()方法,在其中会先进行预检查,比如检查最大并发的Checkpoint数,最小的Checkpoint 36 | 之间的时间间隔。默认情况下,最大并发的Checkpoint数为1,最小的Checkpoint之间的时间间隔为0。判断所有Source算子的Subtask是否都处于运行状态, 37 | 若否则直接报错。同时检查所有待确认的算子的SubTask(Execution)是否是运行状态,若否则直接报错。创建PendingCheckpoint,同时为该次Checkpoint创建 38 | 一个Runnable,即超时取消线程,默认Checkpoint十分钟超时。循环遍历所有Source算子的Subtask,调用snapshotTaskState()方法,这个方法会根据 39 | 是同步或异步配置而触发同步或异步的checkpoint。但不管是同步或异步,最终都会调用Execution的triggerCheckpointHelper()方法,只不过传递 40 | 的最后一个参数有所不同,在这个方法中,它会构造一个TaskManager的网关并调用其triggerCheckpoint()方法触发checkpoint,TaskManagerGateway 41 | 类是一个接口,它只有一个实现也就是RpcTaskManagerGateway,它的triggerCheckpoint()方法会向taskExecutorGateway的triggerCheckpoint() 42 | 方法发送一个RPC的请求。TaskExecutorGateway也是一个接口,它也只有一个实现就是TaskExecutor,在这个类的triggerCheckpoint()方法中,它会 43 | 找出具体需要触发checkpoint的那个Task,调用其triggerCheckpointBarrier()方法,这个方法中会根据真正的Task类型调用其triggerCheckpointAsync() 44 | 方法,如果这个Task是SourceStreamTask,它就会调用到基类StreamTask的triggerCheckpointAsync()方法,向其执行线程提交一个triggerCheckpoint() 45 | 请求触发异步调用,由于是在Source端,所以在checkpoint时并不需要进行对齐,而是直接触发StreamTask的performCheckpoint()方法的调用,这个方法 46 | 会通过actionExecutor来分三步调用:第一步调用operatorChain的prepareSnapshotPreBarrier()方法来做一些checkpoint前的准备工作,第二部调用 47 | operatorChain的broadcastCheckpointBarrier()将自己收到的CheckpointBarrier向下游传播,第三步调用checkpointState()方法开始进行自己的 48 | 异步checkpoint。 49 | 50 | 在上面的checkpointState()方法中,会根据checkpointId和设置的checkpoint位置存放信息来构建一个checkpoint output流的工厂CheckpointStreamFactory。 51 | 这个工厂被用于checkpoint持久化数据,它会创建一个CheckpointStateOutputStream,这个Stream对应一个FSDataOutputStream,如果是使用FsStateBackend 52 | 或是RocksDBStateBackend作为状态后端,FSDataOutputStream对应的就是分布式文件系统的输入流实例(此处的输出就是分布式文件系统的输入),因此 53 | 它会将本地文件写往分布式文件系统,完成后调用closeAndGetHandle()方法关闭这个输出流,生成StreamStateHandle,这是个文件句柄,将这个句柄发 54 | 给JM,将来能够通过这个句柄的openInputStream()读取状态数据。扯的有点远了。。。继续吧,在创建完成这个checkpoint流工厂的创建后,会调用内部类 55 | CheckpointingOperation的executeCheckpointing()方法,在这个方法中会调用checkpointStreamOperator()方法对所有的算子进行快照(不管是否 56 | chain在一起,如果chain在一起则分开进行快照)。在checkpointStreamOperator()方法调用了每个算子的snapshotState()方法,并调用到AbstractStreamOperator 57 | 类的snapshotState()方法,这是个通用实现,在这个方法中,先调用了snapshotState(snapshotContext)方法,这个方法主要实现了Raw State的快照 58 | (Raw State存放的是原始状态的快照,Flink对这种状态的数据结构一无所知,只有在用户自定义的operator中会使用到,一般不用)。紧接着,它分别调用 59 | OperatorStateBackend和KeyedStateBackend的snapshot方法对Operator State和Keyed State进行快照。 60 | 61 | 先来分析下Operator State的快照吧,在operatorStateBackend.snapshot()方法会对应SnapshotStrategy的snapshot()方法调用,这是个接口,我们 62 | 来看一下DefaultOperatorStateBackendSnapshotStrategy类对其的实现,在DefaultOperatorStateBackendSnapshotStrategy类的snapshot() 63 | 方法中,它对List State和Broadcast State进行了深拷贝,然后异步调用streamFactory.createCheckpointStateOutputStream()方法获取checkpoint 64 | 输出流,并将之前深拷贝得到的List State和Broadcast State数据写入checkpoint文件中,最后创建StreamStateHandle,至此就完成了异步写入checkpoint 65 | 的操作。 66 | 67 | 文字来看可能不太清晰,来张图看下: 68 | ![Checkpoint](../images/checkpoint.jpeg "Checkpoint") 69 | 70 | 算子Operator会用checkpoint barrier来对流进行划分,在它之前的数据被划分到checkpoint中,而在其之后的数据被划分到之后的checkpoint中。当 71 | StateBackend已经完成checkpoint时会提醒Task,Task会发送确认消息到JobManager的CheckpointCoordinator确认该检查点。 72 | 73 | InputProcessorUtil类中有一个非常重要的方法createCheckpointBarrierHandler(),它根据设置的一致性语义,创建对应的checkpoint barrier处理 74 | 器。如果设置的是EXACTLY_ONCE语义,则使用CheckpointBarrierAligner类进行处理(在Flink 1.11中会判断是否使用非对齐checkpoint,如果使用则会做 75 | 非对齐checkpoint),此时当下游收到一个checkpoint barrier时,就会暂停处理这个checkpoint barrier所在通道的后续数据,并开始进行对齐,也就是将 76 | 这些数据存到缓冲区,直到其他所有相同id的checkpoint barrier都已经到达,就会对状态进行checkpoint,并广播checkpoint barrier到下游。直到所有 77 | checkpoint barrier被广播到下游,才开始处理排队在缓冲区的数据。AT_LEAST_ONCE使用的是CheckpointBarrierTracker。如果设置的是at least once 78 | 语义,则不会进行对齐,它只会在所有的checkpoint barrier都到达后进行checkpoint,至于先期到达的数据会继续向下游进行流动,因此当发生故障时数据可能 79 | 会出现重复处理的情况。当然了,如果operator有多个数据输入才会有对齐问题,如果只有一个输入源是不会有这个问题的。同时也不难发现,对齐会降低Task处理数 80 | 据的能力,影响系统吞吐量。因此,exactly once语义的吞吐量会较at least once要低(有得必有失嘛,不然at least once就没有存在的必要了)。 81 | 82 | 从代码上看就是,除了SourceStreamTask,其余的OneInputStreamTask、TwoInputStreamTask和MultipleInputStreamTask中都在其init初始化方法 83 | 中调用了InputProcessorUtil.createCheckpointedInputGate()方法或InputProcessorUtil.createCheckpointedInputGatePair()方法中调用 84 | createCheckpointBarrierHandler来创建对应的checkpoint barrier handler处理器,这个处理器会判断checkpoint的模式,如果是Exactly once语义 85 | 则使用CheckpointBarrierAligner,如果是at least once则使用CheckpointBarrierTracker。这两个类均实现了CheckpointBarrierHandler接口, 86 | 主要负责checkpoint barrier到来时候的对齐处理逻辑。同时,我们也可以确定,Flink只支持at least once语义和exactly once语义,不支持storm 87 | 曾经支持的at most语义。 88 | 89 | CheckpointBarrierAligner类实现了exactly once语义下的barrier对齐的逻辑,具体的对齐逻辑位于该类的processBarrier方法中。主要流程如下图所示: 90 | ![Barrier对齐](../images/exactlyonce.png "Barrier对齐") 91 | 92 | 而CheckpointBarrierTracker类实现了at least once语义下的barrier处理逻辑,具体的处理逻辑同样位于processBarrier方法中。主要流程如下图所示: 93 | ![Barrier处理](../images/atleastonce.png "Barrier处理") 94 | 95 | CheckpointBarrierHandler的processBarrier方法是在CheckpointedInputGate类中的pollNext()方法调用,而CheckpointedInputGate是InputGate 96 | 的一个包装类,除了负责读取上游节点的数据外,也会对接收到的checkpoint barrier做出响应。也就是如果需要读取数据的channel被barrierHandler阻塞, 97 | 那么这个channel到来的数据会缓存在bufferStorage中,直到该通道取消阻塞,而从该InputGate读取数据的时候会优先读取bufferStorage中的数据。如果 98 | 没有才会从channel读取数据。 99 | 100 | 最终checkpoint barrier会到达SinkTask,SinkTask同样会根据语义进行对齐(这与其它的中间operator的处理类似),当它收到全部的checkpoint barrier 101 | 时会给自己的状态做checkpoint并在完成后向JobManager发送确认其已经完成checkpoint。当JobManager收到该应用的所有Sink发送的确认信息后(以及所有 102 | 有状态的算子所发送的确认信息),表明本次checkpoint顺利完成,这个checkpoint已经时一个完整的checkpoint,可以用于故障恢复了。此时,JobManager也 103 | 会向所有的operator通知checkpoint操作的完成,operator接收到这个通知后可以做一些额外的逻辑,比如在上一篇所讲2PC,向kafka提交事务。 104 | 105 | 当operator的状态很大时,复制整个状态并发送给远程状态存储会很费时,Flink对此进行了优化,它会进行异步快照,即现将状态保存到本地,在本地快照完成后 106 | ,Flink的Task会恢复处理数据,同时后台异步线程会将本地快照保存到远端存储。此外,RocksDBStateBackend能够支持增量异步快照,以减少数据的传输。 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/_build/html/dataDeliver.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 数据传递 — flink源码阅读 v0.1 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 97 | 98 |
99 | 100 | 101 | 107 | 108 | 109 |
110 | 111 |
112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 |
130 | 131 |
    132 | 133 |
  • Docs »
  • 134 | 135 |
  • 数据传递
  • 136 | 137 | 138 |
  • 139 | 140 | 141 | View page source 142 | 143 | 144 |
  • 145 | 146 |
147 | 148 | 149 |
150 |
151 |
152 |
153 | 154 |
155 |

数据传递

156 |

本节主要介绍数据在各个节点之间是如何进行传递的,以及如何在数据传递的过程中进行自然的反压.

157 |
158 |

数据传递流程

159 |
160 |
_images/shuffle.png 161 |
162 |

整体实现步骤如下:

163 |
164 |
    165 |
  1. 在M1/M2处理完数据后,本地需要ResultPartition RS1/RS2来临时存储数据;

  2. 166 |
  3. 通知JobManager,上游有新的数据产生;

  4. 167 |
  5. JobManager通知和调度下游节点可以消费新的数据;

  6. 168 |
  7. 下游节点向上游请求数据;

  8. 169 |
  9. 通过Channel在各个TaskManager之间传递数据.

  10. 170 |
171 |
172 |

数据在节点之间传递的具体流程如下图:

173 |
174 |
_images/shuffle-data.png 175 |
    176 |
  • 数据在operator处理完成后,先交给RecordWriter,每条记录都要选择一个下游节点,所以要经过ChannelSelector;

  • 177 |
  • 在每个channel都有一个serializer,把这条Record序列化为ByteBuffer;

  • 178 |
  • 接下来数据被写入ResultPartition下的各个ResultSubPartition里,此时该数据已经存入MemorySegment;

  • 179 |
  • 单独的线程控制数据的flush速度,一旦触发flush,则通过Netty的NIO通道向对端写入;

  • 180 |
  • 接收端的Netty Client收到数据后,进行decode操作,把数据拷贝到Buffer里,然后通知InputChannel;

  • 181 |
  • 当InputChannel中有可用的数据时,下游算子从阻塞醒来,从InputChannel取出Buffer,再反序列化成Record,并将其交给算子执行相应的用户代码

  • 182 |
183 |
184 |
185 |
186 |

数据传递源码

187 |

首先,将数据流中的数据交给RecordWriter. 188 | 然后,选择序列化器并序列化数据,写入到相应的Channel. 189 | 当输出缓冲中的字节数超过了高水位值,则Channel.isWritable()会返回false.当输出缓存中的字节数又掉到低水位值以下,则Channel.isWritable()会重新返回true. 190 | 核心发送方法中如果channel不可写,则会跳过发送.当channel再次可写后,Netty会调用该Handle的handleWritabilityChanged方法,从而重新出发发送函数.

191 |

Flink通过Credit实现网络流控,即下游会向上游发送一条credit message,用以通知其目前可用信用额度,然后上游会根据这个信用消息来决定向下游发送多少数据.当 192 | 上游把数据发送给下游时,它就从下游信用上划走相应的额度.

193 |

在上游通过Channel发送数据后,下游通过decodeMsg来获取数据.

194 |

Flink其实做阻塞和获取数据的方式非常自然,利用了生产者和消费者模型,当获取不到数据时,消费者自然阻塞;当数据被加入队列,消费者被notify。 195 | Flink的背压机制也是借此实现。 196 | 至此,Flink数据在节点之间传递的过程便介绍完成。

197 |
198 |
199 | 200 | 201 |
202 | 203 |
204 |
205 | 206 | 212 | 213 | 214 |
215 | 216 |
217 |

218 | © Copyright 2019, zlzhang0122 219 | 220 |

221 |
222 | Built with Sphinx using a theme provided by Read the Docs. 223 | 224 |
225 | 226 |
227 |
228 | 229 |
230 | 231 |
232 | 233 | 234 | 235 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /docs/_build/latex/sphinxhighlight.sty: -------------------------------------------------------------------------------- 1 | \NeedsTeXFormat{LaTeX2e}[1995/12/01] 2 | \ProvidesPackage{sphinxhighlight}[2016/05/29 stylesheet for highlighting with pygments] 3 | 4 | 5 | \makeatletter 6 | \def\PYG@reset{\let\PYG@it=\relax \let\PYG@bf=\relax% 7 | \let\PYG@ul=\relax \let\PYG@tc=\relax% 8 | \let\PYG@bc=\relax \let\PYG@ff=\relax} 9 | \def\PYG@tok#1{\csname PYG@tok@#1\endcsname} 10 | \def\PYG@toks#1+{\ifx\relax#1\empty\else% 11 | \PYG@tok{#1}\expandafter\PYG@toks\fi} 12 | \def\PYG@do#1{\PYG@bc{\PYG@tc{\PYG@ul{% 13 | \PYG@it{\PYG@bf{\PYG@ff{#1}}}}}}} 14 | \def\PYG#1#2{\PYG@reset\PYG@toks#1+\relax+\PYG@do{#2}} 15 | 16 | \expandafter\def\csname PYG@tok@w\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.73,0.73,0.73}{##1}}} 17 | \expandafter\def\csname PYG@tok@c\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}} 18 | \expandafter\def\csname PYG@tok@cp\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} 19 | \expandafter\def\csname PYG@tok@cs\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}\def\PYG@bc##1{\setlength{\fboxsep}{0pt}\colorbox[rgb]{1.00,0.94,0.94}{\strut ##1}}} 20 | \expandafter\def\csname PYG@tok@k\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} 21 | \expandafter\def\csname PYG@tok@kp\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} 22 | \expandafter\def\csname PYG@tok@kt\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.56,0.13,0.00}{##1}}} 23 | \expandafter\def\csname PYG@tok@o\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} 24 | \expandafter\def\csname PYG@tok@ow\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} 25 | \expandafter\def\csname PYG@tok@nb\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} 26 | \expandafter\def\csname PYG@tok@nf\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.02,0.16,0.49}{##1}}} 27 | \expandafter\def\csname PYG@tok@nc\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.05,0.52,0.71}{##1}}} 28 | \expandafter\def\csname PYG@tok@nn\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.05,0.52,0.71}{##1}}} 29 | \expandafter\def\csname PYG@tok@ne\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} 30 | \expandafter\def\csname PYG@tok@nv\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.73,0.38,0.84}{##1}}} 31 | \expandafter\def\csname PYG@tok@no\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.38,0.68,0.84}{##1}}} 32 | \expandafter\def\csname PYG@tok@nl\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.13,0.44}{##1}}} 33 | \expandafter\def\csname PYG@tok@ni\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.84,0.33,0.22}{##1}}} 34 | \expandafter\def\csname PYG@tok@na\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} 35 | \expandafter\def\csname PYG@tok@nt\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.02,0.16,0.45}{##1}}} 36 | \expandafter\def\csname PYG@tok@nd\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.33,0.33,0.33}{##1}}} 37 | \expandafter\def\csname PYG@tok@s\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} 38 | \expandafter\def\csname PYG@tok@sd\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} 39 | \expandafter\def\csname PYG@tok@si\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.44,0.63,0.82}{##1}}} 40 | \expandafter\def\csname PYG@tok@se\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} 41 | \expandafter\def\csname PYG@tok@sr\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.14,0.33,0.53}{##1}}} 42 | \expandafter\def\csname PYG@tok@ss\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.32,0.47,0.09}{##1}}} 43 | \expandafter\def\csname PYG@tok@sx\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.78,0.36,0.04}{##1}}} 44 | \expandafter\def\csname PYG@tok@m\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} 45 | \expandafter\def\csname PYG@tok@gh\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.00,0.50}{##1}}} 46 | \expandafter\def\csname PYG@tok@gu\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.50,0.00,0.50}{##1}}} 47 | \expandafter\def\csname PYG@tok@gd\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.63,0.00,0.00}{##1}}} 48 | \expandafter\def\csname PYG@tok@gi\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.63,0.00}{##1}}} 49 | \expandafter\def\csname PYG@tok@gr\endcsname{\def\PYG@tc##1{\textcolor[rgb]{1.00,0.00,0.00}{##1}}} 50 | \expandafter\def\csname PYG@tok@ge\endcsname{\let\PYG@it=\textit} 51 | \expandafter\def\csname PYG@tok@gs\endcsname{\let\PYG@bf=\textbf} 52 | \expandafter\def\csname PYG@tok@gp\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.78,0.36,0.04}{##1}}} 53 | \expandafter\def\csname PYG@tok@go\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.20,0.20,0.20}{##1}}} 54 | \expandafter\def\csname PYG@tok@gt\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.27,0.87}{##1}}} 55 | \expandafter\def\csname PYG@tok@err\endcsname{\def\PYG@bc##1{\setlength{\fboxsep}{0pt}\fcolorbox[rgb]{1.00,0.00,0.00}{1,1,1}{\strut ##1}}} 56 | \expandafter\def\csname PYG@tok@kc\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} 57 | \expandafter\def\csname PYG@tok@kd\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} 58 | \expandafter\def\csname PYG@tok@kn\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} 59 | \expandafter\def\csname PYG@tok@kr\endcsname{\let\PYG@bf=\textbf\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} 60 | \expandafter\def\csname PYG@tok@bp\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.00,0.44,0.13}{##1}}} 61 | \expandafter\def\csname PYG@tok@fm\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.02,0.16,0.49}{##1}}} 62 | \expandafter\def\csname PYG@tok@vc\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.73,0.38,0.84}{##1}}} 63 | \expandafter\def\csname PYG@tok@vg\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.73,0.38,0.84}{##1}}} 64 | \expandafter\def\csname PYG@tok@vi\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.73,0.38,0.84}{##1}}} 65 | \expandafter\def\csname PYG@tok@vm\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.73,0.38,0.84}{##1}}} 66 | \expandafter\def\csname PYG@tok@sa\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} 67 | \expandafter\def\csname PYG@tok@sb\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} 68 | \expandafter\def\csname PYG@tok@sc\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} 69 | \expandafter\def\csname PYG@tok@dl\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} 70 | \expandafter\def\csname PYG@tok@s2\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} 71 | \expandafter\def\csname PYG@tok@sh\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} 72 | \expandafter\def\csname PYG@tok@s1\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.25,0.44,0.63}{##1}}} 73 | \expandafter\def\csname PYG@tok@mb\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} 74 | \expandafter\def\csname PYG@tok@mf\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} 75 | \expandafter\def\csname PYG@tok@mh\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} 76 | \expandafter\def\csname PYG@tok@mi\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} 77 | \expandafter\def\csname PYG@tok@il\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} 78 | \expandafter\def\csname PYG@tok@mo\endcsname{\def\PYG@tc##1{\textcolor[rgb]{0.13,0.50,0.31}{##1}}} 79 | \expandafter\def\csname PYG@tok@ch\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}} 80 | \expandafter\def\csname PYG@tok@cm\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}} 81 | \expandafter\def\csname PYG@tok@cpf\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}} 82 | \expandafter\def\csname PYG@tok@c1\endcsname{\let\PYG@it=\textit\def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}} 83 | 84 | \def\PYGZbs{\char`\\} 85 | \def\PYGZus{\char`\_} 86 | \def\PYGZob{\char`\{} 87 | \def\PYGZcb{\char`\}} 88 | \def\PYGZca{\char`\^} 89 | \def\PYGZam{\char`\&} 90 | \def\PYGZlt{\char`\<} 91 | \def\PYGZgt{\char`\>} 92 | \def\PYGZsh{\char`\#} 93 | \def\PYGZpc{\char`\%} 94 | \def\PYGZdl{\char`\$} 95 | \def\PYGZhy{\char`\-} 96 | \def\PYGZsq{\char`\'} 97 | \def\PYGZdq{\char`\"} 98 | \def\PYGZti{\char`\~} 99 | % for compatibility with earlier versions 100 | \def\PYGZat{@} 101 | \def\PYGZlb{[} 102 | \def\PYGZrb{]} 103 | \makeatother 104 | 105 | \renewcommand\PYGZsq{\textquotesingle} 106 | --------------------------------------------------------------------------------