Kafka 的消息异常情况~追日

Kafka 的消息异常情况

1.消息丢失情况

消息发送端 Producer

(1) acks=0: 表示 Producer 不需要等待任何 broker 确认收到消息的回复, 就可以继续发送下一条消息. 性能最高, 但是最容易丢消息. 对性能要求很高但对数据丢失不敏感的情况可以用这种模式.

(2) acks=1: 至少要等待 Leader 已经成功将数据写入本地 log, 但是不需要等待所有 Follower是否成功写入. 就可以继续发送下一条消息. 这种情况下, 如果 Follower 没有成功备份数据同时 Leader挂掉, 则会导致消息丢失.

(3)acks=-1或all: Leader 需要等待所有备份 (min.insync.replicas 配置备份个数参数设置)都成功写入日志, 这种策略会保证只要有一个备份存活就不会丢失数据. 如果 min.insync.replicas配置的是1则也可能丢消息, 类似于 acks=1情况.

消息消费端 Consumer

如果消费这边配置的是自动提交, 万一消费到数据还没处理完, 就自动提交 offset 了, 但是此时 Consumer 直接宕机了, 未处理完的数据丢失, 下次也消费不到.

2.消息重复消费

消息发送端 Producer

发送消息如果配置了重试机制, 比如网络抖动时间过长导致发送端发送超时, 实际broker可能已经接收到消息, 但发送方会重新发送消息

消息消费端 Consumer

如果消费这边配置的是自动提交, 刚拉取了一批数据处理了一部分, 但还没来得及提交, 服务挂掉, 下次重启又会拉取相同的一批数据重复处理. 此时, 一般消费端都是要做消费幂等处理的.

3.消息乱序

如果发送端 Producer 配置了重试机制, Kafka不会等之前那条消息完全发送成功才去发送下一条消息,这样可能会出现:

发送了[1,2,3]条消息, 第一条超时, 后面两条发送成功, 再重试发送第1条消息, 这时消息在broker端的顺序就是[2,3,1]

所以, 是否一定要配置重试要根据实际需求情况而定. 也可以用同步发送的模式去发消息, 当然acks不能设置为0, 这样也能保证消息发送的有序.

Kafka保证全链路消息顺序消费, 需要从发送端开始, 将所有有序消息发送到同一个分区 Partition, 然后用一个 Consumer 去消费, 但是这种形式性能比较低, 可以在 Consumer 端接收到消息后将需要保证顺序消费的几条消费发到内存队列, 一个内存队列开启一个线程顺序处理消息.

4.消息积压

(1) 线上可能因为发送方 Producer 发送消息速度过快, 或者 Consumer 方处理消息过慢, 可能会导致broker积压大量未消费消息.

若积压上百万未消费消息需要紧急处理, 可以修改消费端程序, 让其将收到的消息快速转发到其他 topic(同时可设置很多分区), 然后再启动多个消费者同时消费新主题的不同分区。

(2)由于消息数据格式变动或 Consumer 端程序有bug, 导致消费者一直消费不成功, 也可能导致broker积压大量未消费消息.

此种情况可以将这些消费不成功的消息转发到其它队列里去(类似死信队列).

5.延时队列

延时队列存储的对象是延时消息(指消息被发送以后, 并不想立即让消费者获取, 而是等待特定时间后, 消费者才能获取这个消息进行消费), 延时队列的使用场景有许多, 比如:

(1) 在订单系统中, 一个用户下单之后通常有 15 - 30 分钟的时间进行支付, 如果 30 分钟之内没有支付成功, 那么这个订单将进行异常处理, 此时就可以采用延时队列来进行处理这些订单.

(2) 订单完成1小时后通知用户进行评价。

实现思路:

​ 发送延时消息时先把消息按照不同的延迟时间段发送到指定的队列中( topic_1s,topic_5s,topic_10s,…topic_2h,这个一般不能支持任意时间段的延时), 然后通过定时器进行轮训消费这些topic, 查看消息是否到期, 若到期就把这个消息发送到具体业务处理的topic中, 队列中消息越靠前的到期时间越早, 具体来说就是定时器在一次消费过程中, 对消息的发送时间做判断, 看下是否延迟到对应时间, 如果到了就转发, 如果还没到这一次定时任务就可以提前结束了.

6.消息回溯

若某段时间对已消费消息计算的结果觉得有问题, 可能是由于程序逻辑设计导致的计算错误, 当程序 bug 修复后, 这时可能需要对之前已消费的消息重新消费, 可以指定从多久之前的消息回溯消费. 这时候可以用 Consumer 的 offsetsForTimes, seek等方法指定从某个 offset 偏移的消息开始消费.

7.分区数越多吞吐量越高?

分区数到达某个值吞吐量反而会开始下降, 实际上很多事情都会有一个临界值, 当超过这个临界值之后很多原本符合既定逻辑规律的走向又会变得不同(一般情况分区数跟集群机器数量相当就差不多).

此外吞吐量的数值和趋势还和磁盘, 文件系统, I/O调度策略等因素相关.

Note: 如果分区数设置过大, 比如设置10000, 可能会设置不成功, 后台会报错"java.io.IOException : Too many open files"

异常中最关键的信息是 "Too many open flies", 一种常见的 Linux 系统错误, 通常意味着文件描述符不足, 一般发生在创建线程, 创建 Socket, open打开文件这些场景下 . 在 Linux系统的默认设置下, 这个文件描述符的个数不是很多, 通过 ulimit -n 命令可以查看: 一般默认是1024, 可以将该值增大, 比如: ulimit -n 65535

8.消息传递保障

  • at most once(消费者最多收到一次消息, {0, 1}次): acks = 0 可以实现.
  • at least once(消费者至少收到一次消息, {1, n}次):ack = all 可以实现.
  • exactly once(消费者刚好收到一次消息), at least once 加上消费者幂等性可以实现, 还可以用Kafka生产者的幂等性来实现.

Kafka生产者的幂等性: 因为发送端重试导致的消息重复发送问题, Kafka的幂等性可以保证重复发送的消息只接收一次, 只需在生产者加上参数 props.put(“enable.idempotence”, true) , 默认是false不开启.

具体实现原理: Kafka 每次发送消息会生成 PID 和 Sequence Number, 并将这两个属性一起发送给 broker, broker会将 PID 和 Sequence Number 跟消息绑定一起存起来, 下次如果生产者重发相同消息, broker会检查PID和Sequence Number, 如果相同不会再接收.

PID: 每个新的 Producer 在初始化的时候会被分配一个唯一的 PID, 这个PID 对用户完全是透明的. 生产者如果重启则会生成新的PID.
Sequence Number:, 对于每个 PID, 该 Producer 发送到每个 Partition 的数据都有对应的序列号, 这些序列号是从0开始单调递增的.

9.kafka的事务

Kafka的事务不同于Rocketmq, Rocketmq 是保障本地事务(比如数据库)与 mq 消息发送的事务一致性, 而 Kafka 的事务主要是保障一次发送多条消息的事务一致性(要么同时成功要么同时失败), 一般在Kafka的流式计算场景用得多一点, 比如 Kafka 需要对一个topic里的消息做不同的流式计算处理, 处理完分别发到不同的topic里, 这些topic分别被不同的下游服务系统消费(比如hbase, redis, es等), 这种我们肯定希望系统发送到多个topic的数据保持事务一致性, Kafka 若要实现类似Rocketmq的分布式事务需要额外开发功能. Kafka的事务处理特性可以参考官方文档

Reference

  • Kafka API
  • Kafka Design

你可能感兴趣的:(kafka,中间件)