系列文檔參見這裏。
1. 背景與原理
1.1 背景
其實就是數據處理流水線。可以參考https://zhuanlan.zhihu.com/p/114717285
常見的應用場景:
第一個,事件驅動型,比如:刷單,監控等;
第二個,數據分析型的,比如:庫存,雙11大屏等;
第三個適用的場景是數據管道,也就是ETL場景,比如一些日誌的解析等;
第四個場景,機器學習,比如個性推薦等。
1.2 基本概念
- bounded、unbounded
- state。無狀態:來一條處理一條;有狀態:需要keep之前的狀態。Flink提供本地狀態,需要定期遠程備份。
- time。event(事件發生事件)、ingestion、processing time。
- api:sql/table api(dynamic tables);dataStream API(streams, windows),processFunction(events,state,time)。
1.3 穩定性的一些配置
2. flink安裝
2.1 brew方式
安裝:brew install apache-flink。通過brew info apache-flink查找flink的安裝位置。一般在/usr/local/Cellar/apache-flink/1.10.0目錄下。
這個目錄相當精簡,沒有python相關的環境。
cd到根目錄下,啓動standalone模式:
./libexec/bin/start-cluster.sh
slave清單在conf文件夾下
2.2 安裝包啓動
直接到官網找到源碼,下載後,cd到根目錄下,啓動standalone模式。這裏的內容是比較詳細的,同目錄下有pyflink-shell,外面的example裏面也有python的例子
2.3 使用conda/pip安裝
pip install apache-flink,
pip install alink
順便把alink也裝上。如果出現PyYAML問題,執行pip3 install --ignore-installed PyYAML。
啓動腳本在~/anaconda3/lib/python3.7/site-packages/pyflink/bin目錄下。可以看出內容同樣很豐富。
2.4 使用方式
2.4.1 啓動集羣
- start-cluster.sh local啓動的是flink進程,web管理頁面爲:http://localhost:8081/
2.4.2 pyflink
pyflink-shell.sh local啓動的是一個python編譯器和自帶的flink環境,可以通過s_env和b_env獲得環境變量。在新版本中,PyAlink 新增了 getMLEnv 的接口,直接獲取 PyFlink 的執行環境。 這個接口返回四元組(benv, btenv, senv, stenv),分別對應 PyFlink 中的四種執行環境: ExecutionEnvironment、BatchTableEnvironment、StreamExecutionEnvironment 和 StreamTableEnvironment。 基於這四個變量,用戶可以調用 PyFlink 的接口。
此外,在之前的版本中,PyAlink 提供了方便使用 Flink 不同執行環境的函數:useLocalEnv 和 useRemoteEnv。 這兩個接口在新版本中將同樣返回四元組 (benv, btenv, senv, stenv)。 用戶可以通過返回的執行環境來調用 PyFlink 的接口。
PyAlink 提供了 TableSourceBatchOp 和 TableSourceStreamOp 將 PyFlink 中的 Table 分別轉換爲 Alink 中的 BatchOperator 和 StreamOperator。
同時,對於 PyAlink 中的 Operator,提供了 getOutputTable 來獲取算法組件對應的 Table 。
pyflink-shell.sh remote localhost 8081,這樣可以連接到最開始啓動的flink環境上面去。
2.4.3 alink
mac上可以參考這裏進行安裝
進入jupyter notebook使用下面的方式進行查看
from pyalink.alink import *
resetEnv()
useLocalEnv('localhost',8081,1)
source_url = CsvSourceBatchOp()\
.setFilePath("http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data")\
.setSchemaStr("sepal_length double, sepal_width double, petal_length double, petal_width double, category string")
source_url.firstN(5).print()
3. 操縱數據
3.1 基本讀寫數據
-
基於文件的:
read_text(path):按行讀取文件並將其作爲字符串返回。
read_csv(path, type):解析逗號(或其他字符)分隔字段的文件。返回元組的DataSet。支持基本java類型及其Value對應作爲字段類型。 -
基於集合:
from_elements(*args):從Seq創建數據集。
generate_sequence(from, to):並行生成給定間隔中的數字序列。 -
支持的Data Sink
write_text():按字符串順序寫入數據元。通過調用每個數據元的str()方法獲得字符串。
write_csv(…):將元組寫爲逗號分隔值文件。行和字段分隔符是可配置的。每個字段的值來自對象的str()方法。
output():打印標準輸出上每個數據元的str()值。
3.2 python中獲取數據
pyflink預綁定了四個變量,批處理表環境變量名爲:
- bt_env(pyflink.table.table_environment.BatchTableEnvironment)
- 批處理執行環境變量名爲: b_env(pyflink.dataset.execution_environment.ExecutionEnvironment)
- 流表環境變量名爲:st_env(pyflink.table.table_environment.StreamTableEnvironment)
- 流執行環境變量名爲:s_env(pyflink.datastream.stream_execution_environment.StreamExecutionEnvironment)
使用本地執行環境時,使用 Notebook 提供的“停止”按鈕即可停止長時間運行的Flink作業。 使用遠程集羣時,需要使用集羣提供的停止作業功能。
可以直接使用 Python 腳本而不是 Notebook 運行。但需要在代碼最後調用 resetEnv(),否則腳本不會退出。
下面是python的例子
In [1]: import os, shutil
In [2]: sink_path = './streaming.csv'
In [3]: #設置了結果輸出路徑
In [4]: s_env.set_parallelism(1)
Out[4]: <pyflink.datastream.stream_execution_environment.StreamExecutionEnvironment at 0x10991e1d0>
In [5]: #設置並行度爲1,因爲是local模式只能設置爲1,定義了一個數據來源
In [6]: t = st_env.from_elements([(1, 'hi', 'hello'), (2, 'hi', 'hello')], ['a', 'b', 'c'])
In [7]:
In [8]: st_env.connect(FileSystem().path(sink_path))\
...: .with_format(OldCsv()
...: .field_delimiter(',')
...: .field("a", DataTypes.BIGINT())
...: .field("b", DataTypes.STRING())
...: .field("c", DataTypes.STRING()))\
...: .with_schema(Schema()
...: .field("a", DataTypes.BIGINT())
...: .field("b", DataTypes.STRING())
...: .field("c", DataTypes.STRING()))\
...: .register_table_sink("stream_sink")
Out[8]: <pyflink.table.descriptors.StreamTableDescriptor at 0x1097b4590>
In [9]: t.select("a + 1, b, c")\
...: ... .insert_into("stream_sink")
In [10]: st_env.execute("stream_job")
In [11]: # 如果在local模式下,以下代碼才能執行.輸出了文件結果
In [12]: with open(sink_path, 'r') as f:
...: print(f.read())
...:
2,hi,hello
3,hi,hello
頁面上顯示完成了一個任務,用時812ms。
3.3 在Alink中與Dataframe互操作
轉DataFrame:
source = CsvSourceBatchOp()\
.setSchemaStr("sepal_length double, sepal_width double, petal_length double, petal_width double, category string")\
.setFilePath("https://alink-release.oss-cn-beijing.aliyuncs.com/data-files/iris.csv")
res = source.select(["sepal_length", "sepal_width"])
df = res.collectToDataframe()
# Operations with df
res.print()
轉BatchOperator/StreamOperator
schema = "f_string string,f_long long,f_int int,f_double double,f_boolean boolean"
op = BatchOperator.fromDataframe(df, schema)
op.print()
op = StreamOperator.fromDataframe(df, schema)
op.print(key="op")
StreamOperator.execute()
同時,PyAlink 也提供了靜態方法來進行轉換:dataframeToOperator(df, schemaStr, opType),這裏 df 和 schemaStr 參數與上文相同,opType 取值爲 “batch” 或 “stream”。
3.4 Alink中StreamOperator數據預覽
對於 StreamOperator, 在使用 Jupyter Notebook 時,PyAlink 提供了一種動態的數據預覽方式。 這種預覽方式採用了 DataFrame 的顯示方式,支持隨着時間窗口不斷進行刷新,從而有較好的視覺體驗來觀察流式數據。這種預覽方式通過以下方法實現: print(self, key=None, refreshInterval=0, maxLimit=100)
- key 爲一個字符串,表示給對應的 Operator 給定一個索引;不傳值時將隨機生成。
- refreshInterval 表示刷新時間,單位爲秒。當這個值大於0時,所顯示的表將每隔 refreshInterval 秒刷新,顯示前 refreshInterval 的數據;當這個值小於0時,每次有新數據產生,就會在觸發顯示,所顯示的數據項與時間無關。
- maxLimit 用於控制顯示的數據量,最多顯示 maxLimit 條數據。
schema = "age bigint, workclass string, fnlwgt bigint, education string, education_num bigint, marital_status string, occupation string, relationship string, race string, sex string, capital_gain bigint, capital_loss bigint, hours_per_week bigint, native_country string, label string"
adult_batch = CsvSourceStreamOp() \
.setFilePath("http://alink-dataset.cn-hangzhou.oss.aliyun-inc.com/csv/adult_train.csv") \
.setSchemaStr(schema)
sample = SampleStreamOp().setRatio(0.01).linkFrom(adult_batch)
sample.print(key="adult_data", refreshInterval=3)
StreamOperator.execute()
需要特別注意的是:使用 print 進行數據預覽的 StreamOperator 需要嚴格控制數據量。 單位時間數據量太大不僅不會對數據預覽有太大幫助,還會造成計算與網絡資源浪費。 同時, Python 端在收到數據後進行轉換也是比較耗時的操作,兩者會導致數據預覽延遲。 比較合理的做法是通過採樣組件 SampleStreamOp 來達到減少數據量的目的。
4. 常用算子
4.1 operator算子
operator是基於dataStream進行操作的。
基於單條記錄
Map:輸入x,輸出x’
FlatMap:輸入x,輸出list
合併多條流
MapPartition:輸入list,輸出list’
Filter:輸入list,輸出list’
Reduce:輸入list,輸出x
ReduceGroup:輸入list,輸出dict
Aggregate:輸入list,輸出x,比reduce更復雜
join:輸入兩個list,輸出合併的list’
CoGroup:二維Reduce
Cross:輸入兩個list,輸出x
拆分單條流
split
基於窗口操作
window
下面是個例子:
4.2 UDF函數
我們提供了 udf 和 udtf 函數來幫助構造 UDF/UDTF。 兩個函數使用時都需要提供一個函數體、輸入類型和返回類型。
函數體對於 UDF 而言,是直接用 return 返回值的 Python 函數,或者 lambda 函數; 對於 UDTF 而言,是用 yield 來多次返回值的 Python 函數。
輸入類型均爲 DataType 類型的 Python list。
輸出類型,UDF 爲單個 DataType 類型,UDTF爲 DataType 類型的 Python list。
DataType 類型可以直接用DataTypes.DOUBLE()等類似的函數得到。
以下是定義 UDF/UDTF 的代碼示例:
# 4種 UDF 定義
# ScalarFunction
class PlusOne(ScalarFunction):
def eval(self, x, y):
return x + y + 10
f_udf1 = udf(PlusOne(), input_types=[DataTypes.DOUBLE(), DataTypes.DOUBLE()], result_type=DataTypes.DOUBLE())
# function + decorator
@udf(input_types=[DataTypes.DOUBLE(), DataTypes.DOUBLE()], result_type=DataTypes.DOUBLE())
def f_udf2(x, y):
return x + y + 20
# function
def f_udf3(x, y):
return x + y + 30
f_udf3 = udf(f_udf3, input_types=[DataTypes.DOUBLE(), DataTypes.DOUBLE()], result_type=DataTypes.DOUBLE())
# lambda function
f_udf4 = udf(lambda x, y: x + y + 40
, input_types=[DataTypes.DOUBLE(), DataTypes.DOUBLE()], result_type=DataTypes.DOUBLE())
udfs = [
f_udf1,
f_udf2,
f_udf3,
f_udf4
]
# 4種 UDTF 定義
# TableFunction
class SplitOp(TableFunction):
def eval(self, *args):
for index, arg in enumerate(args):
yield index, arg
f_udtf1 = udtf(SplitOp(), [DataTypes.DOUBLE(), DataTypes.DOUBLE()], [DataTypes.INT(), DataTypes.DOUBLE()])
# function + decorator
@udtf(input_types=[DataTypes.DOUBLE(), DataTypes.DOUBLE()], result_types=[DataTypes.INT(), DataTypes.DOUBLE()])
def f_udtf2(*args):
for index, arg in enumerate(args):
yield index, arg
# function
def f_udtf3(*args):
for index, arg in enumerate(args):
yield index, arg
f_udtf3 = udtf(f_udtf3, input_types=[DataTypes.DOUBLE(), DataTypes.DOUBLE()], result_types=[DataTypes.INT(), DataTypes.DOUBLE()])
# lambda function
f_udtf4 = udtf(lambda *args: [ (yield index, arg) for index, arg in enumerate(args) ]
, input_types=[DataTypes.DOUBLE(), DataTypes.DOUBLE()], result_types=[DataTypes.INT(), DataTypes.DOUBLE()])
udtfs = [
f_udtf1,
f_udtf2,
f_udtf3,
f_udtf4
]
注意:在 Flink 1.10 及以上版本對應的 PyAlink 包中,udf 定義的 UDF 與 PyFlink 中的 udf 定義是完全一致的。
4.3 SQL操作
schema = "age bigint, workclass string, fnlwgt bigint, education string, education_num bigint, marital_status string, occupation string, relationship string, race string, sex string, capital_gain bigint, capital_loss bigint, hours_per_week bigint, native_country string, label string"
adult_batch = CsvSourceStreamOp() \
.setFilePath("http://alink-dataset.cn-hangzhou.oss.aliyun-inc.com/csv/adult_train.csv") \
.setSchemaStr(schema)
UDFStream = UDFStreamOp().setFunc(f_udf2)\
.setSelectedCols(["age", "education_num"]) \
.setOutputCol("newAge") \
.linkFrom(adult_batch)
UDFStream.registerTableName("A")
res = StreamOperator.sqlQuery("select age,newAge from A where age>30")
res.print()
StreamOperator.execute()
4.4 pyflink API
常用的api見
https://github.com/alibaba/Alink/blob/master/docs/index-cn.md
5. 代碼注意事項
數據在物理層面上的傳遞有如下幾種方法:
在做實時成交額統計時,一般不用accumulator方法,而是用tableAPI的retractStream方法。key值一般是遠大於併發度的,如果每次更新都產生一個非常大的map的話,系統會受不了。
6. 客戶端操作
6.1 command line
info 看執行計劃,在https://flink.apache.org/visualizer/裏可以把命令轉化爲執行計劃圖。和物理計劃會有差別。
list列出運行任務
detach模式,任務結束就退出
cancel/stop 暴力取消vs優雅取消
cancel可以指定savepoint,下次可以用savepoint。
6.2 sql-clinet
sql-client.sh embedded啓動命令
通過explain命令可以直接看執行計劃
6.3 restful方式
更多命令參考這裏:
https://ci.apache.org/projects/flink/flink-docs-stable/monitoring/rest_api.html
➜ flink-1.7.2 curl http://127.0.0.1:8081/overview
{"taskmanagers":1,"slots-total":4,"slots-available":0,"jobs-running":3,"jobs-finished":0,"jobs-cancelled":0,"jobs-failed":0,"flink-version":"1.7.2","flink-commit":"ceba8af"}%
➜ flink-1.7.2 curl -X POST -H "Expect:" -F "jarfile=@/Users/baoniu/Documents/work/tool/flink/flink-1.7.2/examples/streaming/TopSpeedWindowing.jar" http://127.0.0.1:8081/jars/upload
{"filename":"/var/folders/2b/r6d49pcs23z43b8fqsyz885c0000gn/T/flink-web-124c4895-cf08-4eec-8e15-8263d347efc2/flink-web-upload/6077eca7-6db0-4570-a4d0-4c3e05a5dc59_TopSpeedWindowing.jar","status":"success"}%
➜ flink-1.7.2 curl http://127.0.0.1:8081/jars
{"address":"http://localhost:8081","files":[{"id":"6077eca7-6db0-4570-a4d0-4c3e05a5dc59_TopSpeedWindowing.jar","name":"TopSpeedWindowing.jar","uploaded":1553743438000,"entry":[{"name":"org.apache.flink.streaming.examples.windowing.TopSpeedWindowing","description":null}]}]}%
➜ flink-1.7.2 curl http://127.0.0.1:8081/jars/6077eca7-6db0-4570-a4d0-4c3e05a5dc59_TopSpeedWindowing.jar/plan
{"plan":{"jid":"41029eb3feb9132619e454ec9b2a89fb","name":"CarTopSpeedWindowingExample","nodes":[{"id":"90bea66de1c231edf33913ecd54406c1","parallelism":1,"operator":"","operator_strategy":"","description":"Window(GlobalWindows(), DeltaTrigger, TimeEvictor, ComparableAggregator, PassThroughWindowFunction) -> Sink: Print to Std. Out","inputs":[{"num":0,"id":"cbc357ccb763df2852fee8c4fc7d55f2","ship_strategy":"HASH","exchange":"pipelined_bounded"}],"optimizer_properties":{}},{"id":"cbc357ccb763df2852fee8c4fc7d55f2","parallelism":1,"operator":"","operator_strategy":"","description":"Source: Custom Source -> Timestamps/Watermarks","optimizer_properties":{}}]}}% ➜ flink-1.7.2 curl -X POST http://127.0.0.1:8081/jars/6077eca7-6db0-4570-a4d0-4c3e05a5dc59_TopSpeedWindowing.jar/run
{"jobid":"04d80a24b076523d3dc5fbaa0ad5e1ad"}%
7. TableAPI和 Flink SQL
7.1 table API
定義表
輸出表
查詢操作
7.2 flink sql
7.3 優化原理
分段優化
sub-plan reuse
agg優化