在kafka中,获取数据是采用的拉取(pull)模式,为什么会这样选择,我们可以对比下推送(push)和拉取(pull)有何不同。
在推送(push)系统中:
1.数据从节点推送到消费者(consumer)的速率是由节点来控制的,根据消费者的消费速率来最大化的推送数据给消费者,但是一旦生产者产生数据推送给节点的速率远大于消费者处理数据的速率,那么节点推送给消费者的消息就会造成消费者所在的服务器内存溢出等错误;
2.因为是节点主动推送消息给消费者,所以消息将会在节点上面积累一定的数据量或者在规定的时间里就把消息发送给消费者进行消费,在进行数据推送的时候它是不会关心消费者是否能够及时处理以及是否能够进行处理的。
3.为了避免消费者因为处理不及引起的不能进行处理消息,因此在推送系统中为了实现低延迟,可能每次都会一条条的发送数据,这就会造成网络的严重浪费。
在拉取(pull)系统中:
1.消费者可以控制消费速率来满足它是否已经超过消费负载来进行消费的拉取操作,其缺点就是不能很好的最大化利用系统性能及消费速率。
2.因为消费者是主动去拉取数据,它可以对发送给消费者的数据进行批量处理。每次获取的数据都是根据当前指定位置在日志记录中拉取,因此消费者不会担心来不及消费数据的问题。也因此可以不用引入低延迟也能获取很好的处理效果。
3.如果节点上面没有数据,那么消费者会不停的"空转",这会造成资源的浪费,为了避免这种情况,需要提供一定的阻塞等待直到数据的到来或者一定的时间后。
在消费者系统中关于记录消费位置,在大多数的这种系统中,在消息发送给消费者后节点都会记录使用了哪些数据,而这些使用过的数据要么在节点上面马上记录或者直到消费者确认后在记录,但是为了更好的记录数据在消费到了什么位置,同时保持数据的较小性,这些消费系统都会把消费后的数据删除掉。但是无论是马上记录消费消息还是消费者确认后进行消息删除都会有问题,如果是马上把发送的消息进行删除,如果因为网络问题消费者并没有接收到消息,那么消息就会丢失掉,如果使用消费者确认的机制进行消息删除,当消费者消费完消息后,在回传确认机制的时候如果遇到网络问题,那么就会导致二次消费,同时对于每一个单一消息来说,节点也会记录其相应状态来保证消息不会被重复发送,这个就会影响到性能。但是kafka却使用了另外一种思路来实现,在kafka中每个主题被划分为一组完全有序的分区,每个分区在任何给定时间都由每个订阅用户组中的一个消费者使用,也就是说在有序的分区中消费者的消费位置在分区上也只是一个数字而已,它只是记录了消费者消费到了什么地方,记录的是下一次消费开始的偏移量,因此可以定期的检查这个位置的状态,这样对于我们消息消费的确认也是非常方便的。而且这种实现机制也可以让我们可以手动的设置消息消费的偏移量来反复的消费消息。
在了解完kafka的消息获取机制后,我们也需要了解kafka是如何保证消息能够正确的从生产者接收并且消费者消费完消息后如何保证下一次消息的消费是从最新的位置开始消费的。在kafka里面提供了3种机制用于保证消息的传递:
1.At most once--消息最多传递一次,这种方式可能导致消息的丢失并且不会重新传递。
2.At least once--消息至少被传递一次,这种方式消息一定不会被丢失并且可以重新传递。
3.Exactly once--恰好一次,这种方式也是大家最需要的方式,这种方式消息被传递一次,并且只有一次。
在0.11.0.0版本之前,如果生产者提交的消息没有收到来自kafka节点收到并已保存消息成功的确认,那么生产者是可以再次将消息发送给kafka节点的,它提供了at-least-once语法来保证消息能正确的发送给kafka节点并且保存到log中。在0.11.0.0版本及之后,kafka为了保证重新发送的消息不会再次被保存到log中,提供了幂等性操作,为了做到幂等性,kafka为每个生产者提供了一个唯一的id以及每条消息都提供了一个序列号来保证消息的不重复。同样从0.11.0.0开始,生产者支持使用类似事务的语义将消息发送到多个主题分区的能力:即要么所有消息都成功写入,要么都没有写入。这个的主要用例是Kafka主题之间的exactly-once处理。当然也并不是所有的生产者都必须要求数据发送成功,对于延迟敏感的使用,可以让生产者指定它想要的持久性级别。如果生产者指定它想要等待提交的消息,那么它可以花费10毫秒的时间。但是,生产者也可以指定它想要完全异步地执行发送,或者它想要只等待leader(但不一定是follower)收到消息。
对于消费者来说,消费者控制log的消费位置。如果消费者端从来没有宕机,那么它的消费位置将会保存在内存中,但是如果宕机了,当另外一个新的消费者来进行消费这个主题分区的时候,这个消费者就需要知道从什么地方开始消费log,因此这里有2种可选方式进行消费记录的更新:
1.先读取消息,保存在日志的偏移位置,然后在进行处理消息。这种方式可能会出现记录了偏移位置后,但是消费者宕机了,导致消息没有被正确执行解析,后面的另外的消费者来消费的时候就会从记录的偏移位置开始消费消息,这就导致了部分消息的丢失。这种实现方式就是at-most-once最多消费一次的类似语法。
2.先读取消息,然后消费,最后记录消费的日志偏移位置。这种方式可能会在消费者消费完消息后,但是突然宕机了,导致处理后的消息偏移位置没有正确的返回给节点进行记录,当另外一个消费者来消费的时候就会从已经被处理过的消息的偏移量出重新消费消息,这就会导致重复消费的嫌疑。这种方就是at-least-once至少消费一次的类似语法。
在上面只是提到了at-most-once和at-least-once的实现,那么在kafka中是如何实现Exactly once语法的呢?在消息从kafka最新版本0.11.0.0中,我们利用了最新的事务生成器的功能。消费者消费的位置将会作为一个消息存储在一个topic中,因此,我们可以将偏移量写入Kafka与接收处理数据的输出主题相同的事务中,当事务失败的时候,消费者的位置将会被重置为旧的偏移位置值,同时根据其“隔离级别”,产生的输出主题将会对其他的消费者不可见。在默认的“read_uncommitted”隔离级别中,所有消息对消费者是可见的,即使它们是中止的事务的一部分,但是在“read_committed”中,消费者将只返回自已提交事务的消息(以及任何不属于事务的消息)。因此,Kafka有效地支持在Kafka流中Exactly once交付,而事务生产者/消费者通常可以在Kafka主题之间传输和处理数据时提供Exactly once交付。如果需要和其他系统实现Exactly once的交付,那么就需要和其他系统进行个性化配置操作,否则,Kafka默认保证至少一次传递,并且允许用户在处理一批消息之前通过禁用生产者重试和在消费者中提交偏移量来实现最多一次的传递。