继续上一篇。
The consumer:
kafka的消费者通过向broker发送“fetch”请求获取他要消费的partition的信息。消费者在每个请求中标记他已经消费到的offset值,broker将以该offset作为起始位置的a chunk of log即一批消息返回给consumer。可见消费者自己维护消费状态,broker是无状态的,如有需要可重复消费。
Push vs Pull
在kafka的设计中,producer将消息push给broker,consumer从broker那里pull消息进行消费。基于push的模式,很难适应不同特点的consumer,push时,消息的发送速率完全由broker掌控。该设计的初衷是消费者以最大的速率进行消费,但是
每个consumer的硬件性能、消费能力不同
,一旦消费速度远远落后于生产速度,就会出现拒绝服务等异常。pull模式消费者可以依据其自身能力进行消费,每次消费完后他都会pull一批消息(可以设置size),没有不必要的等待时间。
pull模式的缺点是:当broker中没有未被消费的数据时,即offset已经是最新值了,consumer会一直循环,进入忙等待的状态。为了避免这种情况,允许consumer阻塞在“long poll”的等待中,直到数据到达,(也可以设置为一直等待until a given number of bytes)。consumer的配置文件中可以设置:fetch.min.bytes,表示consumer发起一次fetch请求,broker应该返回给他的最小字节数,如果broker端没有这么多消息,则请求被阻塞,一直等待,累积够这么多数据才返回。同时为了避免无止境的等待,可以设置:fetch.wait.max.ms,表示等待的最长时间。
Consumer Position
追踪记录已经被消费掉的数据非常重要,kafka中利用offset,且由consumer自己维护。
许多消息系统会在broker端保存元数据信息,记录哪些消息已被消费过。这种情况下存在一个问题:当一条消息发送给consumer之后,broker可以立即修改状态变为已消费,或者等到consumer的确认后才修改状态。如果broker发出消息后立即更新状态标记为consumed,则可能发生意外,使consumer未真正消费到这条消息,消息被丢失;为了克服这一点,许多消息系统增加了确认机制,即:发出消息后只是标记为send,等consumer真正消费完返回确认信号后才标记为consumed。这种方式确实可以避免丢失消息,但如果consumer已处理了该条消息,但是发送确认信号之前出故障了,那么确认丢失,消息便会被重复消费两次;另一个缺点就是增加确认机制必然导致性能降低,broker需要为每条消息维护多个状态,还需要处理异常情况。broker负载太重。
(kafka中broker无状态,consumer自己维护offset,同样可以在发出fetch请求后更新offset值,或者消费完这条消息之后才修改offset。你可以根据实际应用对可靠性的需求选择任意一种方式,立即修改值可能导致发生故障时,例如网络断开,消息得不到处理便丢失了。)
kafka的设计则避免了以上的复杂情况。consumer只消费一个partition,他只需要维护一个整形数值,表明下次消费的消息位置。而且,consumer会定期向zookeeper提交他的offset,避免自己crash之后继续消费。(可以详细看一下consumer的参数)
Message Delivery Semantics
消息传递的可靠性保证:(涉及producer到broker、consumer与broker,即生产者端和消费者端)
at most once:消息可能会丢失但绝不重传;
at least once:从不丢失,可能重传;
exactly once:最理想的状态,消息只被传送一次,不丢失也不重传,kafka目前不能保证;
对于producer:发送一条消息给broker,只有消息被commit to the log,才算发送成功。由于broker 有备份机制,所以用户可以设置自己想要的可靠性:
request.required.acks:
- 0:producer发出消息即完成发送,不等待确认,这种方式延迟最小、可靠性最差,最容易丢失消息;
- 1:当且仅当leader收到消息返回确认信号后认为发送成功,只有当leader crash,而且未被同步至其他follower时才丢消息;
- -1:只有当leader以及所有follower都收到消息确认后,才发送成功,最好的可靠性,延迟也较大。但是还是有可能丢消息;
对于consumer:消费一条消息,之后更新offset,有以下几种方式:
- 读消息,更新offset,最后处理消费消息:可能未处理之前consumer crash,但已经更新了offset,consumer重启后或者一个新的consumer会从offset之后的位置继续消费,所以丢失了数据,at-most-once;
- 读消息,处理消费消息,最后更新offset:可能处理之后,更新offset之前crash,消息会被重复处理,at-least-once;
- 怎么实现exactly-once:如果可以把offset和消息消费后的output保存在一起,eg都保存在HDFS文件中,就可以保证他们被同时更新,这样就可以保证output时刻和offset保持同步。