kafka是什么?
Kafka 是一个广受欢迎的流式处理平台。你可以认为它是一个专门用于将信息从一个地方放置到另一个地方的服务。它能帮助你在众多服务间构建异步事件处理模型,创建生产-消费机制或者均衡地分布式作业 - 这方面的案例多不胜数。
无论出于何种目的而使用kafka,你都有必要了解一些kafka的基础理论。
生产者和消费者
kafka包含生产者和消费者的概念,前者推送消息到kafka,而后者从kafka获取这些消息。
由于有各种各样的消息数据通过kafka被处理,因此为了将它们分类理顺,kafka允许你为不同类型的消息创建独立的处理上下文,即以主题(topics)的形式将消息进行分组。
每一个试图推送消息的生产者都必须为消息提供一个topic名称。
此外,消费者也会订阅一系列的topics()继而可以从这些topics中消费消息
消费者组
在现实场景中,你想要每个感兴趣的服务从某个特定的topic接收每一条消息,同时你希望你的消息精确地递送至每个服务的某个实例;这是消费者组就可以登场了。
最根本的,如果你想将一类消息分发给一群消费者处理或者将这些消费和其他组的消费者区分开来, 你会将这些消费者添加到一个消费者组。
每个topic中的每条消息会被递送到每个消费者组,但在消费者组内,单条消息只会被某一个消费者处理(组内其他的消费对这条消息是无感的)。
从下图中也可以看到,每条消息会终结于每个消费者组中的某个特定消费者。
分区
现在让我们聚焦到单个的消费者组中。假设你有一个叫作EmailGroup的组,该组包含3个消费者;那么kafka会将消息投递至哪个消费者呢?
事实上,每个topic被划分为了多个分区,分区是整个topic的一个数据分块,一条消息只会出现在其中一个分区上。
生产者生产的消息包含键和值两部分;值就是你想投递的数据,键也可以用来传递信息,但它更适合用来作分区键,生产者会将该键进行hash,以确定将对应的消息发往哪个分区。这就意味着,如果你想发送两条不同的消息到相同的分区,那么你得为它们设置同样的键。
很好,目前为止,我们知道了生产者如何与分区协同,但是消费者方面呢?
消费者将会订阅一个topic(加入消费者组是必然的)并且拉取它所对应的分区中的消息。这儿最重要的概念是每个分区必然被确定的单个消费者消费,而每个消费者则能够订阅多个分区(也可能0个分区,比如消费者数量大于分区数量)。
聚焦至分区,你会看到kafka是可以保证单个分区消息有序的。假设有条消息A(键为阿勇)被发送到一个topic,之后有一条消息B(键也为阿勇)也发送到该topic;然后消费者将会依次先接收到A然后是B,当然这种顺序仅对单个分区有效,不同分区的消息将以不确定的顺序被消费。
实际上,分区只是一个仅允许往后追加的日志,这也是保证顺序的原因。另一方面,不同的分区是不同的数据分片(日志),因此无法保证顺序。
作为图形化的解释,这里生产者发布了3条消息——A、B和C——都具有相同的键。分区看起来是这样的:
如果消费者现在订阅了这个主题,并且被分配到这个分区,那么它将首先接收UserCreatedA,然后是UserCreatedB,最后是UserCreatedC。
Offset
现在你可能会问:“我的应用如何追踪哪条消息已经被消费,接着是哪条消息将被拉取?”,由于在kafka中消息日志是线性结构,分区中每条消息都被分配了确定的offset(位移偏量),比如分区中第一条消息offset记作0,吓一条记作1,以此类推。
基于以上原理,kafka中每条消息能够通过topic名称,分区号和offset作唯一标定。
消费者使用offsets来指定它在一个log中的消费位置。事实上,offsets存在两种,一种是在kafka中可持久化的用于在消费者崩溃时作为消费存根,另一种是在消费者端本地存储用于协调持续的消息轮询。
已提交的offset
第一种偏移量是提交偏移量,它用于在消费者崩溃时标记该消费者最后的消费点。
比如,有个新启动的消费者要消费一个新的topic,它从offset-0拉取了10条消息;消息被处理后,消费者想标记消息为“已消费”并通知kafka,这个过程叫作“提交”,简而言之,消费者想要告诉集群记住它已经消费到位置offset-10了。
现在,即使服务端/消费者宕机,在恢复后,新的消费者接入进分区同样可以从offset-10之后继续消费。
提交的offset被持久化到Kafka中,只有通过提交才能更改。使用者在连接到集群时获取此offset,以了解前一个消费者已消费到何处。
消费端offset
你或许会问,当你拉取消息的时候使用哪个offset?是已提交的那个?看下面的代码,对poll方法连续调用了两次,但是中间却没有commit操作。你会期待第二次调用poll得到什么?
val anyTimeout = 100
consumer.poll()
consumer.poll()
您当然希望代码会继续执行,所以第二次poll应该会返回下一批消息,对吗?事实却是如此。代码背后,消费者记住了一个叫作position的东西;每次您调用poll并确定下一条要获取的消息时,它都会增加,而与此同时,如果您调用某个提交方法,offset则会被提交。
通过调用以下方法中的一个,你能改变消费者的position:
consumer.seek(somePartition, newOffset)
consumer.seekToBeginning(somePartitions)
consumer.seekToEnd(somePartitions)
最后,初始offset是多少呢?如果你新建了一个消费者组,刚开始不会有提交的offset。
kafka允许你选择一个策略,记住这个策略设置只会在没有产生已提交offset时才会生效,否则如何已提交offset已经存在,kafka将会使用该offset;
可选的策略如下:
- Earliest - 偏移量将设置为可用的最低/最早偏移量
- Lastest - 偏移量将设置为可用的最大/最新偏移量
- None - 将抛出异常,因此可以手动处理
balabala