目前為止,已經討論了機器學習和批處理模式的數據挖掘。現在審視持續處理流數據,實時檢測其中的事實和模式,好像從湖泊來到了河流。
先研究一下不斷改變的動態環境帶來的挑戰,在列出流處理應用的先決條件(如,與Twitter的TCP Sockets)之后, 結合Spark, Kafka and Flume 把數據放入一個低延遲,高吞吐量,可縮放的處理流水線。
要點如下:
? 分析流式應用架構的挑戰,約束和需求
? 利用Spark Streaming 從 TCP socket 中處理實時數據
? 連接 Twitter 服務,準實時解析 tweets
? 使用 Spark, Kafka 和 Flume 建立一個可靠,容錯,可縮放,高吞吐量,低延遲的集成應用
? 以 Lambda 和 Kappa 的架構范式結尾
Spark Streaming在數據密集型應用中的位置
按照慣例, 先看一下最初的數據密集型應用架構,指明我們所感興趣的 Spark Streaming 模塊的所處位置.
下圖著重指明了整體架構中的Spark Streaming模塊,Spark SQL和 Spark MLlib:
數據流可以來自股票市場的時序分析,企業交易,各種交互,事件,web流量,點擊流,和傳感器數據等,都是及時且帶有時間戳的數據。用例有欺詐檢測和防偽,移動的交叉銷售和銷售提升,或者交通預警。這些數據流需要及時處理以便于監測,例如 異常檢測,異常奇點,垃圾郵件,欺詐和入侵; 也可提供基礎的統計,見解,趨勢,和推薦。 某些情形,總結性的匯總信息需要存儲以備將來使用。從架構范式的角度看,我們從面向服務的架構轉為事件驅動的架構。
數據的流處理有兩個模型:
? 在數據到達時及時處理每條記錄,在處理前不需要在容器中緩存記錄,比如
Twitter's Storm, Yahoo's S4, 和 Google's MillWheel.
? 微型批處理 或者小間隔執行批處理計算,例如
Spark Streaming and Storm Trident. 根據微型批處理設置的時間窗口,將流入的數據記錄緩沖到一個容器中。
Spark Streaming 經常用來與 Storm 比較,這是兩種不同的流數據模型。 Spark Streaming 基于微型批處理,而 Storm 基于及時處理流入的數據。 Storm 也提供了微型批處理選項 即 Storm Triden。
流處理應用的主要驅動因素的時間延遲。時延從 RPC (short for Remote Procedure Call) 的毫秒 到幾秒 到微信批處理方案的幾分鐘不等。
RPC 允許請求程序與遠程服務器之間的同步操作。線程允許多個 RPC 調用的并發。
分布式RPC 模型實現的一個實例就是 Apache Storm.
Storm 實現了無邊界元組的無狀態毫秒級延遲處理,結合數據流作為噴發源使用了拓撲或定向環圖的及時,提供了過濾, join, 聚合和轉換.
Storm 也實現了一個高層抽象叫做 Trident , 與Spark類似, 以微型批處理進行流式數據處理。
考慮時延的限制, 從毫秒到秒級, Storm 是個很好的候選. 對于秒到分鐘級, Spark Streaming and Storm Trident
是最佳選擇。 對于若干分鐘以上, Spark 和 NoSQL 數據庫如
Cassandra 和HBase 是合適的方案. 對于多小時運算海量數據,Hadoop 是理想的競爭者。
盡管吞吐量逾時延相關,但不是一個簡單的反線性關系。
如果處理一條消息需要2 ms, 只取決于延遲的情況, 可以假設吞吐量限制在500條消息每秒。如果緩存了8ms的數據,那么批處理允許更高的吞吐量。 10 ms 時延, 系統可以緩沖10,000 條消息. 為了一個可忍受的時延增長,我們要顯著的增加吞吐量。這是Spark Streaming 微型批處理的一個魔法。
Spark Streaming 內部工作方式
Spark Streaming 充分利用了 Spark 的核心架構,作為流處理功能的入口點,它構建在 SparkContext 的 StreamingContext 之上. 集群管理器將至少單獨分片一個工作節點作為接收器,這是一個長時間運行的任務執行器來處理進入的流數據。
執行器創建 Discretized Streams 或者從輸入數據流中得來的 DStreams, DStream 默認為另一個worker 的緩存。 接收器服務于輸入數據流,多個接收器提升了并行性,產生多個DStreams , Spark 用它unite 或 join RDD.
下圖給出了 Spark Streaming 的那邊工作概要。客戶端通過集群管理器與
Spark 集群交互 ,Spark Streaming 有一個專屬的worker 運行著長任務來接待輸入的數據流,轉化為 discretized streams 或DStreams. 接收器采集,緩存,復制數據,然后放入一個 RDDs的流.
Spark 接收器能夠從多種數據源接收數據。核心的輸入源包括從TCP socket 和 HDFS/Amazon S3 到 Akka Actors. 其它的數據源包括
Apache Kafka, Apache Flume, Amazon Kinesis, ZeroMQ, Twitter, 以及其它用戶定義的接收器。
我們可以區分資源是否可靠,可靠的數據源可以確認數據的接收憑證以及副本能否重發,非可靠數據源指接收器不能確認消息的接收憑證。 Spark 可以對workers,分區和接收器的數量進行縮放。
下圖給出了Spark Streaming 可能的數據源和持久化選項:
Spark Streaming 的底層基礎
Spark Streaming 由接收器, Discretized Streams 和 用于持久化的Spark Connectors 組成.
作為 Spark Core, 基本的數據結構是 RDD, 基本編程抽是 Discretized Stream 或 DStream.
下圖解釋了看做 RDDs連續序列的 Discretized Streams. DStream 的批處理間隔是可配置的。
DStreams 是在批處理間隔是對流入數據的快照。時間步長可以從500ms 到幾秒,DStream 的底層結構是一個 RDD.
一個DStream 基本上是 RDDs的一個連續序列。這使得Spark Streaming 非常強大,可以充分利用Spark Core 中已有的函數,數據轉換和動作, 可以與Spark SQL 對話, 在流入的數據流上執行SQL 查詢和 Spark MLlib. 通用的及基于鍵值對RDDs的數據轉換同樣適用。DStreams 得益于 RDDs 內部體系和容錯機制。 discretized stream 有額外的轉換和輸出操作。DStream 的大多數通用操作是 transform 和 foreachRDD.
下圖給出了 DStreams的生命周期,將消息通過微型批處理實現創建成 RDDs ,RDDs上的數據轉換函數和動作 觸發了 Spark jobs. 從上而下分布解釋:
- 輸入流中, 流入的消息緩存在容器中,時間窗口有微型批處理分配.
- 在 discretized stream 步驟中, 緩存的微型批處理轉化成 DStream RDDs.
- Mapped DStream 是通過使用轉換函數從原始的 DStream中獲得的. 這三個步驟構成了在預定義時間窗口的所接收數據的轉換。 底層的數據結構是RDD, 我們保留了轉換中的數據世系。
-
最后一步是 RDD上的一個動作,觸發了Spark job.
5-5 Dstream
數據轉換可以無狀態的或者有狀態的。無狀態意味著沒有狀態需要程序維護,而有狀態意味著程序保持狀態,前面所記住的操作可能影響當前的操作。一個有狀態操作需要或修改一些系統的狀態,無狀態操作則不是。 無狀態數據轉換在一個時間點處理每個批處理中的一個
DStream. 有狀態數據轉換處理多個批處理來獲得結果,需要可配置的目錄檢查點。檢查點是容錯體系的主要機制, Spark Streaming 周期性存儲一個應用的數據和元數據。Spark Streaming 中的有狀態數據轉換有兩種類型:updateStateByKey 和 windowed transformations.
updateStateByKey 維護了在 Pair RDD 中一個流中的每個鍵值的狀態。當DStream中的鍵值漲停被更新時返回一個新的狀態。 一個例子是tweets的stream 中運行了given hashtags 的個數統計。Windowed transformations 在多個批處理的滑動窗口中執行。一個窗口有一個定義好的長度或時間單元的區間,是多個單一
DStream 處理的間隔,定義了一個窗口轉換中有多少個批處理或窗口轉換中計算的頻率。
下面的 schema 在 DStreams上的窗口操作,在給定長度和滑動間隔時生成 window DStreams :
函數示例是 countByWindow (windowLength, slideInterval). 在 DStream上的一個滑動窗口中,計算每個單元素RDD的元素個數,返回一個新的DStream. 通過每60秒計算一次tweets流中指定的hashtags的數據作為示例的解釋。 窗口的時間幀是指定的。分鐘級的窗口長度是有道理的。
由于是計算和內存密集型的,所以小時級的窗口長度是不推薦的。在Cassandra 或 HBase 這樣的數據中聚合數據更為方便。窗口數據轉換計算出的結果是基于窗口長度和窗口滑動間隔的。
Spark 的性能受窗口長度,窗口滑動間隔,和持久化的影響。
構建容錯系統
實時流處理系統必須是 24/7可用的,這需要在各種錯誤發生時系統是可靠的。 Spark 和 RDD 抽象 被設計成可以無縫處理集群中任何節點的故障.
Spark Streaming 容錯處理的主要機制是 核對檢驗點,自動重啟驅動, 和自動故障切換。檢驗點中存儲了應用的狀態,使它 Spark能夠從驅動故障中國年恢復。
Spark Version 1.2 通過寫前日志, 可靠的接收器, 和文件流保證了數據的零丟失。 寫前日志代表了接收數據的容錯存儲。出現故障要求重新計算結果。
DStream 操著有著準確的語意。數據轉換可能計算多次,但產生相同的結果。 DStream 輸出操作至少有一個語意。輸出操作也可能執行多次。
以TCP sockets處理實時數據
理解了流操作的基礎,先在TCP socket 上作實驗。 TCP socket 建立在客戶端和服務器之間 的雙工通信,通過已建的連接交互數據。 WebSocket 與Http 連接不一樣,是長連接。
HTTP 不會在server側保持一個連接并連續推送數據到web瀏覽器。很多 web應用因而通過AJAX和XML請求采用了長輪詢 . WebSockets 是HTML5 中實現的標準,支持現代的 web瀏覽器并稱為了實時通訊的跨平臺標準。
創建TCP sockets
運行netcat 創建一個 TCP Socket Server, 這是Linux系統中的一個小工具可以作為數據服務器,命令如下:
#
# Socket Server
#
an@an-VB:~$ nc -lk 9999
hello world
how are you
hello world
cool it works
一旦 netcat運行起來, 以Spark Streaming 的客戶端打開第二個控制臺來接收和處理數據。 一旦 Spark Streaming 客戶端控制臺監聽我們的鍵入單詞并處理, 就是反饋, hello world.
處理實時數據
我們將使用 Spark 提供的Spark
Streaming 示例程序 network_wordcount.py. 可以從GitHub 獲得 https://github.com/apache/spark/blob/master/examples/src/main/python/streaming/network_wordcount.py.
代碼如下:
"""
Counts words in UTF8 encoded, '\n' delimited text received from the
network every second.
Usage: network_wordcount.py <hostname> <port>
<hostname> and <port> describe the TCP server that Spark Streaming
would connect to receive data.
To run this on your local machine, you need to first run a Netcat
server
`$ nc -lk 9999`
and then run the example
`$ bin/spark-submit examples/src/main/python/streaming/network_
wordcount.py localhost 9999`
"""
from __future__ import print_function
import sys
from pyspark.streaming import StreamingContext
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: network_wordcount.py <hostname> <port>",
file=sys.stderr)
exit(-1)
sc = SparkContext(appName="PythonStreamingNetworkWordCount")
ssc = StreamingContext(sc, 1)
lines = ssc.socketTextStream(sys.argv[1], int(sys.argv[2]))
counts = lines.flatMap(lambda line: line.split(" "))\
.map(lambda word: (word, 1))\
.reduceByKey(lambda a, b: a+b)
ssc.start()
ssc.awaitTermination()
解釋一下程序的過程:
- 第一行代碼初始化 :
ssc = StreamingContext(sc, 1)
2.接著, 建立流式計算.
連接localhost 或 127.0.0.1 的9999端口從接收數據中得到一個或多個 DStream 對象 :
stream = ssc.socketTextStream("127.0.0.1", 9999)
定義DStream 的計算: 數據轉換和輸出操作:
stream.map(x: lambda (x,1)) .reduce(a+b) .print()
開始計算:
ssc.start()
手工刮起活錯誤處理完成時中斷程序:
ssc.awaitTermination()
當到達完成條件時,可以手工完成處理:
ssc.stop()
通過瀏覽Spark 的監測主頁localhost:4040 可以監測Spark Streaming 流應用 .
這里是程序運行的結果以及netcat記錄的字符:
#
# Socket Client
#
an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6$ ./bin/spark-submit
examples/src/main/python/streaming/network_wordcount.py localhost 9999
連接localhost 的9999端口 運行Spark Streaming network_count 程序:
an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6$ ./bin/spark-submit examples/
src/main/python/streaming/network_wordcount.py localhost 9999
-------------------------------------------
Time: 2015-10-18 20:06:06
-------------------------------------------
(u'world', 1)
(u'hello', 1)
-------------------------------------------
Time: 2015-10-18 20:06:07
-------------------------------------------
. . .
-------------------------------------------
Time: 2015-10-18 20:06:17
-------------------------------------------
(u'you', 1)
(u'how', 1)
(u'are', 1)
-------------------------------------------
Time: 2015-10-18 20:06:18
-------------------------------------------
. . .
-------------------------------------------
Time: 2015-10-18 20:06:26
-------------------------------------------
(u'', 1)
(u'world', 1)
(u'hello', 1)
因此, 已經建立了本地9999端口的socket連接,流式處理
netcat 發送的數據 , 執行發送消息的單純統計。
實時控制Twitter數據
Twitter 提供了兩個 APIs. 搜索 API允許我們根據搜索條目獲得過去的 tweets. 這就是我們前面章節如何從Twitter采集數據的方式。我們的目的是通過,Twitter 提供的實時streaming API ,從博客世界接收用戶剛發送的 tweets .
實時處理Tweets
下面的程序連接了 Twitter服務,處理流入的 tweets單不包括已刪除或無效的 tweets,實時解析相關tweet提取 screen 名稱, 或 tweet text, retweet count, geo-location 信息. 處理過的 tweets 通過Spark Streaming 收集到一個RDD Queue,然后每隔一秒顯示在控制臺上 :
"""
Twitter Streaming API Spark Streaming into an RDD-Queue to process
tweets live Create a queue of RDDs that will be mapped/reduced one at a time in 1 second intervals.
To run this example use
'$ bin/spark-submit examples/AN_Spark/AN_Spark_Code/s07_
twitterstreaming.py'
"""
#
import time
from pyspark import SparkContext
from pyspark.streaming import StreamingContext
import twitter
import dateutil.parser
import json
# Connecting Streaming Twitter with Streaming Spark via Queue
class Tweet(dict):
def __init__(self, tweet_in):
super(Tweet, self).__init__(self)
if tweet_in and 'delete' not in tweet_in:
self['timestamp'] = dateutil.parser.parse(tweet_
in[u'created_at']
).replace(tzinfo=None).isoformat()
self['text'] = tweet_in['text'].encode('utf-8')
#self['text'] = tweet_in['text']
self['hashtags'] = [x['text'].encode('utf-8') for x in
tweet_in['entities']['hashtags']]
#self['hashtags'] = [x['text'] for x in tweet_
in['entities']['hashtags']]
self['geo'] = tweet_in['geo']['coordinates'] if tweet_
in['geo'] else None
self['id'] = tweet_in['id']
self['screen_name'] = tweet_in['user']['screen_name'].
encode('utf-8')
#self['screen_name'] = tweet_in['user']['screen_name']
self['user_id'] = tweet_in['user']['id'`
def connect_twitter():
twitter_stream = twitter.TwitterStream(auth=twitter.OAuth(
token = "get_your_own_credentials",
token_secret = "get_your_own_credentials",
consumer_key = "get_your_own_credentials",
consumer_secret = "get_your_own_credentials"))
return twitter_stream
def get_next_tweet(twitter_stream):
stream = twitter_stream.statuses.sample(block=True)
tweet_in = None
while not tweet_in or 'delete' in tweet_in:
tweet_in = stream.next()
tweet_parsed = Tweet(tweet_in)
return json.dumps(tweet_parsed)
def process_rdd_queue(twitter_stream):
# Create the queue through which RDDs can be pushed to
# a QueueInputDStream
rddQueue = []
for i in range(3):
rddQueue += [ssc.sparkContext.parallelize([get_next_
tweet(twitter_stream)], 5)]
lines = ssc.queueStream(rddQueue)
lines.pprint()
if __name__ == "__main__":
sc = SparkContext(appName="PythonStreamingQueueStream")
ssc = StreamingContext(sc, 1)
# Instantiate the twitter_stream
twitter_stream = connect_twitter()
# Get RDD queue of the streams json or parsed
process_rdd_queue(twitter_stream)
ssc.start()
time.sleep(2)
ssc.stop(stopSparkContext=True, stopGraceFully=True)
運行這個程序,有如下輸出:
an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6$ bin/spark-submit examples/
AN_Spark/AN_Spark_Code/s07_twitterstreaming.py
Time: 2015-11-03 21:53:14
{"user_id": 3242732207, "screen_name": "cypuqygoducu", "timestamp":
"2015-11-03T20:53:04", "hashtags": [], "text": "RT @VIralBuzzNewss:
Our Distinctive Edition Holiday break Challenge Is In this article!
Hooray!... - https://t.co/9d8wumrd5v https://t.co/\u2026", "geo": null,
"id": 661647303678259200}
Time: 2015-11-03 21:53:15
{"user_id": 352673159, "screen_name": "melly_boo_orig", "timestamp":
"2015-11-03T20:53:05", "hashtags": ["eminem"], "text": "#eminem
https://t.co/GlEjPJnwxy", "geo": null, "id": 661647307847409668}
Time: 2015-11-03 21:53:16
{"user_id": 500620889, "screen_name": "NBAtheist", "timestamp": "2015-11-
03T20:53:06", "hashtags": ["tehInterwebbies", "Nutters"], "text": "See?
That didn't take long or any actual effort. This is #tehInterwebbies
... #Nutters Abound! https://t.co/QS8gLStYFO", "geo": null, "id":
661647312062709761}
我們有了一個用Spark流式接收并飛速地處理它們.
# 構建一個穩定縮放的流式應用
攝取數據是從各種源中獲取數據并馬上處理或存儲起來稍后處理的過程。數據消費系統是分散的,可以從架構或物理上與數據源分開。數據攝取經常通過手工腳本實現或基本的自動化。實際上,可以調用象 Flume 和Kafka這樣的高層框架。數據攝取的挑戰來自原數據的物理分散和暫態導致的集成集成脆弱性。對于天氣,交通,社交媒體,網絡行為,傳感器,安全,監控來說,數據產品是連續的。
不斷增長的數據容量和流量以及變化的數據結構和語意是數據攝取混亂并有錯誤傾向。
我們的目標是更加的敏捷,可靠和伸縮性。數據攝取的敏捷性,可靠性和伸縮性決定了流水線的健康程度。敏捷性意味著可以集成新的數據源,并按需改變現存的數據源。 為了保證安全可靠, 需要從基礎設施上防止數據丟失,防止下游應用在入口處得到的數據遭到破壞。伸縮性避免了攝取瓶頸,能夠保持數據的快速處理。

使所有數據得到快速的響應,核心驅動是統一日志。統一日志是實時訂閱的集中化企業結構化日志。所有的組織數據放到一個中心化的日志中。記錄編號從零開始,被看作是一個提交日志或者 journal. Unified Log 的概念是Kappa 架構的核心宗旨。
Unified Log 的特性如下:
? Unified: 針對所有組織的單一部署。
? Append only: 事件是可追加且不可變的
? Ordered: 在一個切片內每個事件有位惟一的偏移量
? Distributed: 考慮容錯, Unified Log 冗余分布在集群的各臺主機上
? Fast: 系統每秒攝取成千上萬的消息
#搭建 Kafka
為了獨立數據上行和數據消費,需要對數據提供者和消費者解耦合。 由于雙方有不同的周期和約束, Kafka 可以解耦合數據處理的流水線.Apache Kafka 是一個分布式的發布訂閱消息系統,也可以理解成一個分布式的提交日志。消息按照話題存儲.
Apache Kafka 有如下特性. 它支持:
? 高容量事件的高吞吐量
? 新的和派生的 feeds 的實時處理
? 低時延的企業級消息系統
?容錯歸功于分區內的分布式消息存儲,每個消息都有一個惟一的序列 ID 叫做 offset.
Consumers 通過 元組(offset, partition, topic)檢查它們的指針.
深入解剖一下 Kafka.Kafka 有三個基本組件: producers, consumers 和brokers. Producers
推送并寫數據到brokers. Consumers 從broker 拉取并讀到數據. Brokers不推送消息給consumers. Consumers 從brokers拉取數據. 分布式協作由 Apache Zookeeper完成.
brokers以話題形式存儲和管理數據. 話題分割到復制分區內。數據持久化broker, 在存留期間并不移除. 如果一個consumer 失敗了, 它總是回到broker 再次獲取數據.
Kafka 需要 Apache ZooKeeper支持. ZooKeeper 是分布式應用的一個高性能協調服務。它集中管理配置,注冊或命名服務,組的成員關系,鎖, 服務器間的同步協調,提供了層次化命名空間,有元數據,監測統計和集群狀態。 ZooKeeper 可以快速地引入 brokers和 consumers,然后在集群內重新均衡。
Kafka producers 不需要 ZooKeeper. Kafka brokers 使用 ZooKeeper提供通用的狀態信息在故障的時候選舉leader. Kafka consumers 使用ZooKeeper 跟蹤消息的偏移量. 新版本的 Kafka 將通過 ZooKeeper 保存consumers,并提取Kafka 中特定的話題信息 .Kafka 為producers提供了自動的負載均衡.
下圖描述了Kafka 的建立過程:

## 安裝測試Kafka
下載 Apache Kafka 執行文件
http://kafka.apache.org/downloads.html 按下列步驟安裝軟件:
1. 下載代碼 .
2.
下載 0.8.2.0 版本并解壓:
> tar -xzf kafka_2.10-0.8.2.0.tgz
> cd kafka_2.10-0.8.2.0
3.啟動zooeeper. 使用一個方便的腳步讓Kafka 得到 ZooKeeper 的一個單節點實例
.
bin/zookeeper-server-start.sh config/zookeeper.properties
an@an-VB:~/kafka/kafka_2.10-0.8.2.0$ bin/zookeeper-server-start.sh
config/zookeeper.properties
[2015-10-31 22:49:14,808] INFO Reading configuration from:
config/zookeeper.properties (org.apache.zookeeper.server.quorum.
QuorumPeerConfig)
[2015-10-31 22:49:14,816] INFO autopurge.snapRetainCount set to 3
(org.apache.zookeeper.server.DatadirCleanupManager)..
4.啟動 Kafka server:
bin/kafka-server-start.sh config/server.properties
an@an-VB:~/kafka/kafka_2.10-0.8.2.0$ bin/kafka-server-start.sh
config/server.properties
[2015-10-31 22:52:04,643] INFO Verifying properties (kafka.utils.
VerifiableProperties)
[2015-10-31 22:52:04,714] INFO Property broker.id is overridden to
0 (kafka.utils.VerifiableProperties)
[2015-10-31 22:52:04,715] INFO Property log.cleaner.enable is
overridden to false (kafka.utils.VerifiableProperties)
[2015-10-31 22:52:04,715] INFO Property log.dirs is overridden to
/tmp/kafka-logs (kafka.utils.VerifiableProperties)
[2013-04-22
15:01:47,051] INFO Property socket.send.buffer.bytes is overridden
to 1048576 (kafka.utils.VerifiableProperties)
5.創建主題. 這里創建一個名為 test 的主題,只有一個分區和一個副本 :
bin/kafka-topics.sh --create --zookeeper localhost:2181
--replication-factor 1 --partitions 1 --topic test
6.如果運行 list topic 命令,可以看到這一主題:
bin/kafka-topics.sh --list --zookeeper localhost:2181
Test
an@an-VB:~/kafka/kafka_2.10-0.8.2.0$ bin/kafka-topics.sh --create
--zookeeper localhost:2181 --replication-factor 1 --partitions 1
--topic test
Created topic "test".
an@an-VB:~/kafka/kafka_2.10-0.8.2.0$ bin/kafka-topics.sh --list
--zookeeper localhost:2181
test
7.通過創建一個 producer and consumer來檢查Kafka 的安裝。 先創建一個 producer ,從控制臺鍵入消息:
an@an-VB:~/kafka/kafka_2.10-0.8.2.0$ bin/kafka-console-producer.sh
--broker-list localhost:9092 --topic test
[2015-10-31 22:54:43,698] WARN Property topic is not valid (kafka.
utils.VerifiableProperties)
This is a message
This is another message
8. 啟動一個 consumer 檢查接收到的消息:
an@an-VB:~$ cd kafka/
an@an-
VB:~/kafka$ cd kafka_2.10-0.8.2.0/
an@an-VB:~/kafka/kafka_2.10-0.8.2.0$ bin/kafka-console-consumer.sh
--zookeeper localhost:2181 --topic test --from-beginning
This is a message
This is another message
consumer 正確接收消息的過程:
1. 檢查Kafka 和 Spark Streaming consumer. 使用 Spark
Streaming Kafka 單詞統計示例. 要注意的是: 當發送Spark
job的時候,必須綁定 Kafka 包, --packages org.apache.
spark:spark-streaming-kafka_2.10:1.5.0. 命令如下:
./bin/spark-submit --packages org.apache.spark:spark-streaming-
kafka_2.10:1.5.0 \ examples/src/main/python/streaming/kafka_
wordcount.py
localhost:2181 test
2 當用Kafka 啟動Spark Streaming 單詞計算程序的時候, 得到下面的輸出:
an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6$ ./bin/spark-submit
--packages org.apache.spark:spark-streaming-kafka_2.10:1.5.0
examples/src/main/python/streaming/kafka_wordcount.py
localhost:2181 test
Time: 2015-10-31 23:46:33
(u'', 1)
(u'from', 2)
(u'Hello', 2)
(u'Kafka', 2)
Time: 2015-10-31 23:46:34
Time: 2015-10-31 23:46:35
3.安裝Kafka Python driver 以便對 Producers and Consumers 編程,并使用python 與Kafka and Spark 交互. 我門使用David Arthur開發的庫, aka, Mumrah
on GitHub 網址 (https://github.com/mumrah). 安裝明亮如下:
pip install kafka-python
an@an-VB:~$ pip install kafka-python
Collecting kafka-python
Downloading kafka-python-0.9.4.tar.gz (63kB)
...
Successfully installed kafka-python-0.9.4
## 開發 producers
下面的程序創建了一個簡單的Kafka Producer ,來發送一個消息 *this is a message sent from the Kafka producer*: 5次,帶時間戳:
kafka producer
import time
from kafka.common import LeaderNotAvailableError
from kafka.client import KafkaClient
from kafka.producer import SimpleProducer
from datetime import datetime
def print_response(response=None):
if response:
print('Error: {0}'.format(response[0].error))
print('Offset: {0}'.format(response[0].offset))
def main():
kafka = KafkaClient("localhost:9092")
producer = SimpleProducer(kafka)
try:
time.sleep(5)
topic = 'test'
for i in range(5):
time.sleep(1)
msg = 'This is a message sent from the kafka producer: '
+ str(datetime.now().time()) + ' -- '
+ str(datetime.now().strftime("%A, %d %B %Y
%I:%M%p"))
print_response(producer.send_messages(topic, msg))
except LeaderNotAvailableError:
# https://github.com/mumrah/kafka-python/issues/249
time.sleep(1)
print_response(producer.send_messages(topic, msg))
kafka.close()
if name == "main":
main()
運行程序,產生如下輸出:
an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark/AN_Spark_
Code$ python s08_kafka_producer_01.py
Error: 0
Offset: 13
Error: 0
Offset: 14
Error: 0
Offset: 15
Error: 0
Offset: 16
Error: 0
Offset: 17
an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark/AN_Spark_Code$
沒有錯誤,并且Kafka broker 給出了消息的偏移量。
## 開發 consumers
為了從 Kafka brokers獲取消息, 開發一個Kafka consumer:
kafka consumer
consumes messages from "test" topic and writes them to console.
from kafka.client import KafkaClient
from kafka.consumer import SimpleConsumer
def main():
kafka = KafkaClient("localhost:9092")
print("Consumer established connection to kafka")
consumer = SimpleConsumer(kafka, "my-group", "test")
for message in consumer:
# This will wait and print messages as they become available
print(message)
if name == "main":
main()
運行程序, 可以確認consumer 收到了所有消息:
an@an-VB:~$ cd ~/spark/spark-1.5.0-bin- hadoop2.6/examples/AN_Spark/AN_Spark_Code/
an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark/AN_Spark_Code$ python s08_kafka_consumer_01.py
Consumer established connection to kafka
OffsetAndMessage(offset=13, message=Message(magic=0, attributes=0,
key=None, value='This is a message sent from the kafka producer:
11:50:17.867309Sunday, 01 November 2015 11:50AM'))
...
OffsetAndMessage(offset=17, message=Message(magic=0, attributes=0,
key=None, value='This is a message sent from the kafka producer:
11:50:22.051423Sunday, 01 November 2015 11:50AM'))
## 在Kafka 上開發Spark Streaming consumer
基于Spark Streaming 中提供的示例代碼, 創建 一個Kafka的 Spark Streaming consumer,并對Brokers中存儲的消息執行 單詞統計:
Kafka Spark Streaming Consumer
from future import print_function
import sys
from pyspark import SparkContext
from pyspark.streaming import StreamingContext
from pyspark.streaming.kafka import KafkaUtils
if name == "main":
if len(sys.argv) != 3:
print("Usage: kafka_spark_consumer_01.py <zk> <topic>",
file=sys.stderr)
exit(-1)
sc = SparkContext(appName="PythonStreamingKafkaWordCount")
ssc = StreamingContext(sc, 1)
zkQuorum, topic = sys.argv[1:]
kvs = KafkaUtils.createStream(ssc, zkQuorum, "spark-streaming-
consumer", {topic: 1})
lines = kvs.map(lambda x: x[1])
counts = lines.flatMap(lambda line: line.split(" ")) \
.map(lambda word: (word, 1)) \
.reduceByKey(lambda a, b: a+b)
counts.pprint()
ssc.start()
ssc.awaitTermination()
執行程序,并運行 Spark submit 命令:
./bin/spark-submit --packages org.apache.spark:spark-streaming-
kafka_2.10:1.5.0 examples/AN_Spark/AN_Spark_Code/s08_kafka_spark_
consumer_01.py localhost:2181 test
得到如下輸出:
an@an-VB:~$ cd spark/spark-1.5.0-bin-hadoop2.6/
an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6$ ./bin/spark-submit
--packages org.apache.spark:spark-streaming-kafka_2.10:1.5.0
examples/AN_Spark/AN_Spark_Code/s08_kafka_spark_consumer_01.py
localhost:2181 test
...
:: retrieving :: org.apache.spark#spark-submit-parent
confs: [default]
0 artifacts copied, 10 already retrieved (0kB/18ms)
Time: 2015-11-01 12:13:16
Time: 2015-11-01 12:13:17
Time: 2015-11-01 12:13:18
Time: 2015-11-01 12:13:19
(u'a', 5)
(u'the', 5)
(u'11:50AM', 5)
(u'from', 5)
(u'This', 5)
(u'11:50:21.044374Sunday,', 1)
(u'message', 5)
(u'11:50:20.036422Sunday,', 1)
(u'11:50:22.051423Sunday,', 1)
(u'11:50:17.867309Sunday,', 1)
...
Time: 2015-11-01 12:13:20
Time: 2015-11-01 12:13:21
# 探索flume
Flume 是一個連續攝取數據的系統,最初被設計成一個日志聚會系統,但演變為可以處理任何類型的流事件數據。
Flume 是一個分布式,可靠的,具有伸縮性的流水線系統,可以有效地采集,聚會,傳輸大容量數據,內置支持上下文路由,過濾復制,和多路復用。 容錯和健壯性好,擁有可調的可靠性機制,多種故障切換和恢復機制. 它使用簡單可擴展的數據模型應用于實時分析。
Flume 提供了:
? 保證傳輸的語意
? 低時延可靠的數據傳輸
? 不需要編碼的聲明式配置
? 可擴展和定制的設置
? 集成了大多數通用的端點數據源
Flume 包括以下元素:
? Event: 一個事件是Flume從源到目的地址傳輸數據的基本單元. 象一條消息那樣,對 Flume 而言是不透明的字節數組,通過可選的頭信息來做上下文路由。
? Client: 一個 client 生產和傳輸事件. 客戶端把 Flume和數據消費者解耦合,是一個生成事件并把它們發送到一個或多個代理 的實體,可以是定制的客戶端或者
Flume log4J 程序 或 潛入到應用中的代理.
? Agent: 一個代理是一個容器,承載了源,信道, sinks, 和其它元素使數據能在不同地址間傳輸。它對托管的組件提供了配置,生命周期管理和監控。代理在物理上是一個運行 Flume的Java 虛擬機。
? Source: 源師Flume 接收事件的實體,至少使用一個信道,通過該信道輪詢數據或者等待數據發送給它,有各種采集數據的源,例如 log4j logs 和 syslogs.
? Sink: Sink 是一個實體,將信道中的數據排空并發送到下一個目的地址。 sinks 允許數據被流式發送到一定范圍的目的地址。 Sinks 支持序列化到用戶的格式. 例如 HDFS sink 將事件寫到 HDFS.
? Channel: 信道是源和sink之間的管道,緩存流入的事件之道sink 將信道中的數據排空。源給信道提供事件,Sink 排空信道。 信道解耦合了上下行系統,抑制了上行數據的突發性, 客服了下行故障。為了實現這一目的,關鍵是調整處理事件的信道容量。
信道提供了兩種層次的持久化: 內存型信道和文件型信道。當JVM崩潰時,內存型信道是不穩定的,
而文件型信道通過寫前日志 把信息存儲到硬盤,所以可以回復。信道是全事務型的。
下圖解釋了這些概念:

# 基于Flume, Kafka和Spark開發數據流水線
利用我們已學的知識,可以構建一個彈性的數據處理流水線。通過Flume 將數據攝取和傳輸放到一起,把Kafka 這樣的可靠的發布訂閱消息系統作為數據代理,最后使用Spark Streaming 完成高速的計算處理。
下圖解釋了流式數據流水線的組成 connect, collect, conduct, compose, consume, consign, 和 control.
這些活動根據用例來配置:
+ Connect 建立了streaming API的綁定
+ Collect 創建了采集線程.
+ Compose 專注于處理數據
+ Consume 為消費系統提供處理后的數據
+
Consign 負責數據持久化
+ Control 負責系統、數據和營養的統籌和監控。

下圖介紹了流式數據流水線的概念及關鍵組件: Spark Streaming, Kafka, Flume, 和低時延數據庫. 在消費或控制型應用中,需要實時監控系統,或者在超出一定閾值時實時發送告警。

下圖介紹了Spark 在處理單平臺運動數據和閑置數據的獨特能力,即根據不同的用例需求與多個持久化數據存儲無縫對接。

這張圖是到目前為止所談論的所有概念。頂部描述了流式處理流水線。底部描述了批處理流水線 ,兩者共享位于中間的通用持久化層,包含各種模式的持久化和序列化。
# Lambda 和Kappa 架構
現在有兩種流行的架構范式: Lambda 和 Kappa 架構.
Lambda 是Storm 創建者何主要提交者 Nathan Marz 的作品. 他倡導在所有數據上構建功能型架構。批處理分支是Hadoop 的最初設想 , 是歷史數據的高時延高吞吐量的預處理,然后用于消費。實時處理分支由Storm設計的,可以增量處理流數據,快速產生結論性見解,從存儲中實時聚合數據。
Kappa 是Kafka 的主要提交者之一 Jay Kreps 和他在 Confluent (previously at LinkedIn)的同事的作品. 它倡導全部流式流水線,和前面談到的統一日志,是企業級的高效實現.
## 理解 Lambda 架構
Lambda 架構結合了批處理和流式處理,在所有數據上提供了統一的查詢機制。有三層: 存儲預計算信息的批處理層,增量進行流式實時處理的速度層,服務層合并了批處理和實時處理的隨機查詢。 下圖給出了 Lambda 的架構。

## 理解Kappa 架構
Kappa 架構目標是全企業的流模式驅動,起源于 Jay Kreps 和他在LinkedIn的同事的一個評論文章。
從那時起,他們利用Apache
Kafka 創建了 Confluent with Apache,并作為
Kappa 架構的主要促成者. 基本宗旨是使用統一日志作為企業信息架構的主干,進而移動到流處理模式。
統一日志是一個集中的企業結構化日志,用于實時訂閱。所有組織的數據放入到一個集中的日志,記錄寫入時從零開始做標記,可以時提及日志或日志集合。統一日志的概念是
Kappa 架構的核心宗旨.
統一日志的特點如下:
? Unified: 所有組織的同一部署
? Append only: 事件是可追加且不可修改的
? Ordered: 每個事件在分片內有唯一的偏移量
? Distributed: 出于容錯考慮, 統一日志分布于在集群的不同主機上。
? Fast: 系統每秒攝取成千上萬的數據
下面的截圖展示了 Jay Kreps 宣稱對 Lambda 架構的保留意見. 他關于Lambda架構的主要保留意見是在Hadoop and Storm兩個不同的系統中實現了相同的工作每個有著各自的特性,以及隨之而來的復雜性。
Kappa 通過Apache Kafka 既處理了實時數據,也處理了歷史數據。

# 小結
我門列舉了流式處理架構應用的基礎,描述了他們的挑戰,約束和優勢。深入了解了Spark Streaming 的內部工作原理包括如何Spark Core 適用,以及與Spark SQL 和 Spark MLlib對話, 通過TCP sockets 解釋了流處理概念,接下來,是從Twitter 服務中實時攝取tweet。使用Kafka最大限度地增加了流處理架構的彈性,討論了上下行數據與消費者之間的解耦合。 還討論了Flume—這個可靠,靈活,伸縮性數據攝取和傳輸的流水線系統。結合Flume, Kafka, 和Spark 在時變領域交付了非并行健壯性,速度和敏捷性.
在結尾觀察和點評了兩種流處理架構范式 Lambda 和 Kappa 架構. Lambda 架構在通用的查詢前端結合了 批處理和流數據. 最初想法形成了 Hadoop和Storm的愿景. Spark 有自己的批處理和流處理范式, 以共有代碼為基礎提供了單一的環境,從而有效地在現實中使用這一架構。Kappa 架構宣揚了同一日志的概念, 這創建了面向事件的架構,企業的所有數據通過信道傳輸到一個中心化的提交日志,同時對所有消費者實時使用。