At most once:消息可能会丢失,但不会重复。
At least once:消息不会丢失,但有可能重复。
Exactly once:正好一次。消息不会重复也不会丢失。
在0.11.0.0之前,如果生产者未能收到已提交消息的响应,除了重新发送消息外别无选择,但提供了至少一次的语义保证。即原始请求实际上已经成功,但是遇上某些意外情况,则会在重新发送时将消息再次写入日志。
如:网络抖动、超时等问题,导致Producer没有收到Broker返回的税局Ack,则Producer会继续重试发送消息,从而导致消息重复发送。
从0.11.0.0开始,Kafka生产者支持幂等选项,该选项保证重新发送不会导致日志中出现重复条目
从0.11.0.0开始,Kafka支持类似于将消息发送到多个分区的事务能力:即,所有消息要么全部成功写入,要么都不成功。
为了实现Producer的幂等语义,Kafka引入了ProducerID和Sequence Number
而Broker 端也会为每个
注意:幂等设计只能保证单个 Producer 对于同一个Partition的Exactly Once语义
Kafka的事务处理,主要是允许应用可以把消费和生产的batch处理(涉及多个partition)在一个原子单元内完成,操作要么全部完成、要么全部失败。例如:从某个Topic消费数据,经过一系列转换后写回另一个Topic,这个过程中要保证从源Topic读取与向目的Topic写入的原子性,这将有助于从故障中恢复。
为了实现这种机制,我们需要应用能提供一个唯一id,即使故障恢复后也不会改变,这个id就是Transactionnal.id(也叫txn.id)。Transactionnal.id跟内部的PID可能(可能是因为Producer也许会发生故障)一一对应,不同之处在于Transactionnal.id由用户提供,而PID是Producer内部生成的。
Kafka事务性语义提供的保证主要有以下三个:
# Producer支持的事务接口
public interface Producer<K, V> extends Closeable {
// 初始化事务
// 确保前一个生产者实例发起的具有相同的transactional.id的事务已完成
// 如果前一个实例事务在执行中失败,则中止。如果最后一笔交易已开始但尚未完成,则等待其完成
// 获取生产者ID和epoch,用于后续事务中生产者发出的消息
void initTransactions();
// 开启事务
void beginTransaction() throws ProducerFencedException;
// 将指定偏移量的列表发送给Consumer Group Coordinator
// 并将这些偏移量标记为当前事务的一部分
// 仅当事务成功提交后,这些偏移量才被视为已提交
// 提交的偏移量是应用程序将使用的下一条消息,即lastProcessedMessageOffset +1
void sendOffsetsToTransaction(Map<TopicPartition, OffsetAndMetadata> offsets, String consumerGroupId) throws ProducerFencedException;
void sendOffsetsToTransaction(Map<TopicPartition, OffsetAndMetadata> offsets, ConsumerGroupMetadata groupMetadata)
throws ProducerFencedException;
// 提交事务
void commitTransaction() throws ProducerFencedException;
// 回滚事务
void abortTransaction() throws ProducerFencedException;
}
时间轮,是一个高效的延时队列,或者说定时器。Kafka中的时间轮(TimingWheel)是一个存储定时任务的环形队列,底层采用数组实现,数组中的每个元素可以存放一个定时任务列表(TimerTaskList)。TimerTaskList是一个环形的双向链表,链表中的每一项表示的都是定时任务项(TimerTaskEntry),其中封装了真正的定时任务TimerTask。
Connect是Kafka的一部分,是一种用于在Kafka和其他系统之间可扩展的、可靠的流式传输数据的工具。Kafka Connect可以提取整个数据库或将所有应用服务器中的指标收集到Kafka主题中,使数据可用于低延迟的流处理。它为在Kafka和外部数据存储系统之间移动数据提供了一种可靠且可伸缩的方案。
它使得能够快速定义将大量数据集合移入和移出Kafka的连接器变得简单。导出作业可以将数据从Kafka Topic传输到二次存储和查询系统,或者传递到批处理系统以进行离线分析。
Kafka Connect功能包括:
如果你想把数据从MySQL移动到ElasticSearch中,你会使用什么方案?
如果你想把MySQL中的数据或者XML数据转成JSON写入到ElasticSearch中,或者转换成Parquet写入到Hadoop HDFS中,或者转成CSV写入到S3中,你会使用什么方案?
在很多公司的应用中,经常需要使用Logstash把日志导入到ElasticSearch中,也需要使用Flume把日志导入到Hadoop HDFS中,很多时候也需要使用工具把Oracle中的数据导入的Hadoop HDFS中,也需要使用工具把MySQL中的数据或者把XML导入到Oracle。但是这几种场景有一个共性:
Connect的几个基本概念:
存储各个connector、task的状态信息。在Worker分布式的情况下,存储在Kafka 的一个Topic中,topic的名字由worker配置项status.storage.topic来指定。
Status 有5种:
在运行时,会定时更新状态信息。
存储各个source task 的下一次创建的task实例的序号offset。在运行时会更新这些source task的offset信息。单机模式、分布式模式下,都会存储这个offset的。只是在Standalone模式下,是以本地File方式存储。分布式模式下,是存储在Kafka中的一个topic中。Topic的名称由worker配置项offset.storage.topic来指定。
在Kafka connect中,每一个Connector,以及与之关联的Task都会有一些配置信息。在rebalance后,还是需要用到这些配置的。为了使得Worker Group内共享配置,也需要对connector、task的配置进行存储。
连接器可以配置转换,以进行一次轻量级的消息修改。可以在连接器配置中指定转换链:
# 转换别名列表,按指定的顺序执行转换
transforms=alias1,alias2
# 执行转换的类名称
transforms.$alias.type=
# 转换的配置属性
transforms.$alias.$transformationSpecificConfig=
Kafka Transformations包含一些广泛应用的的类型和路由:
类型或路由 | 说明 |
---|---|
InsertField | 增加一个静态字段者元数据 |
ReplaceField | 过滤或者重命名字段 |
MaskField | 将字段替换为类型 |
ValueToKey | 提取的记录值上的字段名称作为key |
HoistField | 将数据包装为Struct或Map中的单个字段 |
ExtractField | 从Struct和Map中提取特定字段,然后在结果中仅包含此字段 |
SetSchemaMetadata | 修改schema名称或版本 |
TimestampRouter | 根据原主题和时间戳修改一条记录的topic。当使用需要根据时间戳写入不同表或索引时很有用。 |
RegexRouter | 根据原主题,替换字符串和正则表达式修改一条记录的topic |
$ connect-standalone.sh config/connect-file.properties config/connect-file-source.properties config/connect-file-sink.properties
# 启动分布式连接器
$ connect-distributed.sh config/connect-distributed.properties
# 添加连接器
$ curl -XPOST --header "Content-Type:application/json" localhost:8083/connectors -d '
{
"name":"mysql-product-connector",
"config": {
"connector.class":"JdbcSourceConnector",
"connection.url":"jdbc:mysql://192.168.1.139:3306/test?user=root&password=123456",
"table.whitelist":"product",
"validate.non.null":false,
"topic.prefix":"mysql-",
"mode":"timestamp",
"timestamp.column.name":"create_time"
}
}'
要在Kafka和其他系统之间复制数据,用户创建自定义的从系统中pull数据或push数据到系统的Connector(连接器)。Connector有两种形式:
connector不会执行任何复制自己的数据:它们的配置展示了要复制的数据,而Connector是负责将该作业分解成一组可以分配给worker的任务。这些任务也有两种相对应的形式:
REST API | 说明 |
---|---|
GET /connectors | 返回所有正在运行的connector |
POST /connectors | 新建一个connector; 请求体必须是json格式并且需要包含name字段和config字段,name是connector的名字,config是json格式,必须包含你的connector的配置信息 |
GET /connectors/{name} | 获取指定connetor的信息 |
GET /connectors/{name}/config | 获取指定connector的配置信息 |
PUT /connectors/{name}/config | 更新指定connector的配置信息 |
GET /connectors/{name}/status | 获取指定connector的状态,包括它是否在运行、停止、或者失败,如果发生错误,还会列出错误的具体信息。 |
GET /connectors/{name}/tasks | 获取指定connector正在运行的task |
GET /connectors/{name}/tasks/{taskid}/status | 获取指定connector的task的状态信息 |
PUT /connectors/{name}/pause | 暂停connector和它的task,停止数据处理知道它被恢复。 |
PUT /connectors/{name}/resume | 恢复一个被暂停的connector |
POST /connectors/{name}/restart | 重启一个connector,尤其是在一个connector运行失败的情况下比较常用 |
POST /connectors/{name}/tasks/{taskId}/restart | 重启一个task,一般是因为它运行失败才这样做 |
DELETE /connectors/{name} | 删除一个connector,停止它的所有task并删除配置 |