alpakka项目是一个基于akka-streams流处理编程工具的scala/java开源项目,通过提供connector连接各种数据源并在akka-streams里进行数据处理。alpakka-kafka就是alpakka项目里的kafka-connector。对于我们来说:可以用alpakka-kafka来对接kafka,使用kafka提供的功能。或者从另外一个角度讲:alpakka-kafka就是一个用akka-streams实现kafka功能的scala开发工具。

alpakka-kafka提供了kafka的核心功能:producer、consumer,分别负责把akka-streams里的数据写入kafka及从kafka中读出数据并输入到akka-streams里。用akka-streams集成kafka的应用场景通常出现在业务集成方面:在一项业务A中产生一些业务操作指令写入kafka,然后通过kafka把指令传送给另一项业务B,业务B从kafka中获取操作指令并进行相应的业务操作。如:有两个业务模块:收货管理和库存管理,一方面收货管理向kafka写入收货记录。另一头库存管理从kafka中读取收货记录并更新相关库存数量记录。注意,这两项业务是分别操作的。在alpakka中,实际的业务操作基本就是在akka-streams里的数据处理(transform),其实是典型的CQRS模式:读写两方互不关联,写时不管受众是谁,如何使用、读者不关心谁是写方。这里的写和读两方分别代表kafka里的producer和consumer。

本篇我们先介绍alpakka-kafka的producer功能及其使用方法。如前所述:alpakka是用akka-streams实现了kafka-producer功能。alpakka提供的producer也就是akka-streams的一种组件,可以与其它的akka-streams组件组合形成更大的akka-streams个体。构建一个producer需要先完成几个配件类构成:

1、producer-settings配置:alpakka-kafka在reference.conf里的akka.kafka.producer配置段落提供了足够支持基本运作的默认producer配置。用户可以通过typesafe config配置文件操作工具来灵活调整配置

2、de/serializer序列化工具:alpakka-kafka提供了String类型的序列化/反序列化函数,可以直接使用

4、bootstrap-server:一个以逗号分隔的kafka-cluster节点ip清单文本

下面是一个具体的例子:

  implicit val system = ActorSystem("kafka_sys")
val bootstrapServers = "localhost:9092"
val config = system.settings.config.getConfig("akka.kafka.producer")
val producerSettings =
ProducerSettings(config, new StringSerializer, new StringSerializer)
.withBootstrapServers(bootstrapServers)

这里使用ActorSystem只是为了读取.conf文件里的配置,还没有使用任何akka-streams组件。akka.kafka.producer配置段落在alpakka-kafka的reference.conf里提供了默认配置,不需要在application.conf里重新定义。

alpakka-kafka提供了一个最基本的producer,非akka-streams组件,sendProducer。下面我们示范一下sendProducer的使用和效果:

import akka.actor.ActorSystem
import akka.kafka.scaladsl.{Consumer, SendProducer}
import org.apache.kafka.clients.producer.{ProducerRecord, RecordMetadata}
import akka.kafka._
import org.apache.kafka.common.serialization._
import scala.concurrent.duration._
import scala.concurrent.{Await, Future} object SendProducerDemo extends App {
implicit val system = ActorSystem("kafka_sys")
implicit val executionContext = system.dispatcher
val bootstrapServers = "localhost:9092"
val config = system.settings.config.getConfig("akka.kafka.producer")
val producerSettings =
ProducerSettings(config, new StringSerializer, new StringSerializer)
.withBootstrapServers(bootstrapServers)
val producer = SendProducer(producerSettings)
val topic = "greatings"
val lstfut: Seq[Future[RecordMetadata]] =
(100 to 200).reverse
.map(_.toString)
.map(value => new ProducerRecord[String, String](topic, s"hello-$value"))
.map(msg => producer.send(msg)) val futlst = Future.sequence(lstfut)
Await.result(futlst, 2.seconds) scala.io.StdIn.readLine()
producer.close()
system.terminate()
}

以上示范用sendProducer向kafka写入100条hello消息。使用的是集合遍历,没有使用akka-streams的Source。为了检验具体效果,我们可以使用kafka提供的一些手工指令,如下:

\w> ./kafka-topics --create --topic greatings --bootstrap-server localhost:9092
Created topic greatings.
\w> ./kafka-console-consumer --topic greatings --bootstrap-server localhost:9092
hello-100
hello-101
hello-102
hello-103
hello-104
hello-105
hello-106
...

既然producer代表写入功能,那么在akka-streams里就是Sink或Flow组件的功能了。下面这个例子是producer Sink组件plainSink的示范:

import akka.Done
import akka.actor.ActorSystem
import akka.kafka.scaladsl._
import akka.kafka._
import akka.stream.scaladsl._
import org.apache.kafka.clients.producer.ProducerRecord
import org.apache.kafka.common.serialization._ import scala.concurrent._
import scala.concurrent.duration._ object plain_sink extends App {
implicit val system = ActorSystem("kafka_sys")
val bootstrapServers = "localhost:9092"
val config = system.settings.config.getConfig("akka.kafka.producer")
val producerSettings =
ProducerSettings(config, new StringSerializer, new StringSerializer)
.withBootstrapServers(bootstrapServers) implicit val executionContext = system.dispatcher
val topic = "greatings"
val done: Future[Done] =
Source(1 to 100)
.map(_.toString)
.map(value => new ProducerRecord[String, String](topic, s"hello-$value"))
.runWith(Producer.plainSink(producerSettings)) Await.ready(done,3.seconds) scala.io.StdIn.readLine()
system.terminate()
}

这是一个典型的akka-streams应用实例,其中Producer.plainSink就是一个akka-streams Sink组件。

以上两个示范都涉及到构建一个ProducerRecord类型并将之写入kafka。ProducerRecord是一个基本的kafka消息类型:

   public ProducerRecord(String topic, K key, V value) {
this(topic, null, null, key, value, null);
}

topic是String类型,key, value 是 Any 类型的。 alpakka-kafka在ProducerRecord之上又拓展了一个复杂点的消息类型ProducerMessage.Envelope类型:

sealed trait Envelope[K, V, +PassThrough] {
def passThrough: PassThrough
def withPassThrough[PassThrough2](value: PassThrough2): Envelope[K, V, PassThrough2]
} final case class Message[K, V, +PassThrough](
record: ProducerRecord[K, V],
passThrough: PassThrough
) extends Envelope[K, V, PassThrough] {
override def withPassThrough[PassThrough2](value: PassThrough2): Message[K, V, PassThrough2] =
copy(passThrough = value)
}

ProducerMessage.Envelope增加了个PassThrough参数,用来与消息一道传递额外的元数据。alpakka-kafka streams组件使用这个消息类型作为流元素,最终把它转换成一或多条ProducerRecord写入kafka。如下:

object EventMessages {
//一对一条ProducerRecord
def createMessage[KeyType,ValueType,PassThroughType](
topic: String,
key: KeyType,
value: ValueType,
passThrough: PassThroughType): ProducerMessage.Envelope[KeyType,ValueType,PassThroughType] = {
val single = ProducerMessage.single(
new ProducerRecord[KeyType,ValueType](topic,key,value),
passThrough
)
single
}
//一对多条ProducerRecord
def createMultiMessage[KeyType,ValueType,PassThroughType] (
topics: List[String],
key: KeyType,
value: ValueType,
passThrough: PassThroughType): ProducerMessage.Envelope[KeyType,ValueType,PassThroughType] = {
import scala.collection.immutable
val msgs = topics.map { topic =>
new ProducerRecord(topic,key,value)
}.toSeq
val multi = ProducerMessage.multi(
msgs,
passThrough
)
multi
}
//只传递通过型元数据
def createPassThroughMessage[KeyType,ValueType,PassThroughType](
topic: String,
key: KeyType,
value: ValueType,
passThrough: PassThroughType): ProducerMessage.Envelope[KeyType,ValueType,PassThroughType] = {
ProducerMessage.passThrough(passThrough)
} }

flexiFlow是一个alpakka-kafka Flow组件,流入ProducerMessage.Evelope,流出Results类型:

  def flexiFlow[K, V, PassThrough](
settings: ProducerSettings[K, V]
): Flow[Envelope[K, V, PassThrough], Results[K, V, PassThrough], NotUsed] = { ... }

Results类型定义如下:

  final case class Result[K, V, PassThrough] private (
metadata: RecordMetadata,
message: Message[K, V, PassThrough]
) extends Results[K, V, PassThrough] {
def offset: Long = metadata.offset()
def passThrough: PassThrough = message.passThrough
}

也就是说flexiFlow可以返回写入kafka后kafka返回的操作状态数据。我们再看看flexiFlow的使用案例:

import akka.kafka.ProducerMessage._
import akka.actor.ActorSystem
import akka.kafka.scaladsl._
import akka.kafka.{ProducerMessage, ProducerSettings}
import akka.stream.scaladsl.{Sink, Source}
import org.apache.kafka.clients.producer.ProducerRecord
import org.apache.kafka.common.serialization.StringSerializer import scala.concurrent._
import scala.concurrent.duration._ object flexi_flow extends App {
implicit val system = ActorSystem("kafka_sys")
val bootstrapServers = "localhost:9092"
val config = system.settings.config.getConfig("akka.kafka.producer")
val producerSettings =
ProducerSettings(config, new StringSerializer, new StringSerializer)
.withBootstrapServers(bootstrapServers) // needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val topic = "greatings" val done = Source(1 to 100)
.map { number =>
val value = number.toString
EventMessages.createMessage(topic,"key",value,number)
}
.via(Producer.flexiFlow(producerSettings))
.map {
case ProducerMessage.Result(metadata, ProducerMessage.Message(record, passThrough)) =>
s"${metadata.topic}/${metadata.partition} ${metadata.offset}: ${record.value}" case ProducerMessage.MultiResult(parts, passThrough) =>
parts
.map {
case MultiResultPart(metadata, record) =>
s"${metadata.topic}/${metadata.partition} ${metadata.offset}: ${record.value}"
}
.mkString(", ") case ProducerMessage.PassThroughResult(passThrough) =>
s"passed through"
}
.runWith(Sink.foreach(println(_))) Await.ready(done,3.seconds) scala.io.StdIn.readLine()
system.terminate()
} object EventMessages {
def createMessage[KeyType,ValueType,PassThroughType](
topic: String,
key: KeyType,
value: ValueType,
passThrough: PassThroughType): ProducerMessage.Envelope[KeyType,ValueType,PassThroughType] = {
val single = ProducerMessage.single(
new ProducerRecord[KeyType,ValueType](topic,key,value),
passThrough
)
single
}
def createMultiMessage[KeyType,ValueType,PassThroughType] (
topics: List[String],
key: KeyType,
value: ValueType,
passThrough: PassThroughType): ProducerMessage.Envelope[KeyType,ValueType,PassThroughType] = {
import scala.collection.immutable
val msgs = topics.map { topic =>
new ProducerRecord(topic,key,value)
}.toSeq
val multi = ProducerMessage.multi(
msgs,
passThrough
)
multi
}
def createPassThroughMessage[KeyType,ValueType,PassThroughType](
topic: String,
key: KeyType,
value: ValueType,
passThrough: PassThroughType): ProducerMessage.Envelope[KeyType,ValueType,PassThroughType] = {
ProducerMessage.passThrough(passThrough)
} }

producer除向kafka写入与业务相关的业务事件或业务指令外还会向kafka写入当前消息读取的具体位置offset,所以alpakka-kafka的produce可分成两种类型:上面示范的plainSink, flexiFlow只向kafka写业务数据。还有一类如commitableSink还包括了把消息读取位置offset写入commit的功能。如下:

val control =
Consumer
.committableSource(consumerSettings, Subscriptions.topics(topic1, topic2))
.map { msg =>
ProducerMessage.single(
new ProducerRecord(targetTopic, msg.record.key, msg.record.value),
msg.committableOffset
)
}
.toMat(Producer.committableSink(producerSettings, committerSettings))(DrainingControl.apply)
.run() control.drainAndShutdown()

如上所示,committableSource从kafka读取业务消息及读取位置committableOffsset,然后Producer.committableSink把业务消息和offset再写入kafka。

下篇讨论我们再具体介绍consumer。

alpakka-kafka(1)-producer的更多相关文章

  1. 【转】 详解Kafka生产者Producer配置

    粘贴一下这个配置,与我自己的程序做对比,看看能不能完善我的异步带代码:   -----------------------------------------    详解Kafka生产者Produce ...

  2. Kafka的Producer和Consumer源码学习

    先解释下两个概念: high watermark (HW) 它表示已经被commited的最后一个message offset(所谓commited, 应该是ISR中所有replica都已写入),HW ...

  3. Kafka学习-Producer和Customer

    在上一篇kafka入门的基础之上,本篇主要介绍Kafka的生产者和消费者. Kafka 生产者 kafka Producer发布消息记录到Kakfa集群.生产者是线程安全的,可以在多个线程之间共享生产 ...

  4. Error when sending message to topic test with key: null, value: 2 bytes with error: (org.apache.kafka.clients.producer.internals.ErrorLoggingCallback)

    windows下使用kafka遇到这个问题: Error when sending message to topic test with key: null, value: 2 bytes with ...

  5. kafka 客户端 producer 配置参数

    属性 描述 类型 默认值 bootstrap.servers 用于建立与kafka集群的连接,这个list仅仅影响用于初始化的hosts,来发现全部的servers.格式:host1:port1,ho ...

  6. Kafka遇到30042ms has passed since batch creation plus linger time at org.apache.kafka.clients.producer.internals.FutureRecordMetadata.valueOrError(FutureRecordMetadata.java:94)

    问题描述: 运行生产者线程的时候显示如下错误信息: Expiring 1 record(s) for XXX-0: 30042 ms has passed since batch creation p ...

  7. 057 Java中kafka的Producer程序实现

    1.需要启动的服务 这里启动的端口是9092. bin/kafka-console-consumer.sh --topic beifeng --zookeeper linux-hadoop01.ibe ...

  8. Kafka: Producer (0.10.0.0)

    转自:http://www.cnblogs.com/f1194361820/p/6048429.html 通过前面的架构简述,知道了Producer是用来产生消息记录,并将消息以异步的方式发送给指定的 ...

  9. 【Kafka】Producer配置

    名称 描述 类型 默认值 bootstrap.servers kafka集群地址,ip+端口,以逗号隔开.不管这边配置的是什么服务器,客户端会使用所有的服务器.配置的列表只会影响初始发现所有主机.配置 ...

  10. Kafka生产者producer简要总结

    Kafka producer在设计上要比consumer简单,不涉及复杂的组管理操作,每个producer都是独立进行工作的,与其他producer实例之间没有关联.Producer的主要功能就是向某 ...

随机推荐

  1. 四:SpringBoot-定时任务和异步任务的使用方式

    SpringBoot-定时任务和异步任务的使用方式 1.定时任务 2.同步和异步 3.定时器的使用 3.1 定时器执行规则注解 3.2 定义时间打印定时器 3.3 启动类开启定时器注解 4.异步任务 ...

  2. Web信息收集之搜索引擎-GoogleHacking

    Web信息收集之搜索引擎-GoogleHacking 一.信息收集概述 二.Google Hacking 2.1 site 2.2 filetype 2.3 inurl 2.4 intitle 2.5 ...

  3. JavaWeb——过滤器及监听器

    什么是过滤器? 过滤器示意图 Filter是如何实现拦截的? Filter的生命周期 Filter的创建 Filter的销毁 FilterConfig接口 Servlet过滤器有关接口 过滤器配置 F ...

  4. Spark调优,性能优化

    Spark调优,性能优化 1.使用reduceByKey/aggregateByKey替代groupByKey 2.使用mapPartitions替代普通map 3.使用foreachPartitio ...

  5. Phoenix表和索引分区优化方法

    Phoenix表和索引分区,基本优化方法 优化方法 1. SALT_BUCKETS RowKey SALT_BUCKETS 分区 2. Pre-split RowKey分区 3. 分列族 4. 使用压 ...

  6. Django(多表查询操作)

    首先了解一下 mysql中的表之间的关系,一对一,一对多,多对一,多对多. 一对多关系.多对一关系.一对一关系 至少都有一侧是单个实体,所以记录之间的联系通过外键实现,让外键指向这个实体. 实现这种关 ...

  7. UML——交互图(序列图+协作图)

    交互图(interaction diagram):是协作图=通信图UML2.0以后的叫法=合作图=(Collaboration /Communication Diagram)以及序列图=顺序图=时序图 ...

  8. Flink-v1.12官方网站翻译-P025-Queryable State Beta

    可查询的状态 注意:可查询状态的客户端API目前处于不断发展的状态,对所提供接口的稳定性不做保证.在即将到来的Flink版本中,客户端的API很可能会有突破性的变化. 简而言之,该功能将Flink的托 ...

  9. 记angular和asp.net使用grpc进行通信

    AspNetCore配置grpc服务端 新建一个Demo项目: GrpcStartup, 目录结构如下图: GrpcStartup.GrpcServices需要安装下面的依赖 <PackageR ...

  10. BZOJ-1086 [SCOI2005]王室联邦 (树分块)

    递归处理子树,把当前结点当作栈底,然后递归,回溯回来之后如果栈中结点数量到达某一个标准时,弹出栈中所有的元素分到一个块中,最后递归结束了如果栈中还有元素,那么剩下的这些元素放在新的块中 题目:BZOJ ...