Kafka研究系列之架构设计

kafka设计

多个生产者- 主题 - 分区 - segment(index文件和log文件)

index 文件: key: 消息号 value: 消息在磁盘的地址

首先根据offset 二分找到索引文件, 然后根据偏移量找到消息的物理地址

Kafka使用ISR的方式则很好的均衡了确保数据不丢失以及吞吐率

如果要提高数据的可靠性,在设置request.required.acks=-1的同时,也要min.insync.replicas这个参数(可以在broker或者topic层面进行设置)的配合,这样才能发挥最大的功效

设置幂等

enable.idempotence,需要设置为ture,此时就会默认把acks设置为-1, 同时设置重试次数

kafka架构图

消费者类图

生产者代码流程(追加器+发送器)

1. 根据key计算分区

2. 对key和val 进行序列化

3. 根据主题分区获得对应的双端对列,获得最后一个元素, 使用累加器追加到内存的bytebuffer中

4. 如果满了则调用sender进行批量发送给broker

消费者代码流程(Fetcher)

1. 根据分区查询主节点

2. 根据主题分区查询offset

3. 使用fetcher 从并发对列里获得数据

4. 获得成功发起下一次请求,将数据缓存在并发对列中

  

Rebalance(https://www.cnblogs.com/piperck/p/11201896.html)

主要就是维护分区和消费者之间的关系

过程

1. joinGroup

所有成员向协调者发送加入组的请求, 协调者将第一个请求的消费者作为leader,leader制定分配策略

2. syncGroup

leader向协调者发送分配结果, 所有成员向协调者发送syncGroup请求, 然后协调者向成员发送分配结果

分配策略

Range 范围分区(默认的)

假如有10个分区,3个消费者,把分区按照序号排列0,1,2,3,4,5,6,7,8,9;消费者为C1,C2,C3,那么用分区数除以消费者数来决定每个Consumer消费几个Partition,除不尽的前面几个消费者将会多消费一个 
最后分配结果如下

C1:0,1,2,3 
C2:4,5,6 
C3:7,8,9

RoundRobin 轮询分区

把所有的partition和consumer列出来,然后轮询consumer和partition,尽可能的让把partition均匀的分配给consumer

假如有3个Topic T0(三个分区P0-0,P0-1,P0-2),T1(两个分区P1-0,P1-1),T2(四个分区P2-0,P2-1,P2-2,P2-3)

有三个消费者:C0(订阅了T0,T1),C1(订阅了T1,T2),C2(订阅了T0,T2)


分区将会按照一定的顺序排列起来,消费者将会组成一个环状的结构,然后开始轮询。 
P0-0分配给C0 
P0-1分配给C1但是C1并没订阅T0,于是跳过C1把P0-1分配给C2, 
P0-2分配给C0 
P1-0分配给C1, 
P1-1分配给C0, 
P2-0分配给C1, 
P2-1分配给C2, 
P2-2分配给C1, 
p2-3分配给C2

C0: P0-0,P0-2,P1-1 
C1:P1-0,P2-0,P2-2 
C2:P0-1,P2-1,P2-3

如何避免不必要的rebalance?

第一类非必要 Rebalance 是因为未能及时发送心跳,导致 Consumer 被 “踢出”Group 而引发的。这种情况下我们可以设置 session.timeout.ms 和 heartbeat.interval.ms 的值,来尽量避免rebalance的出现。(以下的配置是在网上找到的最佳实践,暂时还没测试过

  • 设置 session.timeout.ms = 6s。
  • 设置 heartbeat.interval.ms = 2s。
  • 要保证 Consumer 实例在被判定为 “dead” 之前,能够发送至少 3 轮的心跳请求,即 session.timeout.ms >= 3 * heartbeat.interval.ms。

将 session.timeout.ms 设置成 6s 主要是为了让 Coordinator 能够更快地定位已经挂掉的 Consumer,早日把它们踢出 Group。

第二类非必要 Rebalance 是 Consumer 消费时间过长导致的。此时,max.poll.interval.ms 参数值的设置显得尤为关键。如果要避免非预期的 Rebalance,你最好将该参数值设置得大一点,比你的下游最大处理时间稍长一点。

kafka的特点

  • 一个主题对应多个业务方,每个业务方有自己的消费组, 不同消费组之间互不影响
    • 实现: 一个主题对应多个分区, 一个分区对应一个消费者, 不同的分区可以同时消费,生产者同理
  • 消息可以被重复消费
    • 实现: 消息有唯一标识offset 消费者消费消息时,只是offset的偏移, 不会删除消息本身
  • 扩展性特别好
    • 实现:一个主题对应多个分区, 一个分区对应一个消费者, 分区位于不同的机器上,而且有多个副本
    • 增加分区可以快速消费某个主题
  • 高性能
    • 写处理
      • 磁盘顺序写
      • 基于MMAP技术,即内存映射文件,将消息先写入到操作系统的页缓存中,由页缓存直接映射到磁盘文件,不需要在用户空间和内核空间直接拷贝消息
      • 将多个消息打包发送 减少IO
    • 读处理
      • 先根据消费端的offset二分查找对应的segment文件 然后找到对应的记录
  • 分区内有序
    • 每个分区内部的消息按照写入的顺序进行消费
  • 消息有且仅有一次
    • 生产端根据生产者id和消息id进行去重
    • 消费端根据业务id做幂等
  • 快速的leader选举
    • 从ISR中顺序选择一台机器

kafka解决传统消息系统哪些痛点?

传统消息系统2大模式:

  1. 点对点(队列)
    1. 优点: 可扩展 消费者轮询消费 2. 缺点: 消息一旦消费就丢弃
  2. 广播
    1. 优点: 可以广播到多个进程 2. 缺点: 不可扩展

kafka解决:

  1. 点对点: 一个消费者对应一个主题的某个分区 消息消费后不会删除, 而是可配置的有效期删除
  2. 广播: 消息被广播到多个消费者组中, 每个消费组内部可扩展

传统消息系统多消费者消费无法保证顺序?

解决: kafka 一个分区内部可以顺序消费.

kafka持久化到磁盘, 如何实现高性能读写? 

在索引文件中存储 offset 和 segment的对应关系

读: 在消费者消费消息时,broker根据消费者给定的offset,基于二分查找先在索引文件找到该offset对应的数据segment文件的位置,然后基于该位置(或往下)找到对应的数据 

在这里插入图片描述

写: 

  • 需要根据消息的主题和分区信息,将该消息写入到该分区当前最后的segment文件中,文件的写入方式是追加写
  • 由于是对segment文件追加写,故实现了对磁盘文件的顺序写,避免磁盘随机写时的磁盘寻道的开销,同时由于是追加写,故写入速度与磁盘文件大小无关
  • 虽然消息写入是磁盘顺序写入,没有磁盘寻道的开销,但是如果针对每条消息都执行一次磁盘写入,则也会造成大量的磁盘IO,影响性能。
    所以在消息写入方面,broker基于MMAP技术,即内存映射文件,将消息先写入到操作系统的页缓存中,由页缓存直接映射到磁盘文件,不需要在用户空间和内核空间直接拷贝消息,所以也可以认为消息传输是发送在内存中的。
     

kafka性能优化方案?

  1. 非常快的(缓存)技术pageCache 
  2. 索引文件二分查找读取数据.
  3. 将多个消息打包成一组, 减少小型I/O读写
  4. producer ,broker 和 consumer 都共享的标准化的二进制消息格式 减少数据格式转换拷贝
  5. 使用 sendfile 方法,可以允许操作系统将数据从 pagecache 直接发送到网络,这样避免重新复制数据
  6. Kafka 以高效的批处理格式支持一批消息可以压缩在一起发送到服务器, 减少网络带宽
  7. 批处理提交日志, 减少IO操作

kafka生产与消费过程

生产: 生产者根据消息id进行轮训或者hash取模计算分区

消费:

  1. 消费者首先从broker获得offset
  2. 然后根据offset二分查找索引文件,找到对应的记录
  3. 消费记录
  4. 更新offset

kafka消费模式

 pull-based 系统的不足之处在于:如果 broker 中没有数据,consumer 可能会在一个紧密的循环中结束轮询,实际上 busy-waiting 直到数据到来。为了避免 busy-waiting,我们在 pull 请求中加入参数,使得 consumer 在一个“long pull”中阻塞等待,直到数据到来(还可以选择等待给定字节长度的数据来确保传输长度)

如何让broker和被消费的消息保持一致性?

传统: 

即当消息被发送出去的时候,消息仅被标记为sent 而不是 consumed;然后 broker 会等待一个来自 consumer 的特定确认,再将消息标记为consumed。这个策略修复了消息丢失的问题,但也产生了新问题。 首先,如果 consumer 处理了消息但在发送确认之前出错了,那么该消息就会被消费两次,第二个是关于性能的,现在 broker 必须为每条消息保存多个状态(首先对其加锁,确保该消息只被发送一次,然后将其永久的标记为 consumed,以便将其移除

kafka:

Kafka 使用完全不同的方式解决消息丢失问题。Kafka的 topic 被分割成了一组完全有序的 partition,其中每一个 partition 在任意给定的时间内只能被每个订阅了这个 topic 的 consumer 组中的一个 consumer 消费。这意味着 partition 中 每一个 consumer 的位置仅仅是一个数字,即下一条要消费的消息的offset。这使得被消费的消息的状态信息相当少,每个 partition 只需要一个数字。这个状态信息还可以作为周期性的 checkpoint。这以非常低的代价实现了和消息确认机制等同的效果

kafka如何实现消息只被发送1次?

发送端幂等性: productor ID + 消息Id 在broker中保证幂等

消费端幂等性: 业务自身的id 

kafka的leader选举

从ISR中顺序选取

Kafka 采取了一种稍微不同的方法来选择它的投票集。 Kafka 不是用大多数投票选择 leader 。Kafka 动态维护了一个同步状态的备份的集合 (a set of in-sync replicas), 简称 ISR ,在这个集合中的节点都是和 leader 保持高度一致的,只有这个集合的成员才 有资格被选举为 leader,一条消息必须被这个集合 所有 节点读取并追加到日志中了,这条消息才能视为提交。这个 ISR 集合发生变化会在 ZooKeeper 持久化,正因为如此,这个集合中的任何一个节点都有资格被选为 leader 。这对于 Kafka 使用模型中, 有很多分区和并确保主从关系是很重要的。因为 ISR 模型和 f+1 副本,一个 Kafka topic 冗余 f 个节点故障而不会丢失任何已经提交的消息。

大多数投票的缺点是,多数的节点挂掉让你不能选择 leader。要冗余单点故障需要三份数据,并且要冗余两个故障需要五份的数据。根据我们的经验,在一个系统中,仅仅靠冗余来避免单点故障是不够的,但是每写5次,对磁盘空间需求是5倍, 吞吐量下降到 1/5,这对于处理海量数据问题是不切实际的。这可能是为什么 quorum 算法更常用于共享集群配置(如 ZooKeeper ), 而不适用于原始数据存储的原因

kafka资源配额

producers 和 consumers 可能会生产或者消费大量的数据或者产生大量的请求,导致对 broker 资源的垄断,引起网络的饱和,对其他clients和brokers本身造成DOS攻击。 资源的配额保护可以有效防止这些问题,在大型多租户集群中,因为一小部分表现不佳的客户端降低了良好的用户体验,这种情况下非常需要资源的配额保护。 实际情况中,当把Kafka当做一种服务提供的时候,可以根据客户端和服务端的契约对 API 调用做限制。

资源配额可以针对 (user,client-id),users 或者client-id groups 三种规则进行配置

消息结构

Field

Description

Offset

This is the offset used in kafka as the log sequence number. When the producer is sending non compressed messages, it can set the offsets to anything. When the producer is sending compressed messages, to avoid server side recompression, each compressed message should have offset starting from 0 and increasing by one for each inner message in the compressed message. (see more details about compressed messages in Kafka below)

Crc

The CRC is the CRC32 of the remainder of the message bytes. This is used to check the integrity of the message on the broker and consumer.

MagicByte

This is a version id used to allow backwards compatible evolution of the message binary format. The current value is 1.

Attributes

This byte holds metadata attributes about the message.

The lowest 3 bits contain the compression codec used for the message.

The fourth lowest bit represents the timestamp type. 0 stands for CreateTime and 1 stands for LogAppendTime. The producer should always set this bit to 0. (since 0.10.0)

All other bits should be set to 0.

TimestampThis is the timestamp of the message. The timestamp type is indicated in the attributes. Unit is milliseconds since beginning of the epoch (midnight Jan 1, 1970 (UTC)).

Key

The key is an optional message key that was used for partition assignment. The key can be null.

Value

The value is the actual message contents as an opaque byte array. Kafka supports recursive messages in which case this may itself contain a message set. The message can be null.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值