flink+pyflink+alink筆記

系列文檔參見這裏

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) -&gt; 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 -&gt; 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優化

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章