「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」。
最近在做一些简单的spark streaming实验,因为不是很想使用它的文件数据源(虽然它可以监听),又不太想用kafka这么重的程序。于是乎想要通过nc这种,然后用python把一个一定速率的数据输入到文件中,然而事与愿违,效果不是很好。最后想到了使用redis的发布订阅功能作为一个类似总线的中介用来交换数据。
为了能够读取redis中的数据,需要redis相关驱动,这里选择了使用jedis作为客户端。比较可惜的是Jedis貌似只有回调这种异步的模式,不像其他那种同步的数据源,依次读取即可。这会为我们后序读取数据稍微带来一些麻烦。
为spark streaming 实现一个简单的数据源的方式我们可以参考官网的示例,基本上没有太大的差别,主要的要修改的地方在于储存数据。储存数据需要我们在自行实现的Receiver接受器类的receive方法中使用store函数储存每一个要被处理的流数据。
假如我们使用的是经典的同步式操作的话,一般会得到一个值,这时候调用store函数储存值,相当的符合正常的思路。可是它使用异步的接受模式。当有消息来时触发message事件。得想办法让message事件能使用store函数。比较幸运的scala的闭包可以完成这个效果。
所以最终的代码可以写成下面这样
class CustomReceiver(host: String, port: Int) extends Receiver[String](StorageLevel.MEMORY_AND_DISK_2) with Logging {
def onStart(): Unit = {
new Thread("Socket Receiver") {
receive()
}.start()
}
def onStop(): Unit = {}
private def receive(): Unit = {
try {
val jedis = new Jedis(host, port)
jedis.subscribe(new JedisPubSub() {
override def onMessage(channel: String, message: String): Unit = {
val itr = new StringTokenizer(message,"\n")
while (itr.hasMoreTokens) {
store(itr.nextToken())
}
}
}, "source")
jedis.close()
restart("try again")
} catch {
case e: java.net.ConnectException => restart("Error connecting to " + host + ":" + port, e)
case t: Throwable => restart("Error receiving data", t)
}
}
}
对于简单的,类似实验目的的spark streaming程序而言,这样还是比较方便的。当然缺点也比较明显,redis这种消息总线的架构是没有所谓的错误容忍性的。属于一种即时性的消息。
在不考虑这种错误恢复的时候,还是可以接受的。毕竟redis相当轻量级。而且它的性能和nats都有的一比。