kafka总结
主要应用场景为流式计算,提供发布订阅的流式平台自身不会产生数据只是平台
kafka的优点 吞吐量大 延迟低
topic(主题)含有 k v 时间戳 key可以为空 创建topic指定分区 分区可以变多但不能变少
kafka服务只有一个进程broker
没有主从之分 主从都只有broker 服务没有主从但是却有主备节点之分,主节点leader工作和生产者沟通和消费者沟通,从节点flower收集
分区
副本(副本数不能大于分区数否则会报错)
日志(存放在硬盘里 只能追加不能修改基于日志系统做的,日志系统顺序读写600m/s,随机读写100k/s)
发布
消费(维护自身的offset)
topic可以多订阅被不同的消费者,每条数据都有自增性(每个分区的offset单独统计)
我们的数据往topic里写具有顺序性而不是排序性
解耦大数据相关的组件:
1自身是一个平台
2语言无关性消费者和生产者的语言可以不一样
3生产者与消费者无关联性只需维护一个文档还有数据类型 消费者与消费者之间无关性(各自维护自身的offset与平台无关)
在0.10版本之前维护在zk中 之后维护在topic里
消费者组概念:
同一消费者组里可以同时消费多个topic,但是同一topic在同一时刻只能被一个组里的一个成员消费.(其主要目的是负载均衡)
一缓存技术:
我们将系统构建在JVM之上,用过java内存的都应该知道两件事:
- 对象的存储开销非常大,会将原始数据的大小自动double甚至更加糟糕。
- Java的GC 会随着堆内数据的越来越多而变得越来越频繁和缓慢。
将文件数据缓存到文件系统的页缓存上是优于缓存到进程内存里的,比如,我们在32G的机子上使用页缓存缓存28-30G 的数据是不需要太多的GC的,而如果我们缓存到进程内存中,将会有很大的性能损耗,因为GC还有重新启动需要加载等等原因(页缓存即使重启也是热缓存而不是冷缓存)。那么对于kafka的顺序读写来说,做一个页缓存的预加载是很不错的一个设计。
与其先将数据加载到内存中再将数据刷写到文件系统上,不如立即将数据写入文件系统日志中,只需要将数据传输到系统内核的也缓存上就Ok了
二常量耗时需求:
消息系统常用的数据结构是B-Tree,虽然B-Tree的时间复杂读为O(log N),但对于磁盘操作却不是这样的,通常当数据量增大一倍时B-Tree的性能将会下降一倍。
那么如果简单的将读写分离,就像kafka一样,写操作就是顺序写,不存在随机写,读操作也是顺序消费。这样做的好处所有操作都是O(1)性能,并且读写操作不会发生堵塞。这样性能和数据大小无关。(使用大量廉价存储就成为了可能。)
三效率:
有两种操作会导致效率地下:过多的小I/O和过多的字节拷贝。
小I/O过多就会造成大量的网络链接资源的浪费,所以kafka为了避免这种现象,在设计是使用了消息集合这种概念。那么服务器每次将会变成一堆数据的写入,消费者变成一堆数据的读取。这种简单的优化可以提升大量的性能, 批量处理导致大的网络数据包, 大的磁盘顺序读写, 连续的内存块等等, 所有的这些能把kafka的间接性的随机消息写改成线性写入后, 发送给消费者。人类的1s相对于机器是很长的时间 所以可以在人能接收的时间内批次处理消息
四段到段的批压缩:
大多数情况下,性能瓶颈不是CPU和磁盘,而是网络I/O,尤其是对于数据中心这种需要广域网之间的数据传输。kafka通过递归的消息集合支持这样的操作. 一批的消息可以被收集在一起后压缩, 并发送到服务器端. 这样被压缩的一批数据, 只有在消费者消费的时候才会被解压。目前支持GZIP, Snappy 和LZ4三种压缩格式
五负载均衡:
负载均衡不多说了,主要是分区策略需要简单说一下。
分区策略:kafka默认提供的是Hash的分区策略,根据Key的值。 在这里可以自己定义分区规则 继承partitioner类
五Push vs. pull
我们考虑的一个初步问题是Consumer应该从Broker拉取数据还是Broker将数据推送给Consumer。在这方面,Kafka和大多数消息系统一样,采用传统的设计方式,由Producer向Broker推送数据,Consumer从Broker上拉取数据。一些日志中心系统,如Scribe和Apache Flume,遵循数据向下游推送的方式。两种方式各有利弊。基于推送的方式,由于是由Broker控制速率,不能很好对不同的Consumer做处理。Consumer的目标通常是以最大的速率消费消息,不幸的是,在一个基于推送的系统中,当Consumer消费速度跟不上生产速度 时,推送的方式将使Consumer“过载”。基于拉取的系统在这方面做的更好,Consumer只是消费落后并在允许时可以追上进度。消费者通过某种协议来缓解这种情况,消费者可以通过这种方式来表明它的负载,这让消费者获得充分的利用但不会“过载”。以上原因最终使我们使用更为传统的Pull的方式。
Pull模型的另一个优势是可以聚合数据批量发送给Consumer。Push模型必须考虑是立即推送数据给Consumer还是等待聚合一批数据之后发送。如果调整为低延迟,这将导致每次只发送一条消息(增加了网络交互)。基于Pull的模式,Consumer每次都会尽可能多的获取消息(受限于可消费的消息数和配置的每一批数据最大的消息数),所以可以优化批处理而不增加不必要的延迟。
基于Pull模式的一个缺陷是如果Broker没有数据,Consumer可能需要busy-waiting的轮训方式来保证高效的数据获取(在数据到达后快速的响应)。为了避免这种情况,我们在Pull请求中可以通过参数配置“long poll”的等待时间,可以在服务端等待数据的到达(可选的等待数据量的大小以保证每次传输的数据量,减少网络交互)。
你可以想象其他一些从端到端,采用Pull的可能的设计。Producer把数据写到本地日志,Broker拉取这些Consumer需要的数据。一个相似的被称为“store-and-forward”的Producer经常被提及。这是有趣的,但是我们觉得不太适合我们可能会有成千上万个Producer的目标场景。我们维护持久化数据系统的经验告诉我们,在系统中使多应用涉及到上千块磁盘将会使事情变得不可靠并且会使操作它们变成噩梦。最后再实践中,我们发现可以大规模的运行强大的SLAs通道,而不需要生产者持久化。
六消费者offset:
记录哪些消息被消费过是消息系统的关键性能点。
大多数消息系统在Broker上保存哪些消息已经被消费的元数据。也就是说,Broker可以在消费传递给Consumer后立即记录或等待消费者确认之后记录。这是一个直观的选择,并且对于单个服务器而言并没有更好的方式可以存储这个状态。大多数消息系统中的存储设备并不能很好的伸缩,所以这也是务实的选择——当Broker确认消息被消费后就立即删除,以保证存储较少的数据。
让Broker和Consumer关于那些消息已经被消费了达成一致并不是一个简单的问题。如果Broker在将消息写到网络之后就立即认为消息已经被消费,那么如果Consumer消费失败(Consumer在消费消息之前Crash或者网络问题等)消息将丢失。为了解决这个问题,一些消息系统增加了ACK机制,消息被标记为只是发送出去而不是已经被消费,Broker需要等待Consumer发送的ACK请求之后标记具体哪些消息已经被消费了。这个策略修复了消息丢失的问题,但是引起了新的问题。第一,如果Consumer处理了消息,但是在发送ACK给Broker之前出现问题,那么消息会被重复消息。第二,Broker需要维护每一条消息的多个状态(是否被发送、是否被消费)。棘手的问题是要处理被发送出去但是没有被ACK的消息。
Kafka采用不同的方式处理。Topic被划分为多个内部有序的分区,每个分区任何时刻只会被一个group内的一个Consumer消费。这意味着一个Partition的Position信息只是一个数字,标识下一条要消费的消息的偏移量。这使得哪些消息已经被消费的状态变成了一个简单的数据。这个位置可以定期做CheckPoint。这使得消息的ACK的代价非常小。
这个方案还有其他的好处。消费者可以优雅的指定一个旧的偏移量并重新消费这些数据。这和通常的消息系统的观念相违背,但对很多消费者来说是一个很重要的特性。比如,如果Consumer程序存在BUG,在发现并修复后,可以通过重新消费来保证数据都正确的处理。
七加载离线数据:
可扩展的持久化存储的能力,是消费者可以定期的将数据导入到像Hadoop这样的离线系统或关系型数据仓库中。
在Hadoop的场景中,我们通过把数据分发到独立的任务中进行并行处理,按照node/topic/partition组合,充分使用另行能力加载数据。Hadoop提供任务管理,失败的任务可以重新启动,而不需要担心重复数据的危险——任务会从原始位置重新启动。
八kafka 如何保证数据安全性。也就是不丢数据,不重复消费,有且仅处理一次:
现在我们对Producer和Consumer已经有了一定的了解,接着我们来讨论Kafka在Producer和Consumer上提供的语义。显然的,在分发消息时是可以有多种语义的:
At most once:消息可能丢失,但不会重复投递
At least once:消息不会丢失,但可能会重复投递
Exactly once:消息不丢失、不重复,会且只会被分发一次(真正想要的)
值得注意的是这分为两个问题:发布消息的可用性和消费消息的可用性。
许多系统都声称提供“exactly once”语义,仔细阅读会发现,这些声明是误导的(他们没有考虑Producer和Consumer可能Crash的场景,或是数据写入磁盘后丢失的情况)。
Kafka提供的语义是直接了当的。发送消息的时候我们有一个消息被Commit到Log的概念。一旦消息已经被Commit,它将不会丢失,只要还有一个复制了消息所在Partition的Broker存活着。“存活”的定义以及我们覆盖的失败的情况将在下一节描述。现在假设一个完美的Broker,并且不会丢失,来理解对Producer和Consumer提供的语义保证。如果Producer发送一条消息,并且发生了网络错误,我们是不能确认错误发生在消息Commit之前还是消息Commit之后的。类似于使用自增主键插入数据库,是不能确认写入之后的主键值的。
Producer没有使用的强制可能的语义。我们无法确认网络是否会发生异常,可以使Producer创建有序的主键使重试发送成为幂等的行为。这个特性对一个复制系统来说不是无价值的,因为服务器在发生故障的情况下依旧需要提供服务。使用这个功能,Producer可以重试,直到收到消息成功commit的响应,在这个点上保证消息发送的exactly once。我们希望把这个特性加到后续的Kafka版本中。
不是所有的场景都需要这样的保证。对应延迟敏感的场景,我们允许Producer指定其期望的可用性级别。如果Producer期望等待消息Commit,那么这可能消耗10ms。Producer也可以指定以异步的方式发送消息或只等Leader节点写入消息(不能Follower)。
接着我们从消费者的视角来描述语义。所有的副本都拥有偏移量相同的日志。Consumer控制它在日志中的偏移量。如果Consumer一直正常运行,它可以只把偏移量存储在内存中,但是如果Consumer crash且我们期望另一个新的Consumer接管消费,那么需要选择一个位置来开始消费。假设Consumer读取了一些消息——它有集中处理消息和位置的方式。
它可以读取消息,然后保存位置信息,然后处理消息。在这个场景中,Consumer可能在保存位置信息后消费消息失败,那么下一次消费可能从保存的位点开始,尽管之前部分消息被处理失败。这是消费处理过程中失败的at-most-once(只被处理了一次,但是可能处理失败)。
它可以读取消息,之后处理消息,最后保存位置信息。这个场景中,Consumer可能在处理完消息,但是保存位点之前Crash,那么下一次会重新消费这些消息,尽管已经被消费过。这是Consumer Crash引起的at-least-once(消息可能会被处理多次)。
在很多场景冲,消息可以有一个主键,这样可以保证处理的幂等性(多次处理不会有影响)。
那么什么是exactly once语义?这里的限制实际上不是消息系统的特性,而是消息处理和位置信息的保存。经典的解决方案是采用两阶段提交的方式来处理。但是这也可以用一个更简单的方式来处理:通过将消息处理结果和位置信息保存在同一位置上。这是更好的,因为很多Consumer期望写入的系统并不支持两阶段提交。例如, 我们的hadoop ETL工具从保存数据到dhfs上的同时也把位移位置也保存到hdfs中了, 这样可以保证数据和位移位置同时被更新或者都没更新.我们在很多系统上使用类似的模式, 用于解决那些需要这种强语义但是却没有主键用于区分重复的储存系统中.
默认Kafka提供at-least-once语义的消息分发,允许用户通过在处理消息之前保存位置信息的方式来提供at-most-once语义。exactly-once语义需要和输出系统像结合,Kafka提供的offset可以使这个实现变的“直接了当的”(变得比较简单)。
九Replication分区:
Replicated Logs: Quorums, ISRs, and State Machines (Oh my!)
Kafka分区机制的核心是日志复制。日志复制是分布式系统中最基础的东西,有很多方式可以实现。日志复制可以作为基于状态机的分布式系统的基础设置。
日志复制模型用于处理连续、有序的输入(例如给log entry添加0、1、2这样的编号)。有很多方式实现日志复制,最简单的方式是Leader选择和提供这个顺序之。只要Leader节点存活,Follower只需要按照Leader选择的值和顺序来复制即可。
当然,如果Leader不会宕机,那我们也不需要Follower了!在Leader宕机之后,我们需要在Follower中选择一个节点成为新的Leader。Follower可能会宕机或者日志落后较多,所以我们必须确保选择一个“及时同步”(复制进度和Leader最近的节点)成为新的Leader。复制算法必须提供这样的保证:如果Client收到一条消息已经被Commit了,如果Leader宕机,新Leader必须包含这条已经被Commit的消息。这是一个权衡:Leader在确认消息Commit之前需要等待更多的Follower来确认复制了消息来保证在Leader宕机后有更多可以成为Leader的Follower节点。
如果你选择了所需要的ACK的数量以及选择Leader时需要比较的日志数以确保能重合,这个叫做Quorum。
一个通用的来权衡的方式是提交日志和选择Leader时都采用大多数投票的原则。这不是Kafka使用的方式,但是无所谓,让我们去理解这种方式来了解实现原理。假设一共有2f+1个备份,那么f+1的副本必须在Leader提交commit之前接收到消息,这样就可以从f+1个节点中选择出新节点作为Leader。因为任何f+1个节点,必然有一个节点包含最全的日志。还有很多关于这个算法的细节需要处理(如何定义日志更全、在Leader节点宕机时保持日志一致性等)在这里先忽略。
大多数选票的方法有非常好的特性:延迟取决于同步最快的Server节点。这说明,如果备份数为3,那么延迟取决于两个备份节点中较快的节点。
有很多类似的算法变体,例如ZooKeeper的Zab,Raft,Viewstamped Replication等。和Kafka最相似的学术刊物是微软的PacificA。
大多数选票方式的取消是它不能容忍很多的故障,导致你没有可以被选为新Leader的节点。为了容忍一个节点故障,需要3分数据备份,容忍两个节点故障则需要5个节点。在我们的经验中,只有足够的冗余才能容忍单一的故障在实际系统中是不够的,每次写5次副本,使用5倍的存储空间,和1/5的带宽,在大体量的数据存储上不是很可行。这就是为什么quorum算法多多应用在像ZK这样存储配置的集群中,而不是数据存储系统中。例如HDFS的namenode的高可用建立在大多数选票的机制上,但是数据存储缺不是。
Kafka使用一个明显不同的方式来选择quorum集合。代替大多数选票,Kafka动态的维护一个“同步的备份(in-sync replicas ISR)”的集合。只有这个集合中的成员能被选举为Leader。一个写入请求需要同步到所有的同步中的备份才能认为是提交的。ISR集合在变更时会被持久化到ZK。因此,任何ISR中的备份都可以被选举为新的Leader。这对于Kafka这种拥有多分区并且需要保证这节点负载均衡的模型来说非常重要。使用ISR模型和f+1个副本,Kafka可以容忍f个备份不可用的情况。
对于大多数的场景,我们认为这样的妥协是合理的。在实践中,为了容忍f个节点故障,大多数选票原则和ISR方式都需要等待相同的备份在提交消息前进行确认(如需要容忍一个节点故障,大多数选票的选择需要3个节点,并且提交消息需要至少一个备份的确认;ISR只需要两个节点,需要确认的副本数一样是一个)。相对于大多数选票的原则,ISR方式不需要等待最慢的服务器确认消息是一个优势。尽管如此,我们进行改善,让客户端决定是否等待消息提交,使用较小的副本数,这样带来的吞吐和更小的磁盘空间要求是有价值的。
另一个重要的设计是Kafka不需要故障的节点恢复所有的数据。这是不常见的,复制算法依赖于存储介质在任何故障的情况下都不丢失数据并且不违反一致性原则。这个假设有两个主要的问题。第一,磁盘故障是持久化数据系统中最常见的问题,并且它通常导致数据不完整。第二,即使这不是一个问题,我们也不希望在每一次写入之后都使用fsync来保证一致性,这会使性能下降两三个数量级。我们的协议中允许一个副本重新加入到ISR集合中,在重新加入之前,它需要从新同步在故障时丢失的数据。
十Unclean leader election: What if they all die?
Kafka保证的数据不丢失,在至少有一个备份保持同步的情况下。如果一个分区所有的备份的节点都故障,那么就不能提供这个保障了。
但是实践系统中需要一些合理的事情,在所有备份故障时。如果不巧遇上这个问题,去考虑哪些情况会发生是非常重要的。有两种方式去做:
- 等待一个ISR中的副本恢复并将其选举为新的Leader(期望它拥有所有的数据)。
- 选择第一个副本(无需在ISR中)作为Leader。
这是在可用性和一致性之间的权衡。如果我们等待ISR中的备份恢复,那么会在这个期间一直不可用。如果这样的副本被损坏,那么我们将永久性的失效。另一方便,如果使用不在ISR中的备份成为Leader,尽管它可能不包含所有的日志。默认情况下,Kafka使用第二种策略,当所有ISR中的备份不可用时,倾向于选择可能不一致的备份。这个方式可以通过unclean.leader.election.enable配置禁用,在哪些停机时间优于不一致的场景。
这种困境不是kafka特有的, 这存在于任何基于quorum方式的结构中. 例如, 多数投票算法, 如果大多数的服务器都永久性失效了, 你必须选择丢失全部的数据或者接受某一台可能数据不一致的服务器上的数据.
十一Availability and Durability Guarantees
在向Kafka写入数据时,Producer可以选择是否等待0,1或(-1)个备份响应。注意,这里说的“被所有备份响应”不是说被所有分配的备份响应,默认情况下只的时所有ISR集合中的备份响应。例如,如果一个Topic配置成只需要两个备份,并且一个备份故障了,那么写入一个备份即认为收到了所有响应。但是,如果这个备份也故障了,那么数据会丢失。这样保证了分区的最大可用,但是可能不是那些相对于可用性更需要可靠性的用户的需求。因此,我们提供两种Topic级别的配置,相对于可用性,优先保证可靠性: - 禁用unclean leader election;如果所有备份不可用,那么分区保持不可用,直到最近的Leader重新恢复可用。这可能导致不可用,但是不会丢失数据。
- 配置一个最小的ISR大小;分区只会在满足最小ISR的情况下接受请求,这样可以避免数据只写入一个备份,而这个备份后续故障导致数据丢失。这个配置只在Producer使用acks=all的配置时有效。这个配置在一致性和可用性上做了权衡。更大的ISR提供了更好的一致性,但是降低了可用性,如果同步备份数小于最小ISR配置时将不可用。
十二Replica Management
以上的讨论都是基于一个日志,即一个Topic的分区考虑的。但是Kafka集群拥有成百上千这样的分区。我们尝试使用轮训的方式来平衡分区,避免高数量的Topic的分区集中在一部分少量的节点上。同样我们要平衡所有Leader分区,这样每个节点上承载的主分区都有一定的比例。
优化Leader的选举过程也是非常重要的,因为这是系统不可用的窗口期。一个直观的实现是,如果一个节点故障了,为这个节点上所有的分区都独立的执行一次选举。代替这种方式,我们选择一个Broker作为Controller,Controller负责一个故障节点影响的所有分区的Leader变更。这样的好处是我们可以批量处理,减少独立选举时大量的通知,这使得大量分区需要选举时变得更快,代价更小。如果Controller故障了,剩余的Broker中会有一个节点成为新的Controller。
Log Compaction
日志压缩确保Kafka会为一个Topic分区数据日志中保留至少message key的最后一个值。它解决了应用crash或系统故障或应用在操作期间重启来重新加载缓存的场景。让我们深入到细节中解释日志压缩是如何工作的。
到屋面位置,我们只说明了在一断时间或达到特定大小的时候丢弃就日志的简单方法。这适用于想日志这样每一条数据都是独立数据的情况。但是重要类别的数据是根据key处理的数据(例如DB中表的变更数据)。
让我们来讨论这样一个具体的流的例子。一个Topic包含了用户email address信息;每一次用户变更邮箱地址,我们都像这个topic发送一条消息,使用用户ID作为primay key。现在我们已经为用户ID为123的用户发送了一些消息,每条消息包含了email address的变更:
123 => bill@microsoft.com
123 => bill@gatesfoundation.org
123 => bill@gmail.com
日志压缩为我们提供了更精细的保留机制,至少保存每个key最后一个变更(如123 => bill@gmail.com)。这样做我们确保了这个日志包含了所有key最后一个值的快照。这样Consumer可以重建状态而不需要保留完成的变更日志。
让我们列一些日志压缩有用的场景,然后看他是如果被使用的。
- DB变更订阅。这是很常见的,一个数据在多个数据系统中,而且其中一个系统是数据库类型的(如RDBMS或KV系统)。例如可能有一个数据库,一个户缓存系统,一个搜索集群,一个Hadoop集群。DB的任何一个变更需要反映到缓存、搜索集群,最终保存到Hadoop中。在这个场景中,你只需要实时系统最新的更新日志。但是如果需要重新加载缓存或恢复宕机的检索节点,就需要完整的数据。
- 事件源。这是一种应用设计风格,它将查询处理和应用程序设计结合到一起,并使用日志作为程序的主要存储。
- 高可用日志。一个本地集成程序可以通过变更日志来做到容错,这样另一个程序能够在当前程序故障时继续处理。例如, 像流数据查询例子, 如计数, 汇总或其他的分组操作. 实时系统框架如Samza, 就是为了达到这个目的使用这个特性的。
在这些场景中,主要处理实时的变更,但有时需要重新加载或重新处理时,需要加载所有数据。日志压缩允许使用相同的Topic来支持这些场景,这种日志使用风格在后续的内容中会更详细的描述。
想法很简单,我们有有无限的日志,以上每种情况记录变更日志,我们从一开始就捕获每一次变更。使用这个完整的日志,我们可以通过回放日志来恢复到任何一个时间点的状态。这种假设的情况下,完整的日志是不实际的,对于那些每一行记录会变更多次的系统,即使数据集很小,日志也会无限的增长下去。丢弃旧日志的简单操作可以限制空间的增长,但是无法重建状态——因为旧的日志被丢弃,可能一部分记录的状态会无法重建(这写记录所有的状态变更都在就日志中)。
日志压缩机制是更细粒度的,每个记录都保留的机制,而不是基于时间的粗粒度。这个想法是选择性的删除哪些有更新的变更的记录的日志。这样最终日志至少包含每个key的记录的最后一个状态。
这个策略可以为每个Topic设置,这样一个集群中,可以一部分Topic通过时间和大小保留日志,另外一些可以通过压缩保留。
这个功能的灵感来自于LinkedIn的最古老且最成功的基础设置——一个称为Databus的数据库变更日志缓存系统。不想大多数的日志存储系统,Kafka为了订阅而量身打造,用于线性的快速读写。和Databus不同,Kafka作为真实的存储,压缩日志是非常有用的,在上有数据源不能重放的情况下。
十三Log Compaction Basics基础
Log head中包含传统的Kafka日志。它包含了连续的连续的offset和所有的消息。日志压缩增加了处理tail Log的选项。上图展示了日志压缩的的Log tail的情况。tail中的消息保存了初次写入时的offset。即使该offset的消息被压缩,所有offset仍然在日志中是有效的。在这个场景中,无法区分和下一个出现的更高offset的位置。如上面的例子中,36、37、38是属于相同位置的,从他们开始读取日志都将从38开始。
压缩允许删除。一条消息伴随着空的值被认为从日志中删除。这个删除标记将会引起所有之前拥有相同key的消息被移除(包括拥有key相同的新消息),但是删除标记比较特殊,它将在一定周期后被从日志中删除来示范空间。这个时间点被称为“delete retention point”。
压缩操作通过在后台周期性的拷贝日志段来完成。清除操作不会阻塞读取,并且可以被配置不超过一定IO吞吐来避免影响Producer和Consumer。实际的日志段压缩过程有点像如下:
日志压缩提供了如下的保证:
- 所有跟上消费的Consumer能消费到所有写入的消息;这些消息有连续的序列号。Topic的min.compaction.lag.ms可以用于保证消息写入多久后才会被压缩。这限制了一条消息在Log Head中的最短存在时间。
- 消息的顺序会被保留。压缩不会重排序消息,只是移除其中一部分。
- 消息的Offset不会变更。这是消息在日志中的永久标志。
- 任何从头开始处理日志的Consumer至少会拿到每个key的最终状态。另外,只要Consumer在小于Topic的delete.retention.ms设置(默认24小时)的时间段内到达Log head,将会看到所有删除记录的所有删除标记。换句话说,因为移除删除标记和读取是同事发生的,Consumer可能会因为落后超过delete.retention.ms而导致错过删除标记。
Log Compaction Details
日志压缩由Log Cleaner执行,后台线程池重新拷贝日志段,移除那些key存在于Log Head中的记录。每个压缩线程如下工作: - 选择Log Head相对于Log Head在日志中占更高比例的日志
- 创建Log Head中每个Key最后一个offset的摘要
- 从头到尾的拷贝日志,并删除之后日志终于到相同key的记录。新的、干净的日志将会立即被交到到日志中,所以只需要一个额外的日志段空间
- Log Head的摘要实际上是一个空间紧凑的哈希表。每个条目使用24个字节。所以如果有8G的整理缓冲区, 则能迭代处理大约366G的日志头部(假设消息大小为1k)。
Configuring The Log Cleaner
Log Cleaner默认启用。这会启动清理的线程池。如果要开始特定Topic的清理功能,可以开启特定的属性:
log.cleanup.policy=compact
这个可以通过创建Topic时配置或者之后使用Topic命令实现。
Log Cleaner可以配置保留最小的不压缩的日志头。可以通过配置压缩的延迟时间:
log.cleaner.min.compaction.lag.ms
这可以用于保证消息比在被压缩的消息大一段时间。如果没有设置,除了最后一个日志外,所有的日志都会被压缩。当前写入的自如端不会被压缩,即使所有的消息都落后于比配置的最小压缩时间。
更多的配置在这里
9 Quotas
从0.9版本开始,Kafka可以对生产和消费请求进行限额配置。基于字节速率来限制,每个group中所有的客户端共享一个限额。
Why are quotas necessary?
Producer和Consumer可能生产或消费大量的数据而耗尽Broker的资源,导致网络饱和。进行限额可以避免这些问题,特别是在多租户的集群中,一小部分低质量的客户端会降低整个集群的体验。实际上,当运行Kafka作为服务时,这还可以对API的使用进行限制。
Client groups
Kafka客户端的身份代表了用于鉴权。 在无鉴权机制的集群中, 用户身份是由服务器使用可配置的PrincipalBuilder进行选择的, Client-id作为客户端逻辑分组, 是由客户端应用选择的一个有意义的名称. 标量(user, client-id)定义共享这个用户身份和客户端ID的逻辑客户端分组.
配额可以用于(user, client-id)组合, 或user, client-id分组。
对一个给定的连接, 最符合这个连接的配额被使用到, 一个限额组的所有连接共享这个限额配置, 例如: 如果(user=”test-user”, client-id=”test-client”) 10MB/s的配额, 这个配置会被所有的具有”test-user”用户和客户端ID是 “test-client”的所有生产者所共享.
Quota Configuration
配额可以按照(user, client-id)或者, user或client-id进行分组, 如果需要更高或更低的配额, 可以覆盖默配额, 这个机制类似于对日志主题配置的覆盖, user 或者 (user, client-id)配额可以覆盖写入到zookeeper下的 /config/users ,client-id配置, 可以写入到 /config/clients。这些覆盖写入会被服务器很快的读取到, 这让我们修改配置不需要重新启动服务器. 每个分组的默认配置也可以同样的方式动态修改。
限额的配置顺序如下: - /config/users//clients/
- /config/users//clients/
- /config/users/
- /config/users//clients/
- /config/users//clients/
- /config/users/
- /config/clients/
- /config/clients/
Broker的quota.producer.default,quota.consumer.default也可以用来配置默认的client-id分组的默认值。这可属性已经不鼓励使用,后续将会删除。默认client-id限额配置可以和其它默认配置一样, 在Zookeeper直接设置。
Enforcement
默认情况下,每个唯一的客户端group会收到一个集群配置的固定的限额。这个限额是基于每个Broker的。每个客户端能发布或获取在每台服务器都的最大速率, 我们按服务器定义配置, 而不是按整个集群定义,是因为如果是集群范围的需要额外的机制来共享配额的使用情况, 这会导致配额机制的实现比较难。
Broker检测到限额违规时时如何处理的?在我们的解决方案中,Broker不会返回错误给客户端,而是降低客户端的速率。Broker计算使客户端回到合理限额的需要的响应延迟。这种方法的处理对客户端是透明,使他们不必执行任何棘手的,特殊的操作。实际上,错误的客户端还可能加剧正在解决的限额问题。
客户端字节率在多个小窗口(例如每个1秒的30个窗口)上进行测量,以便快速检测和纠正配额违规。 通常,具有大的测量窗口(例如,每个30秒的10个窗口)导致大量的流量脉冲,随后是长时间的延迟,这在用户体验方面不是很好。
http://kafka.apache.org/quickstart官网
bin/kafka-topics.sh --list --zookeeper server1:2181 查看topic
bin/kafka-console-producer.sh --broker-list 192.168.1.201:6667 --topic test 生产者
bin/kafka-console-consumer.sh --zookeeper server1:2181 192.168.1.201:6667 --topic test --from-beginning 消费者