做者|Stephan Ewen
整理|秦江傑本文整理自 Flink 創始公司 Ververica 聯合創始人兼 CTO - Stephan Ewen 在 Flink Forward China 2018 上的演講《Stream Processing takes on Everything》。
這個演講主題看似比較激進:流處理解決全部問題。不少人對於 Flink 可能還停留在最初的認知,以爲 Flink 是一個流處理引擎,實際上 Flink 能夠作不少其餘的工做,好比批處理、應用程序。在這個演講中,Stephan 首先會簡單說明他對 Flink 功能的觀點,而後深刻介紹一個特定領域的應用和事件處理場景。這個場景乍看起來不是一個流處理的使用場景,可是在 Stephan 看來,它實際上就是一個頗有趣的流處理使用場景。算法
上圖對爲何流處理能夠處理一切做出詮釋,將數據看作流是一個天然而又十分強大的想法。大部分數據的產生過程都是隨時間生成的流,好比一個 Petabyte 的數據不會憑空產生。這些數據一般都是一些事件的積累,好比支付、將商品放入購物車,網頁瀏覽,傳感器採樣輸出。數據庫
基於數據是流的想法,咱們對數據處理能夠有相應的理解。好比將過去的歷史數據看作是一個截止到某一時刻的有限的流,或是將一個實時處理應用當作是從某一個時刻開始處理將來到達的數據。可能在將來某個時刻它會中止,那麼它就變成了處理從開始時刻到中止時刻的有限數據的批處理。固然,它也有可能一直運行下去,不斷處理新到達的數據。這個對數據的重要理解方式很是強大,基於這一理解,Flink 能夠支持整個數據處理範疇內的全部場景。apache
最廣爲人知的 Flink 使用場景是流分析、連續處理(或者說漸進式處理),這些場景中 Flink 實時或者近實時的處理數據,或者採集以前提到的歷史數據而且連續的對這些事件進行計算。曉偉在以前的演講中提到一個很是好的例子來講明怎麼樣經過對 Flink 進行一些優化,進而能夠針對有限數據集作一些特別的處理,這使得 Flink 可以很好的支持批處理的場景,從性能上來講可以與最早進的批處理引擎相媲美。而在這根軸的另外一頭,是我今天的演講將要說明的場景 – 事件驅動的應用。這類應用廣泛存在於任何服務或者微服務的架構中。這類應用接收各種事件(多是 RPC 調用、HTTP 請求),而且對這些事件做出一些響應,好比把商品放進購物車,或者加入社交網絡中的某個羣組。緩存
在我進一步展開今天的演講以前,我想先對社區在 Flink 的傳統領域(實時分析、連續處理)近期所作的工做作一個介紹。Flink 1.7 在 2018 年 11 月 30 日已經發布。在 Flink 1.7 中爲典型的流處理場景加入了一些很是有趣的功能。好比我我的很是感興趣的在流式 SQL 中帶時間版本的 Join。一個基本想法是有兩個不一樣的流,其中一個流被定義爲隨時間變化的參照表,另外一個是與參照表進行 Join 的事件流。好比事件流是一個訂單流,參照表是不斷被更新的匯率,而每一個訂單須要使用最新的匯率來進行換算,並將換算的結果輸出到結果表。這個例子在標準的 SQL 當中實際上並不容易表達,但在咱們對 Streaming SQL 作了一點小的擴展之後,這個邏輯表達變得很是簡單,咱們發現這樣的表達有很是多的應用場景。網絡
另外一個在流處理領域十分強大的新功能是將復瑣事件處理(CEP)和 SQL 相結合。CEP 應用觀察事件模式。好比某個 CEP 應用觀察股市,當有兩個上漲後緊跟一個下跌時,這個應用可能作些交易。再好比一個觀察溫度計的應用,當它發現有溫度計在兩個超過 90 攝氏度的讀數以後的兩分鐘裏沒有任何操做,可能會進行一些操做。與 SQL 的結合使這類邏輯的表達也變得很是簡單。架構
第三個 Flink 1.7 中作了不少工做的功能是 Schema 升級。這個功能和基於流的應用緊密相關。就像你能夠對數據庫進行數據 Schema 升級同樣,你能夠修改 Flink 表中列的類型或者從新寫一個列。併發
另外我想簡單介紹的是流處理技術不只僅是簡單對數據進行計算,這還包括了不少與外部系統進行事務交互。流處理引擎須要在採用不一樣協議的系統之間以事務的方式移動數據,並保證計算過程和數據的一致性。這一部分功能也是在 Flink 1.7 中獲得了加強。異步
以上我對 Flink 1.7 的新功能向你們作了簡單總結。下面讓咱們來看看今天我演講的主要部分,也就是利用 Flink 來搭建應用和服務。我將說明爲何流處理是一個搭建應用和服務或者微服務的有趣技術。分佈式
我將從左邊這個高度簡化的圖提及,咱們一下子將聊一些其中的細節。首先咱們來看一個理解應用簡單的視角。如左圖所示,一個應用能夠是一個 Container,一個 Spring 應用,或者 Java 應用、Ruby 應用,等等。這個應用從諸如 RPC,HTTP 等渠道接收請求,而後依據請求進行數據庫變動。這個應用也可能調用另外一個微服務並進行下一步的處理。咱們能夠很是天然的想到進入到應用的這些請求能夠看作是個事件組成的序列,因此咱們能夠把它們看作是事件流。可能這些事件被緩存在消息隊列中,而應用會從消息隊列中消費這些事件進行處理,當應用須要響應一個請求時,它將結果輸出到另外一個消息隊列,而請求發送方能夠從這個消息隊列中消費獲得所發送請求的響應。在這張圖中咱們已經能夠看到一些有趣的不一樣。函數
第一個不一樣是在這張圖中應用和數據庫再也不是分開的兩個實體,而是被一個有狀態的流處理應用所代替。因此在流處理應用的架構中,再也不有應用和數據庫的鏈接了,它們被放到了一塊兒。這個作法有利有弊,但其中有些好處是很是重要的。首先是性能上的好處是明顯的,由於應用再也不須要和數據庫進行交互,處理能夠基於內存中的變量進行。其次這種作法有很好而且很簡單的一致性。
這張圖被簡化了不少,實際上咱們一般會有不少個應用,而不是一個被隔離的應用,不少狀況下你的應用會更符合這張圖。系統中有個接收請求的接口,而後請求被髮送到第一個應用,可能會再被髮到另外一個應用,而後獲得相應。在圖中有些應用會消費中間結果的流。這張圖已經展現了爲何流處理是更適合比較複雜的微服務場景的技術。由於不少時候系統中不會有一個直接接收用戶請求並直接響應的服務,一般來講一個微服務須要跟其餘微服務通訊。這正如在流處理的架構中不一樣應用在建立輸出流,同時基於衍生出的流再建立並輸出新的流。
到目前爲止,咱們看到的內容多少還比較直觀。而對基於流處理技術的微服務架構而言,人們最常問的一個問題是如何保證事務性?若是系統中使用的是數據庫,一般來講都會有很是成熟複雜的數據校驗和事務模型。這也是數據庫在過去許多年中十分紅功的緣由。開始一個事務,對數據作一些操做,提交或者撤銷一個事務。這個機制使得數據完整性獲得了保證(一致性,持久性等等)。
那麼在流處理中咱們怎麼作到一樣的事情呢?做爲一個優秀的流處理引擎,Flink 支持了剛好一次語義,保證了每一個事件只會被處理一遍。可是這依然對某些操做有限制,這也成爲了使用流處理應用的一個障礙。咱們經過一個很是簡單流處理應用例子來看咱們能夠作一些什麼擴展來解決這個問題。咱們會看到,解決辦法其實出奇的簡單。
讓咱們以這個教科書式的事務爲例子來看一下事務性應用的過程。這個系統維護了帳戶和其中存款餘額的信息。這樣的信息多是銀行或者在線支付系統的場景中用到的。假設咱們想要處理相似下面的事務:若是帳戶 A 中的餘額大於 100,那麼從帳戶 A 中轉帳 50 元到帳戶 B。這是個很是簡單的兩個帳戶之間進行轉帳的例子。
數據庫對於這樣的事務已經有了一個核心的範式,也就是原子性,一致性,隔離性和持久性(ACID)。這是可以讓用戶放心使用事務的幾個基本保證。有了他們,用戶不用擔憂錢在轉帳過程當中會丟失或者其餘問題。讓咱們用這個例子來放到流處理應用中,來讓流處理應用也能提供和數據相同的 ACID 支持:
原子性要求一個轉帳要不就徹底完成,也就是說轉帳金額從一個帳戶減小,並增長到另外一個帳戶,要不就兩個帳戶的餘額都沒有變化。而不會只有一個帳戶餘額改變。不然的話錢就會憑空減小或者憑空增長。
一致性和隔離性是說若是有不少用戶同時想要進行轉帳,那麼這些轉帳行爲之間應該互不干擾,每一個轉帳行爲應該被獨立的完成,而且完成後每一個帳戶的餘額應該是正確的。也就是說若是兩個用戶同時操做同一個帳戶,系統不該該出錯。
持久性指的是若是一個操做已經完成,那麼這個操做的結果會被妥善的保存而不會丟失。
咱們假設持久性已經被知足。一個流處理器有狀態,這個狀態會被 checkpoint,因此流處理器的狀態是可恢復的。也就是說只要咱們完成了一個修改,而且這個修改被 checkpoint 了,那麼這個修改就是持久化的。
讓咱們來看看另外三個例子。設想一下,若是咱們用流處理應用來實現這樣一個轉帳系統會發生什麼。咱們先把問題簡化一些,假設轉帳不須要有條件,僅僅是將 50 元從帳戶 A 轉到帳戶,也就是說帳戶 A 的餘額減小 50 元而帳戶 B 的餘額增長 50 元。咱們的系統是一個分佈式的並行系統,而不是一個單機系統。簡單起見咱們假設系統中只有兩臺機器,這兩臺機器能夠是不一樣的物理機或者是在 YARN 或者 Kubernetes 上不一樣的容器。總之它們是兩個不一樣的流處理器實例,數據分佈在這兩個流處理器上。咱們假設帳戶 A 的數據由其中一臺機器維護,而帳戶 B 的數據有另外一臺機器維護。
如今咱們要作個轉帳,將 50 元從帳戶 A 轉移到帳戶 B,咱們把這個請求放進隊列中,而後這個轉帳請求被分解爲對帳戶 A 和 B 分別進行操做,而且根據鍵將這兩個操做路由到維護帳戶 A 和維護帳戶 B 的這兩臺機器上,這兩臺機器分別根據要求對帳戶 A 和帳戶 B 的餘額進行改動。這並非事務操做,而只是兩個獨立無心義的改動。一旦咱們將轉帳的請求改的稍微複雜一些就會發現問題。
下面咱們假設轉帳是有條件的,咱們只想在帳戶 A 的餘額足夠的狀況下才進行轉帳,這樣就已經有些不太對了。若是咱們仍是像以前那樣操做,將這個轉帳請求分別發送給維護帳戶 A 和 B 的兩臺機器,若是 A 沒有足夠的餘額,那麼 A 的餘額不會發生變化,而 B 的餘額可能已經被改動了。咱們就違反了一致性的要求。
咱們看到咱們須要首先以某種方式統一作出是否須要更改餘額的決定,若是這個統一的決定中餘額須要被修改,咱們再進行修改餘額的操做。因此咱們先給維護 A 的餘額的機器發送一個請求,讓它查看 A 的餘額。咱們也能夠對 B 作一樣的事情,可是這個例子裏面咱們不關心 B 的餘額。而後咱們把全部這樣的條件檢查的請求彙總起來去檢驗條件是否知足。由於 Flink 這樣的流處理器支持迭代,若是知足轉帳條件,咱們能夠把這個餘額改動的操做放進迭代的反饋流當中來告訴對應的節點來進行餘額修改。反之若是條件不知足,那麼餘額改動的操做將不會被放進反饋流。這個例子裏面,經過這種方式咱們能夠正確的進行轉帳操做。從某種角度上來講咱們實現了原子性,基於一個條件咱們能夠進行所有的餘額修改,或者不進行任何餘額修改。這部分依然仍是比較直觀的,更大的困難是在於如何作到併發請求的隔離性。
假設咱們的系統沒有變,可是系統中有多個併發的請求。咱們在以前的演講中已經知道,這樣的併發可能達到每秒鐘幾十億條。如圖,咱們的系統可能從兩個流中同時接受請求。若是這兩個請求同時到達,咱們像以前那樣將每一個請求拆分紅多個請求,首先檢查餘額條件,而後進行餘額操做。然而咱們發現這會帶來問題。管理帳戶 A 的機器會首先檢查 A 的餘額是否大於 50,而後又會檢查 A 的餘額是否大於 100,由於兩個條件都知足,因此兩筆轉帳操做都會進行,但實際上帳戶 A 上的餘額可能沒法同時完成兩筆轉帳,而只能完成 50 元或者 100 元的轉帳中的一筆。這裏咱們須要進一步思考怎麼樣來處理併發的請求,咱們不能只是簡單地併發處理請求,這會違反事務的保證。從某種角度來講,這是整個數據庫事務的核心。數據庫的專家們花了一些時間提供了不一樣解決方案,有的方案比較簡單,有的則很複雜。但全部的方案都不是那麼容易,尤爲是在分佈式系統當中。
在流處理中怎麼解決這個問題呢?直覺上講,若是咱們可以讓全部的事務都按照順序依次發生,那麼問題就解決了,這也被成爲可序列化的特性。可是咱們固然不但願全部的請求都被依次順序處理,這與咱們使用分佈式系統的初衷相違背。因此咱們須要保證這些請求最後的產生的影響看起來是按照順序發生的,也就是一個請求產生的影響是基於前一個請求產生影響的基礎之上的。換句話說也就是一個事務的修改須要在前一個事務的全部修改都完成後才能進行。這種但願一件事在另外一件事以後發生的要求看起來很熟悉,這彷佛是咱們之前在流處理中曾經遇到過的問題。是的,這聽上去像是事件時間。用高度簡化的方式來解釋,若是全部的請求都在不一樣的事件時間產生,即便因爲種種緣由他們到達處理器的時間是亂序的,流處理器依然會根據他們的事件時間來對他們進行處理。流處理器會使得全部的事件的影響看上去都是按順序發生的。按事件時間處理是 Flink 已經支持的功能。
那麼詳細說來,咱們到底怎麼解決這個一致性問題呢?假設咱們有並行的請求輸入並行的事務請求,這些請求讀取某些表中的記錄,而後修改某些表中的記錄。咱們首先須要作的是把這些事務請求根據事件時間順序擺放。這些請求的事務時間不可以相同,可是他們之間的時間也須要足夠接近,這是由於在事件時間的處理過程當中會引入必定的延遲,咱們須要保證所處理的事件時間在向前推動。所以第一步是定義事務執行的順序,也就是說須要有一個聰明的算法來爲每一個事務制定事件時間。
在圖上,假設這三個事務的事件時間分別是 T+2, T 和 T+1。那麼第二個事務的影響須要在第一和第三個事務以前。不一樣的事務所作的修改是不一樣的,每一個事務都會產生不一樣的操做請求來修改狀態。咱們如今須要將對訪問每一個行和狀態的事件進行排序,保證他們的訪問是符合事件時間順序的。這也意味着那些相互之間沒有關係的事務之間天然也沒有了任何影響。好比這裏的第三個事務請求,它與前兩個事務之間沒有訪問共同的狀態,因此它的事件時間排序與前兩個事務也相互獨立。而當前兩個事務之間的操做的到達順序與事件時間不符時,Flink 則會依據它們的事件時間進行排序後再處理。
必須認可,這樣說仍是進行了一些簡化,咱們還須要作一些事情來保證高效執行,可是整體原則上來講,這就是所有的設計。除此以外咱們並不須要更多其餘東西。
爲了實現這個設計,咱們引入了一種聰明的分佈式事件時間分配機制。這裏的事件時間是邏輯時間,它並不須要有什麼現實意義,好比它不須要是真實的時鐘。使用 Flink 的亂序處理能力,而且使用 Flink 迭代計算的功能來進行某些前提條件的檢查。這些就是咱們構建一個支持事務的流處理器的要素。
咱們實際上已經完成了這個工做,稱之爲流式帳簿(Streaming Ledger),這是個在 Apache Flink 上很小的庫。它基於流處理器作到了知足 ACID 的多鍵事務性操做。我相信這是個很是有趣的進化。流處理器一開始基本上沒有任何保障,而後相似 Storm 的系統增長了至少一次的保證。但顯然至少一次依然不夠好。而後咱們看到了剛好一次的語義,這是一個大的進步,但這只是對於單行操做的剛好一次語義,這與鍵值庫很相似。而支持多行剛好一次或者多行事務操做將流處理器提高到了一個能夠解決傳統意義上關係型數據庫所應用場景的階段。
Streaming Ledger 的實現方式是容許用戶定義一些表和對這些表進行修改的函數。
Streaming Ledger 會運行這些函數和表,全部的這些一塊兒編譯成一個 Apache Flink 的有向無環圖(DAG)。Streaming Ledger 會注入全部事務時間分配的邏輯,以此來保證全部事務的一致性。
搭建這樣一個庫並不難,難的是讓它高性能的運行。讓咱們來看看它的性能。這些性能測試是幾個月以前的,咱們並無作什麼特別的優化,咱們只是想看看一些最簡單的方法可以有什麼樣的性能表現。而實際性能表現看起來至關不錯。若是你看這些性能條造成的階梯跨度,隨着流處理器數量的增加,性能的增加至關線性。
在事務設計中,沒有任何協同或者鎖參與其中。這只是流處理,將事件流推入系統,緩存一小段時間來作一些亂序處理,而後作一些本地狀態更新。在這個方案中,沒有什麼特別代價高昂的操做。在圖中性能增加彷佛超過了線性,我想這主要是由於 JAVA 的 JVM 當中 GC 的工做緣由致使的。在 32 個節點的狀況下咱們每秒能夠處理大約兩百萬個事務。爲了與數據庫性能測試進行對比,一般當你看數據庫的性能測試時,你會看到相似讀寫操做比的說明,好比 10% 的更新操做。而咱們的測試使用的是 100% 的更新操做,而每一個寫操做至少更新在不一樣分區上的 4 行數據,咱們的表的大小大約是兩億行。即使沒有任何優化,這個方案的性能也很是不錯。
另外一個在事務性能中有趣的問題是當更新的操做對象是一個比較小的集合時的性能。若是事務之間沒有衝突,併發的事務處理是一個容易的事情。若是全部的事務都獨立進行而互不干擾,那這個不是什麼難題,任何系統應該都能很好的解決這樣的問題。
當全部的事務都開始操做同一些行時,事情開始變得更有趣了,你須要隔離不一樣的修改來保證一致性。因此咱們開始比較一個只讀的程序、一個又讀又寫可是沒有寫衝突的程序和一個又讀又寫並有中等程度寫衝突的程序這三者之間的性能。你能夠看到性能表現至關穩定。這就像是一個樂觀的併發衝突控制,表現很不錯。那若是咱們真的想要針對這類系統的阿喀琉斯之踵進行考驗,也就是反覆的更新同一個小集合中的鍵。
在傳統數據庫中,這種狀況下可能會出現反覆重試,反覆失敗再重試,這是一種咱們總想避免的糟糕狀況。是的,咱們的確須要付出性能代價,這很天然,由於若是你的表中有幾行數據每一個人都想更新,那麼你的系統就失去了併發性,這自己就是個問題。可是這種狀況下,系統並沒崩潰,它仍然在穩定的處理請求,雖然失去了一些併發性,可是請求依然可以被處理。這是由於咱們沒有衝突重試的機制,你能夠認爲咱們有一個基於亂序處理自然的衝突避免的機制,這是一種很是穩定和強大的技術。
咱們還嘗試了在跨地域分佈的狀況下的性能表現。好比咱們在美國、巴西,歐洲,日本和澳大利亞各設置了一個 Flink 集羣。也就是說咱們有個全球分佈的系統。若是你在使用一個關係型數據庫,那麼你會付出至關高昂的性能代價,由於通訊的延遲變得至關高。跨大洲的信息交互比在同一個數據中心甚至同一個機架上的信息交互要產生大得多的延遲。
可是有趣的是,流處理的方式對延遲並非十分敏感,延遲對性能有所影響,可是相比其它不少方案,延遲對流處理的影響要小得多。因此,在這樣的全球分佈式環境中執行分佈式程序,的確會有更差的性能,部分緣由也是由於跨大洲的通訊帶寬不如統一數據中內心的帶寬,可是性能表現依然不差。
實際上,你能夠拿它當作一個跨地域的數據庫,同時仍然可以在一個大概 10 個節點的集羣上得到每秒幾十萬條事務的處理能力。在這個測試中咱們只用了 10 個節點,每一個大洲兩個節點。因此 10 個節點能夠帶來全球分佈的每秒 20 萬事務的處理能力。我認爲這是頗有趣的結果,這是由於這個方案對延遲並不敏感。
我已經說了不少利用流處理來實現事務性的應用。可能聽起來這是個很天然的想法,從某種角度上來講的確是這樣。可是它的確須要一些很複雜的機制來做爲支撐。它須要一個連續處理而非微批處理的能力,須要可以作迭代,須要複雜的基於事件時間處理亂序處理。爲了更好地性能,它須要靈活的狀態抽象和異步 checkpoint 機制。這些是真正困難的事情。這些不是由 Ledger Streaming 庫實現的,而是 Apache Flink 實現的,因此即便對這類事務性的應用而言,Apache Flink 也是真正的中流砥柱。
至此,咱們能夠說流處理不只僅支持連續處理、流式分析、批處理或者事件驅動的處理,你也能夠用它作事務性的處理。固然,前提是你有一個足夠強大的流處理引擎。這就是我演講的所有內容。
本文爲雲棲社區原創內容,未經容許不得轉載。