在容器化環境中擴展分佈式流式處理器

本文介紹了我們在Kubernetes中擴展分佈式流處理器的經驗。流處理器應該支持維護最佳的並行性。然而,添加更多的資源會帶來額外的成本,但卻不能保證性能的提升。相反,流處理器應該識別出資源需求級別,並進行相應的擴展。

關鍵要點

  • 流式處理器應該是可擴展的,用以滿足流數據處理不斷增長的業務需求。
  • 在容器化環境中擴展流式處理器必須在服務質量與相關成本之間做出權衡。
  • 流式處理器應該能夠通過水平擴展在容器化環境(例如運行在雲服務供應商上的Kubernetes)中利用這種權衡。
  • 在容器化環境中成功運行流式處理應用程序取決於爲每個流式處理器配置的硬件資源。
  • 爲容器化環境添加更多的硬件資源並不會帶來性能的提升。

由於對事件流式處理(即流式處理)應用程序的需求在不斷增長,數據流式處理近來已成爲數據分析領域的主要範式之一。各種流式處理應用已經出現在各個行業中,如電信、交通流量管理、人羣管理、健康信息學、網絡安全、金融,等等。

流式處理器是一種軟件平臺,讓用戶能夠更快地處理和響應傳入的數據流。市場上有很多流式處理器可供選擇,比如Flink、Heron、Kafka、Samza、Spark Streaming、Storm和WSO2 Stream Processor,它們都是開源流式處理器。

流式處理器的實時操作爲提升系統性能提供了高質量的服務。大多數現代流式處理器只需要少數幾個計算節點就可以處理90%的流式場景。但是,隨着業務的擴展,大多數有利可圖的企業需要處理越來越多的工作負載。因此,在選擇流式處理器時,必須選擇容易擴展且能夠處理更大工作負載的解決方案。

流式處理器越來越多地被部署成雲端的軟件即服務(SaaS),例如Amazon Kinesis Data Analytics、Microsoft Azure Stream Analytics、Google Cloud Dataflow,等等。基於雲的流式處理器爲在其上運行的流式處理應用程序提供了彈性擴展能力。以容器爲中心的管理環境(即容器化環境,例如Kubernetes)可以以可伸縮的方式運行流式處理應用程序。然而,由於硬件或軟件環境的異構性和數據流的特性,在容器化環境中何時以及如何進行分佈式流式處理器的縮放就成了一個非常重要的問題。本文介紹了一個數據密集型流式處理應用程序的實際應用場景,解釋瞭如何在Kubernetes中進行系統的擴展,以及相關的權衡。我們使用WSO2 Stream Processor(WSO2 SP)作爲示例流式處理器,因爲它是一個開源的適合實現此類應用程序的雲原生流式處理器。不過,我們認爲相同的概念也同樣適用於市場上其他雲原生流式處理器。

我們將提供一個真實的流式處理示例,與檢測惡意攻擊有關,在這個示例中,有人試圖進行未授權的Web服務器登錄,並造成服務器拒絕服務(DoS攻擊)。一旦檢測到此類DoS攻擊,系統就會向系統管理員發送警報,以便採取必要的安全措施。

使用Web服務器日誌檢測惡意攻擊

可以使用Web服務器日誌中捕獲的HTTP日誌事件來監控Web服務器的運行狀態。如果連續出現相同的HTTP狀態碼(例如401未授權或403禁止訪問),說明有人在嘗試惡意登錄Web服務器。401響應說明外部第三方嘗試訪問Web服務器的憑證已經被拒絕。狀態碼403說明服務器拒絕接受請求,儘管服務器知道如何處理該請求。

image

圖1:檢測對Web服務器的惡意攻擊

有幾種不同的方法可用來處理這種情況。但是,對於這種情況,信息安全專家更喜歡收到實時警報。如果這種惡意請求在三秒鐘內超過30次,並且訪問率((未授權請求次數+禁止訪問計數)/總請求次數)爲1.0,則需要拋出警報。我們使用流式處理器開發了一個警報生成程序(如圖1所示),這個處理器接收並處理來自Web服務器的日誌事件。爲了讓系統具備可擴展性,它被部署在運行在Google Compute Engine(GCE)上的Kubernetes集羣中。清單1顯示了用Siddhi查詢語言編寫的流式SQL代碼。我們將它稱爲Siddhi應用程序。

清單1:使用Siddhi流式SQL開發的惡意攻擊檢測程序

@App:name("MaliciousAttacksDetection")

@App:description("HTTP Log Processor for detecting malicious DoS attacks")

@source(type = 'kafka', partition.no.list='0', threading.option='single.thread', group.id="group", bootstrap.servers='kafka-service:9092', topic.list = 'attackDetectionTopic',

        @map(type = 'json'))

define stream inputStream ( iij_timestamp long, ip string, timestamp long, zone float, cik double, accession string, doc string, code float, size double, idx float, norefer float, noagent float, find float, crawler float, groupID int, browser string);

--Output of query 1: I want to know the IP of the malicious hosts which try to make unauthorized login attempts in short intervals.

@sink(type='log')

define stream outputStreamDoSAlert(ip string ,groupID int);

--The Actual latency of parallel siddhi apps are getting started to measure at this point

@info(name = "Query1")

@dist(execGroup='group11' ,parallel ='1')

from inputStream

select iij_timestamp, ip, timestamp, zone, cik, accession, doc, code, size, idx, norefer, noagent, find, crawler, groupID, browser, convert(time:timestampInMilliseconds(),'long') as injected_iijtimestamp

insert into interimInputStream;

--Query 2: Here all the accesses are either 401 or 403 and they have been done at least 30 times within 3 seconds time period.

@info(name = "Query2")

@dist(execGroup='group3', parallel ='12')

partition with (groupID of interimInputStream)

begin

   from interimInputStream#window.timeBatch(timestamp, 3 sec)

   select  ip, count() as totalAccessCount, sum(ifThenElse(code == 401F, 1, 0)) as unauthorizedCount, sum(ifThenElse(code == 403F, 1, 0)) as forbiddenCount,injected_iijtimestamp as iijtimestamp,groupID,timestamp

   insert into #interimStream3;

   from #interimStream3#throughput:throughput(iijtimestamp,"throughput",3,6,"outputStreamDoSAlert",30)

   select ip, totalAccessCount, (unauthorizedCount + forbiddenCount)/totalAccessCount as accessPercentage ,groupID

   insert into #interimStream5;

   from #interimStream5 [totalAccessCount > 30L and accessPercentage == 1.0]

   select ip ,groupID

   insert into outputStreamDoSAlert;

end;

我們將這個應用程序部署在分佈式流式處理器中,如圖2的部署架構圖所示。每個組件(如Worker-1、Worker-2……)都部署爲單個容器和單個Kubernetes pod。表1中列出了每個容器類別及其執行的任務。圖2顯示了整個系統被部署在六個Kubernetes節點中的方案。

表1:Kubernetes環境中不同類型容器執行的任務

image
我們將應用程序部署在Google Compute Engine的Kubernetes環境中。對於特定的工作負載P,系統應該提供特定的服務質量(QoS)值Q。我們根據Worker的延遲數量來測量Q的值(延遲是指事件進入Worker和事件退出Worker之間的時間差)。下面列出了集羣中部署的每個組件。

  • Web服務器託管在Node 1上;
  • 生產者組件託管在Node 2上,這個組件負責生成工作負載。它讀取Web服務器日誌並將它們發佈到運行在Node 4上的Kafka實例;
  • Node 2和Node 4運行流式處理器的兩個管理器;
  • 由gcloud自動生成的NFS託管在Node 3上;
  • Worker運行在Node 5和Node 6上,並負責處理實際的工作負載。它們從Kafka實例讀取數據,應用流式處理操作,並將結果寫回Kafka實例。

image

圖2:Kubernetes集羣的部署架構。

但是,隨着時間的推移,Web服務器上的工作負載也會增加,這是大多數Web服務器的典型特徵。工作負載可能會從P增加到到2P、4P、……、16P,等等。在這種情況下,運行Web服務器監控系統的企業需要維護Q’(QoS屬性的觀察值),讓它與Q相當。流式處理器應該能夠擴展到足以維持預期的QoS級別。請注意,可伸縮性是系統處理不斷增加的工作負載的能力。在本文中,我們着重關注負載可伸縮性,即隨着流量的增加,系統能夠正常運行。

有兩種方法可以實現負載可伸縮性:強伸縮和弱伸縮。強伸縮在保持問題規模不變的同時增加處理器的數量,弱伸縮也會增加處理器數量,但保持每個處理器的問題規模不變。在本文中,我們採用了弱伸縮,因爲我們遇到的是工作負載增加的情況。

實驗

我們使用了一個運行在Google Compute Engine(GCE)上的Kubernetes集羣。此外,我們使用的節點配備了2核CPU和7.5GB內存,用來託管pod。每個pod都有一個容器,每個容器中都部署了一個流式處理器(SP)組件。我們使用JDK 1.8、Kafka 2.0.1、WSO2-SP 4.3.0和MYSQL 5.7.4來構建docker鏡像。它們都作爲容器化的應用程序部署在集羣中。每個實驗需要40分鐘時間,包括10分鐘的預熱。請注意,我們使用符號x-y-z來表示(節點數)-(Worker數)-(實例數)。

我們使用EDGAR日誌文件數據集作爲實驗數據集,因爲它已經包含CSV格式的Web服務器日誌數據集。我們使用了EDGAR日誌文件數據集log20170325.csv。這個CSV文件中包含了22,146,382個事件,文件總大小約爲2.4GB,平均消息大小爲144字節,每條記錄有15個字段。清單2顯示了EDGAR數據集中的頭兩個記錄。

清單2:來自EDGAR日誌文件數據集的頭兩個記錄

ip,date,time,zone,cik,accession,extention,code,size,idx,norefer,noagent,find,crawler,browser

100.14.44.eca,2017-03-25,00:00:00,0.0,1031093.0,0001137171-10-000013,-index.htm,200.0,7926.0,1.0,0.0,0.0,10.0,0.0,

我們分別測量了三個級別的性能,即節點級別、容器級別和流式處理器級別。不過,本文得出的結論是基於流式處理器級別的延遲和吞吐量。我們在group3 Siddhi執行組中測量了這些值。我們使用Kubernetes集羣進行了六次不同的實驗,得出了表2所示的結果。

在表2中,ID對應於唯一的實驗標識符。節點數量對應於Kubernetes Worker的總數。實例數量是指Siddhi實例的數量。生產者數據速率(線程數)對應於生產者的數量,這些生產者通過從EDGAR數據文件讀取HTTP日誌事件來生成流事件。

結果

兩個節點、6個Worker、6個實例(2-6-6)導致單個工作負載生產者P的平均延遲爲390毫秒。這是最基本的情況。隨着工作負載的增加,延遲顯著增加,而吞吐量會隨之降低。我們將工作負載生成器線程增加到16,用以生成很大的工作負載。場景2顯示了這種情況。當我們將生成器線程增加16時,延遲增加了28.2%,吞吐量減少了29%。場景3表明,減少輸入數據項中唯一組ID的數量會使事情變得更糟(特別是在吞吐量方面)。這是因爲減少唯一組ID的數量會導致應用程序的並行性也減少。

如果我們將每個節點的Worker數量加倍,如場景4所示,我們將每個Worker的內存量減半。與情況1相比,吞吐量降低了三分之二,延遲增加了9倍。因此,Worker的內存使用量對應用程序的延遲起着重要作用。有時候,如果沒有提供足夠的內存,甚至無法部署Worker。

對於第5種場景,我們將Kubernetes Worker節點的數量增加到3個,總共有12個Worker,從而消除了性能瓶頸。現在添加了另一個節點(Node 7),每個節點有四個流式處理器。每個Worker需要1 GB內存,因此每個節點可以有4到5個Worker。因此,即使我們有3-6-6的系統設置(即三個節點,六個Worker和六個實例),只能獲得2-6-6的運行性能。但是,2-12-12和3-12-12的性能特徵會不一樣。

添加更多的硬件資源也無助於是。我們可以從實驗場景6中觀察到這一點。雖然與場景5相比,節點數量增加了一倍,但我們得到了與場景5相似的平均延遲。如果爲Worker添加更多的Siddhi部分應用程序,場景6的方法可能會有用,但這需要額外的內存。

表2:不同Kubernetes集羣的性能
image

當我們在Siddhi應用程序中使用分區時,Siddhi應用程序被分成若干個Siddhi應用程序,這些應用程序將根據分區屬性的唯一值分佈來獲得工作負載。表2最右邊的一列顯示了groupID字段使用的屬性惟一值的數量。在Siddhi應用程序中,我們使用groupID作爲分區屬性。因此,使用6個惟一值意味着只有6個部分Siddhi應用程序可以根據Siddhi的哈希映射獲得工作負載。我們將惟一groupID的數量增加到12來增加並行性。這意味着流將被定向到12個Siddhi部分應用程序。由於這些原因,並行性增加了,我們在場景1中獲得的延遲比場景3中的更好。

總結

可伸縮性是容器環境中的流式處理器面臨的一個重大挑戰,因爲應用程序的工作負載會隨着時間的推移而增加。本文分享了在Kubernetes環境中擴展此類分佈式流式處理器的經驗。爲此,流式處理器應該提供某種編程語言或查詢結構,以維持最佳的並行級別,而不管應用程序的初始規模如何。隨着工作負載的增加,需要提供足夠數量的硬件資源,讓系統可以保持足夠的QoS水平。添加更多資源會產生額外成本,但添加更多的資源並不能保證一定會獲得性能的提升。流式處理器需要能夠識別出資源需求級別,並擴展到可以保持最佳性能與成本比的級別。

關於作者

Sarangan Janakan是WSO2的實習軟件工程師,目前是斯里蘭卡莫拉圖瓦大學計算機科學與工程系的三年級本科生。他的研究興趣包括數據流式處理和雲計算。

Miyuru Dayarathna是WSO2的高級技術主管。他是一名計算機科學家,在流式計算、圖形數據管理和挖掘、雲計算、性能工程、信息安全等方面做出了貢獻。他還是斯里蘭卡莫拉圖瓦大學計算機科學與工程系的顧問。他最近在WSO2的研究重點是流式處理器和身份識別服務器。他已經在知名的國際期刊和會議上發表過技術論文。

查看英文原文:https://www.infoq.com/articles/distributed-stream-processor-container

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