众所周知,作为一个事件流平台,Kafka能够松散地驻留在面向消息的中间件(Message-oriented Middleware,MoM)空间里。而被称为Actor模型的Akka,是一个基于响应、容错和消息传递的同步计算过程。
下面,我将和您讨论分布式编程工具Akka Streams、Kafka Streams和Spark Streaming的主要特点、优缺点、以及如何在一个简单的字数统计应用中使用它们。文中,我主要使用Scala来编写代码,所涉及到的框架都带有Java API。
一、Kafka Streams
Kafka Steams是一个可以处理数据的客户端库(client library)。此处的客户端库是指,我们所编写的应用程序使用了另一个基础设施(在本例中是Kafka集群)所提供的服务。因此,我们需要与一个集群进行交互,以处理持续的数据流。而数据则需要被表示为键值记录的形式,以易于识别,并被组织成主题形式的持久性事件日志。它们本质上是被复制和写入磁盘的持久数据队列。在该架构中,生产者(producer)应用程序将记录推送到主题中(例如电商需要跟踪订单的每一步);而多个消费者(consumer)应用程序需要以各种方式,读取主题中不同时间点的数据。
此类数据结构的架构不但具有高度分布式和可扩展性的特点,而且具有一定的容错性。由于嵌入了exact-once消息语义,Kafka可以确保发来的每一条记录,都能够到达集群,并且仅写入一次,没有重复。正是由于在一般的分布式系统中极难实现,因此Kafka的该特性非常重要。
从Kafka的组织方式来看,其API允许Java或Scala应用程序,在与Kafka集群进行交互的同时,与其他应用程序并行、独立地使用。这种独立性能够满足在大型应用程序中,分布式且可扩展的服务去独立地使用微服务。
Kafka Steams的表现形式
Scala 1 object WordCountApplication extends App { 2 import Serdes._ 3 val props: Properties = { 4 val p = new Properties() 5 p.put(StreamsConfig.APPLICATION_ID_CONFIG, "myFabulousWordCount") 6 p.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "my-kafka-broker-url:9092") 7 p 8 } 9 10 val builder: StreamsBuilder = new StreamsBuilder 11 val textLines: KStream[String, String] = 12 builder.stream[String, String]("TextLinesTopic") 13 val wordCounts: KTable[String, Long] = textLines 14 .flatMapValues(textLine => textLine.toLowerCase.split("\\W+")) 15 .groupBy((_, word)=> word) 16 .count()(Materialized.as("word-counts-table")) 17 18 wordCounts.toStream.to("WordsWithCountsTopic") 19 val streams: KafkaStreams = new KafkaStreams(builder.build(), props) 20 streams.start() 21 22 sys.ShutdownHookThread { 23 streams.close(10, TimeUnit.SECONDS) 24 } 25 }
上述代码便是单词计数应用的Kafka Steams表现形式。显然,这段代码相对较“重”,我试着对其进行分解。
Scala 1 import Serdes._
Kafka针对性能进行了二进制式的记录存储