kafka容错和优化

1、 kafka如何保证数据不丢失

Kafka是一种高吞吐量的分布式发布订阅消息系统。在使用过程中如果使用不当,经常会出现消息丢失的情况,这是业务系统不能容忍的,消息系统最重要的是保证数据不丢失。本文主要记录kafka是如何保证数据不丢失的,主要从三方面来介绍,消息发送端保证数据不丢失,kafka服务保证消息不丢失,消费者保证消息不丢失。

1.1 基础知识

  1. kafka 可以保证分区消息的顺序,同一个分区,先发送到kafka分区的消息,会被先消费掉。
  2. kafka 是通过一个集群对外提供服务,只要是集群中多个副本中有一个副本是活跃的,那么收到的消息就不会丢失。

1.2 kafka集群保证数据不丢失

先思考一个问题:
1.2.1 kafka集群什么时候会丢失消息?

这就要从kafka的复制机制开始讲了。
kafka每个topic有多个分区,分区存储在磁盘上,kafka可以保证分区的数据是有序的,每个分区可以有多个副本。
副本按照是否是首领,可以分为首领副本和跟随者副本(这里对应的就是kafka集群中的leader和follower)。
所有的消息都是发送给leader的,消息消费也是从leader获取的。首领副本第一时间收到消息,或者消费消息,他一定是同步副本。
其他follower都是和leader保持通信,同步leader的消息。当leader不可用时,会选举一个follower会变成leader。

1.2.2 对于一个主一个从的两个kafka,做的集群来说。

(此时的kafka复制系数是2. 对应的配置参数是replication,factor)
一个是leader副本,一个是follower副本。当follower副本一直能与leader副本保持同步的时候 follower副本是 同步副本,
当follower与leader无法保持同步的时候 follower副本则变成非同步副本。

如果leader宕机,这时候系统需要选举一个follower来作为首领,kafka优先选择同步副本作为首领,当系统没有同步副本的时候。
kafka如果选择非同步副本作为首领,则会丢失一部分数据,(这一部分数据就是非同步副本无法及时从首领副本更新的消息)。
kafka如果不选择非同步副本作为首领,则此时kafka集群不可用。

kafka 选择非同步副本作为首领副本的行为叫做,不完全首领选举。如何控制kafka在leader宕机时,同步副本不可用时,是否选择非同步
作为首领?通过kafka的另外一个参数来控制的 : unclean.leader.election. 如果是true 则会发生不完全首领选举。

副本数建议3个就可以,多的话需要更多的磁盘,unclean.leader.election 建议false.

1.2.3 对于两个kafka做的集群来说,肯定是不安全的。那么三个节点的kafka安全吗?

答案是:也不一定安全
因为即使三个副本,也有可能是两个从都是非同步副本,此时主宕机,从要么不可用(影响高可用),要么成为主(数据丢失)。
这里就需要保证kafka系统中至少有两个同步副本。一个肯定是首领副本,另外一个是从的副本。
此时需要kafka的另外一个参数 最小同步副本数 min,insync.replicas
只有保证kafka收到生产者的消息之后,至少有 “最小同步副本数“ 的副本收到消息,才能保证在主宕机时消息不丢失。
这个参数的意思是 kafka收到生产者消息之后,至少几个同步副本,同步之后,才给客户端消息确认。
数量多能保证高可用,但是牺牲效率。

1.2.4 kafka 如何判断一个follower副本是不是同步副本?
满足两个个条件

  • 1.在过去10秒内从首领获取过消息,并且是最新消息。
  • 2.过去6秒内 和 zk直接发送过心跳。

疑问:如果kafka 长时间未收到消息,第一条如何满足?

1.3 消息发送者正确的发送姿势

1.3.1 消息怎么才算是发生成功?

消息的生产者向kafka集群发送消息,需要等待kafka集群确认,这里涉及到一个参数 acks
他的值有三个 0, 1, all
如果是0 ,那么代表发送过去,不等待kafka消息确认,认为成功
一定会丢失消息,可能kafka集群正在选举,此时就无法收到任何异常。
如果是1,那么代表发送过去,等待首领副本确认消息,认为成功
首领肯定收到了消息,写入了分区文件(不一定落盘)。
如果是all, 那么代表发送过去之后,消息被写入所有同步副本之后 ,认为成功。
注意这里是 所有同步副本,不是所有副本。 具体是多少同步副本,还要取决于kafka集群设置的最小同步副本数,和集群当前的同步副本数。

选择这种配置,会可靠,但是牺牲效率,可以通过,增大批和使用异步模式,提高效率。

1.3.2 如果发生消息发生异常怎么办?重试吗?

1.3.2.1 哪些异常需要重试?
网络异常和集群无主,或者正在选举的异常是可以重试的。
1.3.2.2 哪些不需要重试?
配置异常。
1.3.2.3 其他异常怎么办?

序列化异常,内存溢出,栈溢出等。

1.3.3 重要的配置参数
如果网络异常收不到响应,则等待,这里有个配置等待时间 request,timeout.ms 发送消息等待时间。

metadata.fetch.time.out 从kafka 获取元数据的等待时间。
max.block.ms : 配置控制了KafkaProducer.send()并将KafkaProducer.partitionsFor()被阻塞多长时间。由于缓冲区已满或元数据不可用,这些方法可能会被阻塞。用户提供的序列化程序或分区程序中的阻塞将不计入此超时
重试次数 retries,重试直接的等待时间, 默认是100 ms ,可以通过 retry.backoff.ms 配置
多个消息发送给同一个分区的时候,生产者会把消息打成一个批,批大小设置 batch.size 过大占内存,过小发送频繁,并且生产者不是必须满批发送,有个等待时间,linger.ms设置等待多久批不满则发送。

1.4 消息消费者正确的消费姿势

消费者需要向kafka集群提交 已经消费的消息的offset来确定消息消费到了那里。

消息队列的消费方式有两种,一种是发布订阅模式,一种是队列模式。

发布订阅模式 一个消息可以被多个消费者消费。队列模式多个消费者只能消费到一部分消息。

kafka是通过group-id来区分消费组的。
一个topic被 同一个消费组的不同消费者消费 ,相当于是队列模式。被不同消费组消费相当于是 订阅模式。
一个partition在同一个时刻只有一个consumer instance在消费。

对于正确的模式,我们需要配置正确的group-id

auto.offset.reset
没有偏移量可以提交的时候,系统从哪里开始消费。

有两种设置 :earliest 和latest 。

enable.auto.commit
自动提交 ,如果开启了自动提交,那么系统会自动进行提交offset。可能会引起,并未消费掉,就提交了offset.引起数据的丢失。
与自动提交相关的是自动提交的间隔时间 auto.commit.interval.ms 默认是5秒钟提交一次,可以通过查看 kafka config目录下的
配置文件,查询配置的默认值。
自动提交 还可能引起消息的重复消费,特别是 多个客户端直接出现重平衡时。

1.5 总结

只有三方面都保证了。才可以保证数据不丢失。

1.5.1 数据丢失:

acks=1的时候(只保证写入leader成功),如果刚好leader挂了。数据会丢失。

acks=0的时候,使用异步模式的时候,该模式下kafka无法保证消息,有可能会丢。

1.5.2 brocker如何保证不丢失:

acks=all : 所有副本都写入成功并确认。

retries = 一个合理值。

min.insync.replicas=2 消息至少要被写入到这么多副本才算成功。

unclean.leader.election.enable=false 关闭unclean leader选举,即不允许非ISR中的副本被选举为leader,以避免数据丢失。

1.5.3 Consumer如何保证不丢失

如果在消息处理完成前就提交了offset,那么就有可能造成数据的丢失。

enable.auto.commit=false 关闭自动提交offset

处理完数据之后手动提交。

2、Kafka性能优化(面试题)

3.1 消费者参数优化

在生产环境中,往往需要根据实际的数据量和大小来决定更好的参数配置。下剖析几个常用的参数。

  1. enable.auto.commit

指定了消费者是否自动提交偏移量,默认值是 true,为了尽量避免重复数据和数据丢失,可以把它设置为 false,有自己控制合适提交偏移量,如果设置为true,可以通过设置 auto.commit.interval.ms 属性来控制提交的频率。

  1. auto.offset.reset

该参数指定了消费者在读取一个没有 Offset 或者 Offset 无效(消费者长时间失效,当前的 Offset 已经过时并且被删除了)的分区的情况下,应该作何处理, 默认值是 latest,也就是从最新记录读取数据(消费者启动之后生成的记录),另一个值是 earliest,意思是在偏移量无效的情况下,消费者从起始位置开始读取数据。

  1. session.timeout.ms

该参数指定了当消费者被认为已经挂掉之前可以与服务器断开连接的时间。默认是 3s,消费者在 3s 之内没有再次向服务器发送心跳,那么将会被认为已经死亡。此时,协调器将会触发再均衡,把它的分区分配给其他的消费者,该参数与 heartbeat.interval.ms 紧密相关,该参数定义了消费者发送心跳的时间间隔,也就是心跳频率,一般要同时修改这两个参数,heartbeat.interval.ms 参数值必须要小于 session.timeout.ms , 一般是 session.timeout.ms 的三分之一, 比如, session.timeout.ms 设置成 3min,那么 heartbeat.interval.ms 一般设置成 1min,这样,可以更快的检测以及恢复崩溃的节点,不过长时间的轮询或垃圾收集可能导致非预期的再均衡(有一种情况就是网络延迟,本身消费者是没有挂掉的,但是

网络延迟造成了心跳超时,这样本不该发生再均衡,但是因为网络原因造成了非预期的再均衡),把该参数的值设置得大一些,可以减少意外的再均衡,不过检测节点崩溃需要更长的时间。

  1. max.partition.fetch.bytes

该参数指定了服务器从每个分区里返回给消费者的最大字节数。它的默认值是 1MB,也就是说,KafkaConsumer.poll()方法从每个分区里返回的记录最多不超 max.partitions.fetch.bytes 指定的字节。如果一个主题有 20 个分区和 5 个消费者,那么每个消费者需要至少 4MB 的可用内存来接收记录。在为消费者分配内存时,可以给它们多分配一些,因为如果群组里有消费者发生崩溃,剩下的消费者需要处理更多的分区。max.partition.fetch.bytes 的值必须比 Broker 能够接收的最大消息的字节数(通过 max.message.size 属性配置)大, 否则消费者可能无法读取这些消息,导致消费者一直挂起重试,例如,max.message.size 设置为 2MB,而该属性设置为1MB,那么当一个生产者可能就会生产一条大小为2MB 的消息,那么就会出现问题,消费者能从分区取回的最大消息大小就只有 1MB,但是数据量是 2MB,所以就会导致消费者一直挂起重试。

在设置该属性时,另一个需要考虑的因素是消费者处理数据的时间。消费者需要频繁调用 poll()方法来避免会话过期和发生分区再均衡,如果单次调用 poll() 返回的数据太多,消费者需要更多的时间来处理,可能无怯及时进行下一个轮询来避免会话过期。如果出现这种情况,可以把 max.partitioin.fetch.bytes 值改小, 或者延长会话过期时间。

  1. fetch.min.bytes

消费者从服务器获取记录的最小字节数,Broker 收到消费者拉取数据的请求的时候,如果可用数据量小于设置的值,那么 Broker 将会等待有足够可用的数据的时候才返回给消费者,这样可以降低消费者和 Broker 的工作负载。因为当主题不是很活跃的情况下,就不需要来来回回的处理消息,如果没有很多可用数据,但消费者的 CPU 使用率却很高,那么就需要把该属性的值设得比默认值大。

如果消费者的数量比较多,把该属性的值设置得大一点可以降低 Broker 的工作负载。

  1. fetch.max.wait.ms

fetch.min.bytes 设 置 了 Broker 返 回 给 消 费 者 最 小 的 数 据 量 , 而fetch.max.wait.ms 设置的则是Broker 的等待时间,两个属性只要满足了任何一条, Broker 都会将数据返回给消费者,也就是说举个例子,fetch.min.bytes 设置成 1MB, fetch.max.wait.ms 设置成 1000ms,那么如果在 1000ms 时间内,如果数据量达到了 1MB,Broker 将会把数据返回给消费者;如果已经过了 1000ms,但是数据量还没有达到 1MB,那么 Broker 仍然会把当前积累的所有数据返回给消费者。

7.receive.buffer.bytes + send.buffer.bytes

socket 在读写数据时用到的 TCP 缓冲区也可以设置大小。如果它们被设为

-1 ,就使用操作系统的默认值。如果生产者或消费者与 broker 处于不同的数据中心内,可以适当增大这些值,因为跨数据中心的网络一般都有比较高的延迟和比较低的带宽。

8.client.id

Consumer 进程的标识。如果设置成人为可读的值,跟踪问题会比较方便。

3.2 生产者参数优化

  1. bootstrap.servers

无需添加所有的集群地址,Kafka 会根据提供的地址发现其他的地址,但尽量多提供几个,以防提供的服务器关闭。

  1. acks

acks=0 如果设置为 0,那么生产者将不等待任何消息确认。消息将立刻添加到 socket 缓冲区并考虑发送。在这种情况下不能保障消息被服务器接收到。并且重试机制不会生效(因为客户端不知道故障了没有)。每个消息返回的 offset 始

终设置为-1。

acks=1,这意味着 leader 写入消息到本地日志就立即响应,而不等待所有follower 应答。在这种情况下,如果响应消息之后但 follower 还未复制之前 leader 立即故障,那么消息将会丢失。

acks=all 这意味着 leader 将等待所有副本同步后应答消息。此配置保障消息不会丢失(只要至少有一个同步的副本)。这是最强壮的可用性保障。等价于acks=-1。

  1. buffer.memory

生产者用来缓存等待发送到服务器的消息的内存总字节数。如果消息发送比可传递到服务器的快,生产者将阻塞 max.block.ms 之后,抛出异常。此设置应该大致对应生产者将要使用的总内存,但不是硬约束,因为生产者所使用的所有内存都用于缓冲。一些额外的内存将用于压缩(如果启动压缩),以及用于保持发送中的请求。

首先要明确一点,那就是在内存缓冲里大量的消息会缓冲在里面,形成一个一个的 Batch,每个 Batch 里包含多条消息。然后 KafkaProducer 有一个 Sender 线程会把多个Batch 打包成一个 Request 发送到 Kafka 服务器上去。那么如果要是内存设置的太小,可能导致一个问题:消息快速的写入内存缓冲里面,但是Sender 线程来不及把 Request 发送到 Kafka 服务器。这样是不是会造成内存缓冲 很快就被写满?一旦被写满,就会阻塞用户线程,不让继续往 Kafka 写消息了。所以对于“buffer.memory”这个参数应该结合自己的实际情况来进行压测,你需 要测算一下在生产环境,你的用户线程会以每秒多少消息的频率来写入内存缓冲。比如说每秒 300 条消息,那么你就需要压测一下,假设内存缓冲就 32MB,每秒 写 300 条消息到内存缓冲,是否会经常把内存缓冲写满?经过这样的压测,你可 以调试出来一个合理的内存大小。

  1. batch.size

当多个消息要发送到相同分区的时,生产者尝试将消息批量打包在一起,以减少请求交互。这样有助于客户端和服务端的性能提升。该配置的默认批次大小(以字节为单位):16384,即 16KB。不会打包大于此配置大小的消息,发送到 broker 的请求将包含多个批次,每个分区一个,用于发送数据。较小的批次大小有可能降低吞吐量(批次大小为 0 则完全禁用批处理)。一个非常大的批次大小可能更浪费内存,因为我们会预先分配这个资源。比如说发送消息的频率就是每秒 300 条,那么如果 batch.size 调节到了 32KB,或者 64KB,也不会提升发送消息的整体吞吐量。

理论上来说,提升 batch 的大小,可以允许更多的数据缓冲在里面,那么一次 Request 发送出去的数据量就更多了,这样吞吐量可能会有所提升。但是也不能无限的大,过于大了之后,要是数据老是缓冲在 Batch 里迟迟不发送出去,那么岂不是你发送消息的延迟就会很高。比如说,一条消息进入了 Batch,但是要等待 5 秒钟 Batch 才凑满了 64KB,才能发送出去。那这条消息的延迟就是 5 秒钟。

所以需要在这里按照生产环境的发消息的速率,调节不同的 Batch 大小自己测试一下最终出去的吞吐量以及消息的延迟,设置一个最合理的参数。

  1. compression.type

数据压缩的类型。默认为空(就是不压缩)。有效的值有 none,gzip,snappy, 或 lz4。压缩全部的数据批,因此批的效果也将影响压缩的比率(更多的批次意味着更好的压缩)。

  1. retries

设置一个比零大的值,客户端如果发送失败则会重新发送。注意,这个重试功 能 和 客 户 端 在 接 到 错 误 之 后 重 新 发 送 没 什 么 不 同 。 如 果max.in.flight.requests.per.connection 没有设置为 1,有可能改变消息发送的顺序, 因为如果 2 个批次发送到一个分区中,并第一个失败了并重试,但是第二个成功了,那么第二个批次将超过第一个。

“retries”和“retries.backoff.ms”决定了重试机制,也就是如果一个请求失败了可以重试几次,每次重试的间隔是多少毫秒。这个大家适当设置几次重试的机会,给一定的重试间隔即可,比如给 100ms 的重试间隔。

  1. linger.ms

指逗留时间,这个逗留指的是消息不立即发送,而是逗留这个时间后一块发送。这个设置是比较有用的,有时候消息产生的要比能够发送的要快,这个参数完美的实现了一个人工的延迟,使得大批量可以聚合到一个 Batch 里一块发送, 当 Batch 慢了的话,会忽略这个参数立即发送。默认值 : 0。

  1. client.id

当发出请求时传递给服务器的 id 字符串。这样做的目的是允许服务器请求记录这个“逻辑应用名”,这样能够追踪请求的源,而不仅仅只是 ip:port。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值