生态完善度:
各类编程语言支持, SQL支持, 和其他大数据框架集成如Hadoop, 消息队列如Kafka, Hive, Hbase, Mysql, 监控体系等等
注意, 在大数据计算引擎,最典型是mapreduce(计算中间结果会大量落地磁盘, 性能较慢, 但很稳定)
后续的Spark, 除了shuffle时会落地磁盘,其他场景不会, 这样尽可能将中间结果放在内存, 计算会快很多,因为减少了和磁盘IO的性能消耗
再后续的Flink, 以及OLAP领域的内存计算引擎如Impala, Presto等都尽可能在计算时不做磁盘IO(并不是没有磁盘IO). 这样就很好利用了内存的高速存储特性, 极大提升了数据计算性能.–同样的, 不稳定性以及对硬件高配置要求也自然而然发生, 这是需要实际开发时特别注意的.
注意, 因为Flink本身基于Java和Scala开发, 所以目前各个层级API对这2个语言支持最好也是最快, Python目前最新的1.12支持SQL, Table以及DataStream, 但更底层的Process Function级别还不支持
Spark的API 层级设计也类似, 最底层是RDD(更底层就是各类Function), 再之上有DataSet, DataStream, 基于此之上,有SQL层, Structed Streaming层. 图计算,机器学习,Streaming则是基于RDD进行封装.
注意, Gelly, FlinkML, CEP等模块并没有包括在Flink内部,开发时, 需要在pom文件中引入, 框架本身集成也需要做处理. 这一点和Spark框架是一致的, 提供出来的包是最小化功能, 需要其他功能再进一步扩展集成即可.
- Apache Bahir 官网
- Ververica 官网
- Flink 官网 官网
- Maven
- SBT
- 等等
注意, Flink是一个开源产品, 为了降低Flink开发中项目规模, Flink将很多模块拆分出来,也利用了开源社区很多现有模块如Zookeeper, Calcite等
环境要求:
- Java 8
- Flink发布包 官网下载
- 尽可能是Linux, Mac os等操作系统环境
- 下载Flink 的安装包, 解压缩到自己的安装目录下
- 在Flink安装目录的bin目录下,调用
# 启动
bin/start-cluster.sh
# 关闭
bin/stop-cluster.sh
- 3种模式(https://ci.apache.org/projects/flink/flink-docs-release-1.12/deployment/)
- Application Mode,
- Per-Job Mode,
- Session Mode.
- 参考博客
- https://blog.csdn.net/qq_38058332/article/details/108227900
- https://www.jianshu.com/p/1b05202c4fb6
同样的模式,但需要使用K8s和docker技术,这也是被阿里巴巴验证实际可行并且支撑业务发展的方式
另, 因为Flink本身属于美国Apache基金会旗下开源项目,是受到美国出口管制的, 从法律上来说, 美国政府可以要求禁止Apache向中国出口Flink,虽然是开源的,免费的.
# coding=utf-8
import os
import shutil
from pyflink.table import BatchTableEnvironment, EnvironmentSettings
from pyflink.table import DataTypes
from pyflink.table.descriptors import Schema, OldCsv, FileSystem
# 批处理模式, 因为blink palnner更加强大,批流都支持sql,特性也更加丰富,官方也推荐使用blink planner
env_set = EnvironmentSettings.new_instance().in_batch_mode().use_blink_planner().build()
env = BatchTableEnvironment.create(environment_settings = env_set)
# 设置python执行环境为python3
env.get_config().set_python_executable("python3")
source_word= '/Users/hulc/PycharmProjects/pyflink_1/com/hajk/wordcount/word.csv'
# os.path.join(os.path.abspath(os.path.dirname(__file__)), 'word.csv')
env.execute_sql("""
CREATE TABLE source(
id BIGINT,
word STRING
) with (
'connector' = 'filesystem',
'path' = '/Users/hulc/PycharmProjects/pyflink_1/com/hajk/wordcount/word.csv',
'format' = 'csv'
)""")
sink_word = '/Users/hulc/PycharmProjects/pyflink_1/com/hajk/wordcount/result.csv'
# os.path.join(os.path.abspath(os.path.dirname(__file__)), 'result.csv')
if os.path.exists(sink_word):
if os.path.isfile(sink_word):
os.remove(sink_word)
else:
shutil.rmtree(sink_word, True)
env.execute_sql("""
CREATE TABLE sink (
word STRING,
cnt BIGINT
) WITH (
'connector' = 'filesystem',
'path' = '/Users/hulc/PycharmProjects/pyflink_1/com/hajk/wordcount/result.csv',
'format' = 'csv'
)
""")
env.execute_sql("""
INSERT INTO sink
SELECT
word,
count(1) as cnt
FROM
source
GROUP BY word
""")
数据
0,flink
1,pyflink
2,flink
3,pyflink
4,flink
# coding=utf-8
from pyflink.table import EnvironmentSettings, StreamTableEnvironment, udf
from pyflink.datastream import StreamExecutionEnvironment
from pyflink.table import TableFunction
from pyflink.table.types import DataTypes
from pyflink.table.udf import udtf
env_stream = StreamExecutionEnvironment.get_execution_environment()
env_table = StreamTableEnvironment.create(env_stream)
class Split(TableFunction):
def eval(self, string):
for s in string.split(" "):
yield s, len(s)
table_src = env_table\
.from_elements([('sad asd erf wer', 2), ('ert tyu uiyer ert', 3)], schema=DataTypes.ROW([DataTypes.FIELD('a', DataTypes.STRING()), DataTypes.FIELD('b', DataTypes.INT())]))
split = udtf(Split(), result_types = [DataTypes.STRING(), DataTypes.INT()])
result1 = table_src.join_lateral(split(table_src.a).alias("word", "length"))
# print(result1.to_pandas())
result2 = table_src.left_outer_join_lateral(split(table_src.a).alias("word", "length"))
# print(result2.to_pandas())
# sql 中使用
env_table.create_temporary_function('split', udtf(Split(), result_types=[DataTypes.STRING(), DataTypes.INT()]))
env_table.create_temporary_view('view_src', table_src)
result3 = env_table.sql_query("""
SELECT
a,
word,
length
FROM
view_src,
LATERAL TABLE(split(a)) as T(word, length)
""")
# print(result3.to_pandas())
result4 = env_table.sql_query("""
SELECT
a,
word,
length
FROM
view_src
LEFT JOIN
LATERAL Table(split(a)) as T(word, length)
ON TRUE
""")
print(result4.to_pandas())
备注(多种UDF定义和使用方式)
# coding=utf-8
import functools
from pyflink.table.types import DataTypes
from pyflink.table.udf import udf
from pyflink.table.udf import ScalarFunction
from pyflink.table import BatchTableEnvironment, EnvironmentSettings
# 继承基类ScalarFunction
class Add(ScalarFunction):
def eval(self, i, j):
return i + j
add = udf(Add(), result_type=DataTypes.BIGINT())
# 普通python函数,但是加udf注解
@udf(result_type=DataTypes.BIGINT())
def add2(i, j):
return i + j
# lambda函数
add3 = udf(lambda i, j: i + j, result_type=DataTypes.BIGINT())
# callable函数
class CallabelAdd(object):
def __call__(self, i, j):
return i + j
add4 = udf(CallabelAdd(), result_type=DataTypes.BIGINT())
# partial 函数
def partial_add(i, j, k):
return i + j + k
add5 = udf(functools.partial(partial_add, k=1), result_type=DataTypes.BIGINT())
# 定义后,还需要注册
env = BatchTableEnvironment.create(
environment_settings=EnvironmentSettings.new_instance().in_batch_mode().use_blink_planner().build())
env.create_temporary_function('add', add)
# 如果是table api--dsl风格,则可以直接使用python自定义函数,不需要提前注册
from pyflink.datastream import StreamExecutionEnvironment, TimeCharacteristic
from pyflink.table import StreamTableEnvironment, EnvironmentSettings
def log_processing():
env = StreamExecutionEnvironment.get_execution_environment()
env_settings = EnvironmentSettings.Builder().use_blink_planner().build()
t_env = StreamTableEnvironment.create(stream_execution_environment=env, environment_settings=env_settings)
# specify connector and format jars
t_env.get_config().get_configuration().set_string("pipeline.jars",
"file:///my/jar/path/connector.jar;file:///my/jar/path/json.jar")
source_ddl = """
CREATE TABLE source_table(
a VARCHAR,
b INT
) WITH (
'connector' = 'kafka',
'topic' = 'source_topic',
'properties.bootstrap.servers' = 'kafka:9092',
'properties.group.id' = 'test_3',
'scan.startup.mode' = 'latest-offset',
'format' = 'json'
)
"""
sink_ddl = """
CREATE TABLE sink_table(
a VARCHAR
) WITH (
'connector' = 'kafka',
'topic' = 'sink_topic',
'properties.bootstrap.servers' = 'kafka:9092',
'format' = 'json'
)
"""
t_env.execute_sql(source_ddl)
t_env.execute_sql(sink_ddl)
t_env.sql_query("SELECT a FROM source_table") \
.execute_insert("sink_table").wait()
if __name__ == '__main__':
log_processing()
# coding=utf-8
from pyflink.table import StreamTableEnvironment, BatchTableEnvironment, EnvironmentSettings
# 注意,mode和对应Environment都是对应的,混合会报错
# 流,blink
env_setting1 = EnvironmentSettings.new_instance().use_blink_planner().in_streaming_mode().build()
env1 = StreamTableEnvironment.create(environment_settings = env_setting1)
# 流 flink
env_setting2 = EnvironmentSettings.new_instance().use_old_planner().in_streaming_mode().build()
env2 = StreamTableEnvironment.create(environment_settings = env_setting2)
# 批 blink
env_setting3 = EnvironmentSettings.new_instance().use_blink_planner().in_batch_mode().build()
env3 = BatchTableEnvironment.create(environment_settings=env_setting3)
# 批 flink
env_setting4 = EnvironmentSettings.new_instance().in_batch_mode().use_old_planner().build()
env4 = BatchTableEnvironment.create(env_setting4)
# coding=utf-8
from pyflink.common.serialization import SimpleStringEncoder
from pyflink.common.typeinfo import Types
from pyflink.datastream import StreamExecutionEnvironment
from pyflink.datastream.connectors import StreamingFileSink
import os
import shutil
env = StreamExecutionEnvironment.get_execution_environment()
env.set_parallelism(1)
ds = env.from_collection(
collection=[(1, 'jack'), (2, 'rose'), (3, 'laowang'), (4, 'laoli')],
type_info=Types.ROW([Types.INT(), Types.STRING()]))
result_path = '/Users/hulc/PycharmProjects/pyflink_1/com/hajk/datastream/result'
if os.path.exists(result_path):
if os.path.isfile(result_path):
os.remove(result_path)
else:
shutil.rmtree(result_path, True)
ds.add_sink(StreamingFileSink.for_row_format(result_path, SimpleStringEncoder()).build())
env.execute('datastream test1')
# 执行脚本
# /Users/hulc/Library/Python/3.8/lib/python/site-packages/pyflink/bin/flink run -m localhost:8081 -py /Users/hulc/PycharmProjects/pyflink_1/com/hajk/datastream/Test_blink_stream_table.py
考虑到Flink未来Api规划–批流一体, 所以按照官方建议, 直接基于DataStream,但是使用batch mode模式就是批处理了
实际上, Spark也开始做这种尝试, 这个可以从其Dataframe的广泛应用以及structed stream的开发就可以看出.
经典的主从结构, 和Spark非常相似,同样也是需要配置HA模式
it decides when to schedule the next task (or set of tasks),
reacts to finished tasks or execution failures,
coordinates checkpoints,
coordinates recovery on failures, among others
execute the tasks of a dataflow,
buffer and exchange the data streams
The smallest unit of resource scheduling in a TaskManager is a task slot.
The number of task slots in a TaskManager indicates the number of concurrent processing tasks
最大作用就是集群迁移, 版本升级等时, 保存集群中运行的Flink任务状态信息(数据以及操作)
需要主动触发,属于运维类型指令
处理时间,顾名思义, 就是流计算中, 事件时间以时间被处理的时间为准.
对于一些业务场景,希望以最低延迟,但对于数据精度要求不那么高时, 就可以采用以处理时间为基准做事件处理
但注意, 以处理时间为基准,其实会导致数据无法做回放. 这一点和基于事件时间处理相差较大
事件时间, 就是事件中带着发生时间这个信息. 基于此做处理,则可以做数据回放.
精确一次消费语义也是基于事件时间实现的
水印, 这是为了衡量基于事件时间处理的一个机制, 本质可以看做是带着时间戳的一个特殊信息单元, 会被放入数据流中.
并行处理多个流或者任务时, 水印是独立产生的
可以看出, 水印相当于在数据流中放了一些时间标记信息, 这时候就会产生一个问题, 原本是2021-01-03 19:47:00产生的数据, 结果推迟了10小时才到达. 这时候已经超过水印标记范围了, 因为程序不可能无限等待延迟到达的数据, 所以这些数据其实就需要有一个处理策略
- 抛弃
- 侧流输出,再更新以往结果
- 开窗时允许一定延迟时间, 这样晚到一小会的数据也可以被处理
具体可以参考:https://ci.apache.org/projects/flink/flink-docs-release-1.12/dev/stream/operators/windows.html#allowed-lateness
实际参考新浪的Flink实践时, 有一个场景就是用户行为日志有可能延迟到达半天到一天, 他们就是使用Hbase做外部缓存,等到了之后再抽出来做批处理来解决延迟到达问题.
适合场景就是一个流拷贝切分为多个流(单纯拷贝为多个流, 每个流处理各自逻辑. 例如这样就不需要从kafka中设置多个消费者组进行消费处理了)
也可以用来处理延迟到达事件(主流处理, 侧流做延迟到达数据处理–更新或者删除等操作)
也可以用来做流事件处理的监控(主流做处理, 侧流做采样和处理结果对比监控)
三大类, 基于rocksdb, 文件系统以及内存
和Spark一样, 会把窄依赖操作合并为流水线, 这样一个流水线用一个线程执行
流水线可以降低线程间切换和通信的消耗
和Spark类似, Flink是一个TaskManager一个JVM, 但是为了更细粒度利用资源, 将这些资源以Slot插槽方式划分
一个插槽可以理解为一个类似做了线程切割而没有做进程切割. 所以只是内存级别切割,但是CPU级别没有切割, 都还是在一个JVM虚拟机中
开窗, 顾名思义,就是对数据流上数据已一定条件做提取, 无界流改为有界流. 计算时,以一定条件和规则触发计算以及窗口中数据清空处理