alpakka-kafka(1)-producer
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的更多相关文章
- 【转】 详解Kafka生产者Producer配置
粘贴一下这个配置,与我自己的程序做对比,看看能不能完善我的异步带代码: ----------------------------------------- 详解Kafka生产者Produce ...
- Kafka的Producer和Consumer源码学习
先解释下两个概念: high watermark (HW) 它表示已经被commited的最后一个message offset(所谓commited, 应该是ISR中所有replica都已写入),HW ...
- Kafka学习-Producer和Customer
在上一篇kafka入门的基础之上,本篇主要介绍Kafka的生产者和消费者. Kafka 生产者 kafka Producer发布消息记录到Kakfa集群.生产者是线程安全的,可以在多个线程之间共享生产 ...
- 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 ...
- kafka 客户端 producer 配置参数
属性 描述 类型 默认值 bootstrap.servers 用于建立与kafka集群的连接,这个list仅仅影响用于初始化的hosts,来发现全部的servers.格式:host1:port1,ho ...
- 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 ...
- 057 Java中kafka的Producer程序实现
1.需要启动的服务 这里启动的端口是9092. bin/kafka-console-consumer.sh --topic beifeng --zookeeper linux-hadoop01.ibe ...
- Kafka: Producer (0.10.0.0)
转自:http://www.cnblogs.com/f1194361820/p/6048429.html 通过前面的架构简述,知道了Producer是用来产生消息记录,并将消息以异步的方式发送给指定的 ...
- 【Kafka】Producer配置
名称 描述 类型 默认值 bootstrap.servers kafka集群地址,ip+端口,以逗号隔开.不管这边配置的是什么服务器,客户端会使用所有的服务器.配置的列表只会影响初始发现所有主机.配置 ...
- Kafka生产者producer简要总结
Kafka producer在设计上要比consumer简单,不涉及复杂的组管理操作,每个producer都是独立进行工作的,与其他producer实例之间没有关联.Producer的主要功能就是向某 ...
随机推荐
- java中的IO处理和使用,API详细介绍(一)
写在前面:本文章基本覆盖了java IO的全部内容,java新IO没有涉及,因为我想和这个分开,以突出那个的重要性,新IO哪一篇文章还没有开始写,估计很快就能和大家见面.照旧,文章依旧以例子为主,因为 ...
- linux shell 条件判断if else, if elif else....
在linux的shell中 if 语句通过关系运算符判断表达式的真假来决定执行哪个分支.Shell 有三种 if ... else 语句: if ... fi 语句: if ... else ... ...
- js打开新窗口并且居中显示
function openwindow(url,name,iWidth,iHeight) { var url; //转向网页的地址; var name; //网页名称,可为空; var iWidth; ...
- python——模块、标准库、第三方模块安装
模块(module)简介 模块化--指将一个完整的程序分解为一个一个小的模块,通过将模块组合,来搭建出一个完整的程序. 模块化的特点: ① 方便开发 ② 方便维护 ③ 模块可以复用! 在Python中 ...
- 调用个别f5 负载端口为80的vs时,返回值为空的问题
现状: vs负载端口为80并添加XFF,pool包含2个member,member的monitor端口为80&9000. 故障现象: 应用同事描述说再完全复制了一个member并添加到pool ...
- for循环实现的一些小例子
1.1-10阶乘和 package HELLO; public class exercise5 { /** 1-10阶乘和 */ public static void main(String[] ar ...
- shell编程基础一
1.定义变量 a=1 shell定义变量要注意等号前后不能有空格,不然会报错,请严格按照格式编写. 2.打印输出 echo 1 使用echo打印,后面留一个空格. 3.shell中通过 ${变量名} ...
- hdu1890 Robotic Sort (splay+区间翻转单点更新)
Problem Description Somewhere deep in the Czech Technical University buildings, there are laboratori ...
- 【poj 1988】Cube Stacking(图论--带权并查集)
题意:有N个方块,M个操作{"C x":查询方块x上的方块数:"M x y":移动方块x所在的整个方块堆到方块y所在的整个方块堆之上}.输出相应的答案. 解法: ...
- github host
更改hosts文件,地址:C:\Windows\System32\Drivers\etc 不能直接修改,将其拷贝到桌面,进行修改后,再复制到文件目录下(直接替换) 在hosts文件中添加: # git ...