Spark Streaming 能够接收任意的数据流,不仅仅局限于Flume,Kafka,Kinesis,文件系统,套接字等。这需要开发者来实现一个接收者来接收相关的数据源。本文讲述实现整个客户端接收者的过程以及使用它在spark streaming中的应用。 特别需要注意的是,客户端接收者可以用scala或者java来实现。
客户端接收者实现
一个客户端接收者需要实现抽象类的两个方法,如下所示:
onStart():开始接收数据
onStop():停止接收数据
无论是onStart()还是onStop()都必须保证永远不能阻塞。通常,onStart()会开启线程来响应接收数据,onStop()会保证能够停止这些接收数据的线程。接收者线程还可以使用isStopped(),它是一个接收者方法来判断是否应该停止这些正在接收数据的线程。
一旦数据被接收了,这些数据会被Spark通过调用store(data)方法存储到spark里,该方法是在Receiver类中提供的。有很多种store()方法可以接收整个对象活着序列化字节的集合也可以一个存储一个记录。特别需要注意的是,一种store()方法实现的不同会影响它的可靠性和容错性。这些会在后面详细讲解。
在接收数据的过程中任何异常都应该被捕获和合理的处理掉来避免失败。restart(<exception>)方法会通过异步的方式调用onStop和在停顿一小会儿后调用onStart方法来重启接收者。stop(<exception>)会调用onStop()来关闭接收者。reportError(<error>)上报错误信息给驱动程序(可以在日志和UI)不会停止或者重启接收者。
下面是一个客户端接收者。接收来自套接字中的文本流。用'\n'作为分隔符来对文本流中的数据进行切分成一条条的记录,并用spark来存储他们。一旦接收数据的线程出现连接失败或者接收失败的情况,接收者会重启来尝试来建立新的连接。
下面是java代码
public class JavaCustomReceiver extends Receiver<String> { String host = null; int port = -1; public JavaCustomReceiver(String host_ , int port_) { super(StorageLevel.MEMORY_AND_DISK_2()); host = host_; port = port_; } public void onStart() { // Start the thread that receives data over a connection new Thread() { @Override public void run() { receive(); } }.start(); } public void onStop() { // There is nothing much to do as the thread calling receive() // is designed to stop by itself if isStopped() returns false } /** Create a socket connection and receive data until receiver is stopped */ private void receive() { Socket socket = null; String userInput = null; try { // connect to the server socket = new Socket(host, port); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); // Until stopped or connection broken continue reading while (!isStopped() && (userInput = reader.readLine()) != null) { System.out.println("Received data '" + userInput + "'"); store(userInput); } reader.close(); socket.close(); // Restart in an attempt to connect again when server is active again restart("Trying to connect again"); } catch(ConnectException ce) { // restart if could not connect to server restart("Could not connect", ce); } catch(Throwable t) { // restart if there is any other error restart("Error receiving data", t); } } }
在一个spark Streaming应用中使用该客户端接收者
该客户端接收者通过使用streamingContext.receiverStream(<instance of customreceiver>)来被spark streaming应用来使用。这会产生一个input DStream通过使用该接收者。代码如下
// Assuming ssc is the JavaStreamingContext JavaDStream<String> customReceiverStream = ssc.receiverStream(new JavaCustomReceiver(host, port)); JavaDStream<String> words = lines.flatMap(new FlatMapFunction<String, String>() { ... }); ...
全部的源码地址为https://github.com/apache/spark/blob/master/examples/src/main/java/org/apache/spark/examples/streaming/JavaCustomReceiver.java
接收者可靠性
正如在spark streaming编程指南中提到的,基于他们的可靠性和容错性有两种接收者
1.可靠性接收者。对于可靠的数据源能够允许在发送数据以后可以被确认。一个可靠的接收者在正确确认收到了该数据,这表明该源数据已经被完全正确的接收而且被正确的存储在了spark里面。
2.不可靠接收。一个不可靠的接收者不会发送确认给该数据源。这种方法适用于不接受确认的数据源。
为了实现一个可靠接收者,你需要使用store(multiple-records)来存储数据。这是一种阻塞性的方法只有当所有记录被正确的存储在spark内后才返回。如果该接收者配置的存储级别是replication(默认),该存储方法会在复制完全结束后才会返回。因此,这就保证了数据存储是可靠的。这就保证了没有数据丢失,如果在复制数据的过程中接收者程序挂掉,在 缓冲中的数据将不会被确认,数据源会稍后重发数据来保证数据被正确接收。
非可靠接收者不用实现该逻辑。它只是接收记录并且将这些记录一次一个的插入到spark中通过使用store(single-record),因此他的可靠性是无法得到保证的。下面是他的优点:
1.系统需要将数据切分成合适大小的块。
2.系统需要控制接收速率,如果速率被明确指明了。
3.基于上面两点,非可靠接收者要
下表展示了两种方法的优缺点。
Receiver Type | Characteristics |
---|---|
Unreliable Receivers | Simple to implement. System takes care of block generation and rate control. No fault-tolerance guarantees, can lose data on receiver failure. |
Reliable Receivers | Strong fault-tolerance guarantees, can ensure zero data loss. Block generation and rate control to be handled by the receiver implementation. Implementation complexity depends on the acknowledgement mechanisms of the source. |
actor-based客户端接收者的实现和使用
Akka Actors能够被用来接收数据。
class CustomActor extends Actor with ActorHelper { def receive = { case data: String => store(data) } }数据流可以通过下面的方式创建。
// Assuming ssc is the StreamingContext val lines = ssc.actorStream[String](Props(new CustomActor()), "CustomReceiver")