1.什么是時狀態(tài)(state)?
??有狀態(tài)的計算是流處理框架要實現的重要功能,因為稍復雜的流處理場景都需要記錄狀態(tài),然后在新流入數據的基礎上不斷更新狀態(tài)。 例如以下狀態(tài)都需要使用流處理的狀態(tài)功能:
數據流中的數據有重復,想對重復數據去重,需要記錄哪些數據已經流入過應用,當新數據流入時,根據已流入過的數據來判斷去重。
檢查輸入流是否符合某個特定的模式,需要將之前流入的元素以狀態(tài)的形式緩存下來。比如,判斷一個溫度傳感器數據流中的溫度是否在持續(xù)上升。
對一個時間窗口內的數據進行聚合分析,分析一個小時內某項指標的75分位或99分位的數值。
在線機器學習場景下,需要根據新流入數據不斷更新機器學習的模型參數。
??在Flink任務中,Flink的一個算子有多個子任務,每個子任務分布在不同實例上,可以把狀態(tài)理解為某個算子子任務在其當前實例上的一個變量,變量記錄了數據流的歷史信息。當新數據流入時,可以結合歷史信息來進行計算。實際上,Flink的狀態(tài)是由算子的子任務來創(chuàng)建和管理的。一個狀態(tài)更新和獲取的流程如下圖所示,一個算子子任務接收輸入流,獲取對應的狀態(tài),根據新的計算結果更新狀態(tài)。一個簡單的例子是對一個時間窗口內輸入流的某個整數字段求和,那么當算子子任務接收到新元素時,會獲取已經存儲在狀態(tài)中的數值,然后將當前輸入加到狀態(tài)上,并將狀態(tài)數據更新。
對于獲取和更新狀態(tài)數據的邏輯不復雜,但是對于流處理框架還需要解決以下問題:
數據的產出要保證實時性,延遲不能太高。
需保證數據的不重不丟,恰好計算一次,尤其是當狀態(tài)數據非常大或者應用出現故障需要恢復時,要保證狀態(tài)的計算不出任何錯誤。
一般流處理任務都是全天運行的,程序的可靠性非常高。
??基于上述要求, 不能將狀態(tài)直接交由內存管理,因為內存的容量是有限制的,當狀態(tài)數據稍微大一些時,就會出現OOM問題。 作為一個計算框架,Flink提供了有狀態(tài)的計算,封裝了一些底層的實現,比如狀態(tài)的高效存儲、Checkpoint和Savepoint持久化備份機制、計算資源擴縮容等問題。因為Flink接管了這些問題,開發(fā)者只需調用Flink API,這樣可以更加專注于業(yè)務邏輯。
總而言之,Flink中的狀態(tài):
由一個任務維護,并且用來計算某個結果的所有數據,就屬于這個任務的狀態(tài)。
狀態(tài)可以理解為一個本地變量,可以被任何業(yè)務邏輯訪問。
當任務失敗時,可以使用狀態(tài)恢復數據。
狀態(tài)始終與特定的算子相關聯。
算子需要預先注冊其狀態(tài),以便Flink在運行時能夠了解算子狀態(tài)。
2.狀態(tài)描述(StateDescriptor)
??StateDescriptor 是所有狀態(tài)描述符的基類。 Flink 通過 StateDescriptor 定義狀態(tài),包括狀態(tài)的名稱,存儲數據的類型,序列化器等基礎信息。
??Flink 中提供了 ListStateDescriptor、MapStateDescriptor、ValueStateDescriptor、AggregatingStateDescriptor、ReducingStateDescriptor 、FoldingStateDescriptor(廢棄) 狀態(tài)描述符供使用。
2.Flink狀態(tài)分類
2.1 托管狀態(tài)和原始狀態(tài)
??Flink的狀態(tài)有兩種:托管狀態(tài)(Managed State)和原始狀態(tài)(Raw State)。托管狀態(tài)就是由Flink統一管理的,狀態(tài)的存儲訪問、故障恢復和重組等一系列問題都由Flink實現,作為開發(fā)人員只要調接口就可以;而原始狀態(tài)則是自定義的,相當于就是開辟了一塊內存,需要開發(fā)人員管理,實現狀態(tài)的序列化和故障恢復。
Managed State | Raw State | |
---|---|---|
狀態(tài)管理方式 | Flink Runtime托管,自動存儲、自動恢復、自動伸縮 | 用戶管理 |
狀態(tài)數據結構 | Flink提供常見的數據結構,如ValueState、ListValue、MapState等 | 字節(jié)數據組:byte[] |
應用場景 | 絕大多數Flink算子(通過繼承Rich函數或者其他提供的接口類) | 用戶自定義算子 |
2.2 Keyed State和Operator State
??在Flink任務中,一個算子任務會按照并行度分為多個并行子任務執(zhí)行,而不同的子任務會占據不同的任務槽(task slot)。由于不同的slot在計算資源上是物理隔離的,所以Flink能管理的狀態(tài)在并行任務間是無法共享的,每個狀態(tài)只能針對當前子任務的實例有效。而很多有狀態(tài)的操作(比如聚合、窗口)都是要先做keyBy進行按鍵分區(qū)的。按鍵分區(qū)之后,任務所進行的所有計算都應該只針對當前key有效,所以狀態(tài)也應該按照key彼此隔離。在這種情況下,狀態(tài)的訪問方式又會有所不同?;谏鲜銮闆r,托管狀態(tài)可分為兩類:算子狀態(tài)和按鍵分區(qū)狀態(tài)。
2.2.1 算子狀態(tài)(Operator State)概述
??狀態(tài)作用范圍限定為當前的算子任務實例,也就是只對當前并行子任務實例有效。對于一個并行子任務,它所處理的所有數據都會訪問到相同的狀態(tài),狀態(tài)對于同一任務而言是共享的,如圖所示:
??算子狀態(tài)可以用在所有算子上,使用時其實就跟一個本地變量沒什么區(qū)別——因為本地變量的作用域也是當前任務實例。在使用時,還需進一步實現CheckpointedFunction接口。
2.2.2 按鍵分區(qū)狀態(tài)(Keyed State)概述
??按鍵分區(qū)狀態(tài)(Keyed State)顧名思義,是任務按照鍵(key)來訪問和維護的狀態(tài)。它的特點非常鮮明,就是以key為作用范圍進行隔離。在進行按鍵分區(qū)(keyBy)之后,具有相同鍵的所有數據,都會分配到同一個并行子任務中;所以如果當前任務定義了狀態(tài),Flink就會在當前并行子任務實例中,為每個鍵值維護一個狀態(tài)的實例。
??一個并行子任務可能會處理多個key的數據,在底層,Keyed State類似于一個分布式的映射(map)數據結構,所有的狀態(tài)會根據key保存成鍵值對(key-value)的形式。
??當一條數據到來時,任務就會自動將狀態(tài)的訪問范圍限定為當前數據的key,從map存儲中讀取出對應的狀態(tài)值。所以具有相同key的所有數據都會到訪問相同的狀態(tài),而不同key的狀態(tài)之間是彼此隔離的。這種將狀態(tài)綁定到key上的方式,相當于使得狀態(tài)和流的邏輯分區(qū)一一對應了,不會有別的key的數據來訪問當前狀態(tài);而當前狀態(tài)對應key的數據也只會訪問這一個狀態(tài),不會分發(fā)到其他分區(qū)去。這就保證了對狀態(tài)的操作都是本地進行的,對數據流和狀態(tài)的處理做到了分區(qū)一致性。
??Keyed State是KeyedStream
上的狀態(tài)。 狀態(tài)是根據輸入流中定義的鍵(key)來維護和訪問的,所以只能定義在按鍵分區(qū)流(KeyedStream)中,也就keyBy之后才可以使用,如圖所示:
2.2.3 Keyed State 與 Operator State比較
??無論是Keyed State還是Operator State,Flink的狀態(tài)都是基于本地的,即每個算子子任務維護著這個算子子任務對應的狀態(tài)存儲,算子子任務之間的狀態(tài)不能相互訪問。
Operator State | Keyed State | |
---|---|---|
適用算子類型 | 可以適用所有算子 | 只適用于KeyedStream上的算子 |
狀態(tài)分配 | 一個算子子任務對應一個狀態(tài) | 每個Key對應一個狀態(tài) |
創(chuàng)建和訪問方式 | 實現CheckpointedFunction等借口 | 重寫Rich Function,通過里面的RuntimeContext訪問 |
橫向擴展 | 有多種狀態(tài)重新分配的方式 | 狀態(tài)隨著Key自動在多個算子子任務上遷移 |
支持數據結構 | ListState、BroadcastState等 | ValueState、ListValue、MapState等 |
current key OperatorState沒有current key的概念,KeyedState的數值總是與一個current key對應。
snapshot OperatorState 需要手動實現snapshot和restore方法,KeyedState由backend實現,對用戶透明。
heap OperatorState 只有堆內存一種實現,KeyedState由有堆內存和RocksDB兩種實現。
Size OperatorState 一般被認為是規(guī)模比較小的,KeyedState一般是相對規(guī)模較大的。
2.2.4 狀態(tài)的使用
??在 Flink 中,狀態(tài)始終是與特定算子相關聯的;算子在使用狀態(tài)前首先需要“注冊”,其實就是告知Flink當前上下文中定義狀態(tài)的信息,這樣運行時的Flink才能知道算子有哪些狀態(tài)。
??狀態(tài)的注冊,主要是通過“狀態(tài)描述器”(StateDescriptor)來實現的。狀態(tài)描述器中最重要的內容,就是狀態(tài)的名稱(name)和類型(type。狀態(tài)描述器中還可能需要傳入一個用戶自定義函數(user-defined-function,UDF),用來說明處理邏輯,比如ReduceFunction和AggregateFunction。
2.3 按鍵分區(qū)狀態(tài)(Keyed State)
值狀態(tài)(ValueState)
顧名思義,狀態(tài)中只保存一個“值”(value)。
源碼如下:
//接口 T表示泛型,value表示可以是任何具體的數據類型。public interface ValueState<T> extends State { T value() throws IOException; //獲取當前狀態(tài)的值 void update(T value) throws IOException;//更新當前狀態(tài)的值(value即為新的值)}
在使用時,為了讓運行時上下文清楚到底是哪個狀態(tài),需要創(chuàng)建一個"狀態(tài)描述器"提供基本信息(StateDescriptor)
映射狀態(tài)(MapState)
以鍵值對(key-value)的形式將狀態(tài)整體保存起來
對應的MapState<UK, UV>接口中,UK、UV是泛型,分別表示保存的key和value的類型。
MapState提供了操作映射狀態(tài)的方法,與Map的使用非常類似。
列表狀態(tài)(ListState)
將狀態(tài)數據以列表(List)的形式組織起來。
ListState提供操作狀態(tài)的方法,使用方式與一般的List非常相似。
歸約狀態(tài)(ReducingState)
需要對添加進來的所有數據進行歸約,將歸約聚合之后的值作為狀態(tài)保存下來。
歸約邏輯的定義,是在歸約狀態(tài)描述器(ReducingStateDescriptor)中,通過傳入一個歸約函數(ReduceFunction)來實現的。
聚合狀態(tài)(AggregatingState)
聚合狀態(tài)也是一個值,用來保存添加進來的所有數據的聚合結果。
聚合邏輯是由在描述器中傳入一個更加一般化的聚合函數(AggregateFunction)來定義的,里面通過一個累加器(Accumulator)來表示狀態(tài)
2.4 算子狀態(tài)(Operator State)
列表狀態(tài)(ListState)
與Keyed State中的ListState一樣,將狀態(tài)表示為一組數據的列表。
與Keyed State中列表狀態(tài)的區(qū)別是:在算子狀態(tài)的上下文中,不會按鍵(key)分別處理狀態(tài),所以每一個并行子任務上只會保留一個“列表”,也就是當前并行子任務上所有狀態(tài)項的集合。
列表中的狀態(tài)項就是可以重新分配的最細粒度,彼此之間完全獨立。
當算子并行度進行縮放調整時,算子列表狀態(tài)中的所有元素項會被統一收集起來,相當于把多個分區(qū)的列表合并成了一個“大列表”,然后再均勻地分配給所有并行任務。這種“均勻分配”的具體方法就是“輪詢”(round-robin),通過逐一“發(fā)牌”的方式將狀態(tài)項平均分配的。
聯合列表狀態(tài)(UnionState)
與ListState類似,聯合列表狀態(tài)將將狀態(tài)表示為一個列表。
與ListState的區(qū)別在于算子并行度進行縮放調整時對于狀態(tài)的分配方式不同。
在并行度調整時,常規(guī)列表狀態(tài)是輪詢分配狀態(tài)項,而聯合列表狀態(tài)的算子則會直接廣播狀態(tài)的完整列表。
廣播狀態(tài)(BroadcastState)
一種特殊算子狀態(tài),所有分區(qū)的所有數據都會訪問到同一個狀態(tài),如同狀態(tài)被廣播到所有分區(qū)。
在并行度調整時,只要復制一份到新的并行任務就可以實現擴展。
3.總結
Flink狀態(tài):
Flink中的狀態(tài)是用來保存中間結果或者一些緩存數據,由一個任務維護。
Flink中的狀態(tài)可類比為本地變量,可以被任何業(yè)務邏輯訪問。
當Flink任務失敗時,可以使用狀態(tài)恢復數據,狀態(tài)始終與特定的算子相關聯。
算子需要預先注冊其狀態(tài),以便于Flink在運行時能夠了解算子狀態(tài)。
Flink狀態(tài)分類:托管狀態(tài)和原始狀態(tài)
托管狀態(tài)由Flink統一管理,原始狀態(tài)由用戶管理。
托管狀態(tài)分為:算子狀態(tài)(OperatorState)和按鍵分區(qū)狀態(tài)(KeyedState)。
Flink中的托管狀態(tài)(Manage)
current key OperatorState沒有current key的概念,KeyedState的數值總是與一個current key對應。
snapshot OperatorState 需要手動實現snapshot和restore方法,KeyedState由backend實現,對用戶透明。
heap OperatorState 只有堆內存一種實現,KeyedState由有堆內存和RocksDB兩種實現。
Size OperatorState 一般被認為是規(guī)模比較小的,KeyedState一般是相對規(guī)模較大的。
算子狀態(tài)(OperatorState)與按鍵分區(qū)狀態(tài)(KeyedState)的區(qū)別:
算子狀態(tài)分為:列表狀態(tài)(ListState)、廣播狀態(tài)(BroadcastState)、聯合列表狀態(tài)(UnionState)。
按鍵分區(qū)狀態(tài)分為: 值狀態(tài)(ValueState)、列表狀態(tài)(ListState)、映射狀態(tài)MapState)、文章來源:http://www.zghlxwxcb.cn/news/detail-720840.html
歸約狀態(tài)(ReducingState)、聚合狀態(tài)(AggregatingState)。文章來源地址http://www.zghlxwxcb.cn/news/detail-720840.html
到了這里,關于Flink狀態(tài)詳解:什么是時狀態(tài)(state)?狀態(tài)描述(StateDescriptor)及其重要性的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!