Kafka的可靠性及各组件的特性

来源:互联网 发布:js动态添加div属性值 编辑:程序博客网 时间:2024/06/16 01:14

消息分发语义(可靠性)

1、消息发送可靠性保证

    1acks=0 --- producer不等待brokeracks。发送的消息可能丢失,但永远不会重发。

    2acks=1 --- leader不等待其他follower同步,leader直接写log然后发送acksproducer。这种情况下会有重发现象,可靠性比only once好点,但是仍然会丢消息。例如leader挂了了,但是其他replication还没同步完成。

    3acks=all --- leader等待所有follower同步完成才返回acks。消息可靠不丢失(丢了会重发),没收到ack会重发。

2、消费者的可靠性保障(关键是保存offset的时机):

    1)至多一次(at most once):读取消息->保存offset->处理消息。处理消息时崩溃则会丢失消息,因为此时offset已经改变了。

    2)至少一次(at least once):读取消息->处理消息->保存offset。保存offset失败,会造成重复消费,但是不会丢消息。如果重读消费时幂等操作,那就不会出现重复消息了。前面2个步骤失败可以在offset位置重新消费。

    3)有且仅有一次(exactly once):保存offset和处理消息这两个环节采用two-phase commit(2PC)。但是,在Kafka中,一种更简单的方法就是可以把offset和处理后的结果一起存储。有点把处理结果和offset做成原子性的感觉。这样可以避免重复消费。

Kafka的组件和特性

1、分区

1)灵活性(负载均衡控制、灵活消费)

AKafka允许Partition在集群内的Broker之间任意移动,以此来均衡可能存在的数据倾斜问题。

BPartition支持自定义的分区算法,例如可以将同一个Key的所有消息都路由到同一个Partition上去。

C、同时Leader也可以在In-SyncReplica中迁移。由于针对某一个Partition的所有读写请求都是只由Leader来处理,所以Kafka会尽量把Leader均匀的分散到集群的各个节点上,以免造成网络流量过于集中。

     分区有偏移量的概念。消费者通过控制偏移量,可以灵活的消费消息。

2)并发性

任意Partition在某一个时刻只能被一个Consumer Group内的一个Consumer消费(反过来一个Consumer则可以同时消费多个Partition)Kafka非常简洁的Offset机制最小化了BrokerConsumer之间的交互,这使Kafka并不会像同类其他消息队列一样,随着下游Consumer数目的增加而成比例的降低性能。此外,如果多个Consumer恰巧都是消费时间序上很相近的数据,可以达到很高的PageCache命中率,因而Kafka可以非常高效的支持高并发读操作,实践中基本可以达到单机网卡上限

3)高可用

分区采用leader-follower的组织架构来保证高可用

分区有序消费

kafka中每个分区都是一个顺序、不可变的消息队列。提供一个分区内顺序消费的语义

2、消费者

1High-level api 和 low-level api

Consumer API分为High levelLow level两种。前一种重度依赖Zookeeper,所以性能差一些且不自由,但是超省心。第二种不依赖Zookeeper服务,无论从自由度和性能上都有更好的表现,但是所有的异常(Leader迁移、Offset越界、Broker宕机等)Offset的维护都需要自行处理。

总结:

high level api: zookeeper自动管理offset,自动获取last offset,包括leader迁移、broker宕机都自动化管理

low level api :手动管理offsetleader迁移、broker宕机的事情

2Kafkapull模型

消费者应该从brokerpull数据还是broker应该向消费者push数据,在这方面,kafka遵循比较传统的设计,大多数消息系统,生产者推消息到broker,消费者从broker拉取消息,一些logging-centric的系统,比如 Scribe Apache Flume ,采用非常不同的push模式。事实上,push模式和pull模式各有优劣。push模式很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。push模式的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而pull模式则可以根据consumer的消费能力以适当的速率消费消息。

基于pull模式的另一个优点是,它有助于积极的批处理的数据发送到消费者。基于push模式必须选择要么立即发送请求或者积累更多的数据,稍后发送它,无论消费者是否能立刻处理它,如果是低延迟,这将导致短时间只发送一条消息,不用缓存,这是实在是一种浪费,基于pull的设计解决这个问题,消费者总是pull在日志的当前位置之后pull所有可用的消息(或配置一些大size),所以消费者可设置消费多大的量,也不会引入不必要的等待时间。

3、生产者

1)消息丢失问题

不过Kafka采用MessageSet也导致在可用性上一定程度的妥协。每次发送数据时,Producer都是send()之后就认为已经发送出去了,但其实大多数情况下消息还在内存的MessageSet当中,尚未发送到网络,这时候如果Producer挂掉,那就会出现丢数据的情况。

解决办法:

采用网络中的ack机制。当然这种是可选的。通过配置acks的值来控制。

4、大吞吐量、强大消息堆积能力等特性

1)依赖OS文件系统的页缓存

当上层有写操作时,操作系统只是将数据写入PageCache,同时标记Page属性为Dirty。当读操作发生时,先从PageCache中查找,如果发生缺页才进行磁盘调度,最终返回需要的数据。实际上PageCache是把尽可能多的空闲内存都当做了磁盘缓存来使用。同时如果有其他进程申请内存,回收PageCache的代价又很小。

总结:依赖OS的页缓存能大量减少IO,高效利用内存来作为缓存

2)顺序IO以及常量时间getput消息

顺序IO:只采用顺序IO不仅可以利用RAID技术带来很高的吞吐量,同时可以利用队列来提供常量时间的getput。这样获取消息的效率也就是O(1)了。这种设计方法使得消息访问速度和消息堆积的量剥离了联系。而且操作系统对顺序IO都会进行优化,提升整体顺序IO的性能

3sendfile技术(零拷贝)

传统网络IO流程:

   AOS 从硬盘把数据读到内核区的PageCache

   B、用户进程把数据从内核区Copy到用户区。

   C、然后用户进程再把数据写入到Socket,数据流入内核区的Socket Buffer上。

   DOS 再把数据从BufferCopy到网卡的Buffer上,这样完成一次发送。

4Producer支持End-to-End的压缩。数据在本地压缩后放到网络上传输,在Broker一般不解压(除非指定要Deep-Iteration),直至消息被Consume之后在客户端解压。

当然用户也可以选择自己在应用层上做压缩和解压的工作(毕竟Kafka目前支持的压缩算法有限,只有GZIPSnappy),不过这样做反而会意外的降低效率!!!! KafkaEnd-to-End压缩与MessageSet配合在一起工作效果最佳,上面的做法直接割裂了两者间联系。至于道理其实很简单,压缩算法中一条基本的原理重复的数据量越多,压缩比越高。无关于消息体的内容,无关于消息体的数量,大多数情况下输入数据量大一些会取得更好的压缩比。

知识点扩展:MessageSet        https://segmentfault.com/a/1190000006875926

Kafka高可用

kafka每个主题分区的复制日志跨多个可配置的服务器(可设置 topic-by-topic 的复制因子),允许自动故障转到这些副本,当集群服务器发生故障时,消息仍可用。

kafka通过分区的复制,来实现高可用。当leader挂了,可以重新选举新的leader来保证消费的高可用.

选举算法(选日志最完整的作为新leader

总结:zkquorum选举适用在共享集群配置而不是主数据存储。因为其吞吐量低,容忍故障所需要的冗余副本比较多