面试题
1. Kafka 如何保证消息的消费顺序?
我们在使用消息队列的过程中经常有业务场景需要严格保证消息的消费顺序,比如我们同时发了 2 个消息,这 2 个消息对应的操作分别对应的数据库操作是:
- 更改用户会员等级。
- 根据会员等级计算订单价格。
假如这两条消息的消费顺序不一样造成的最终结果就会截然不同。
我们知道 Kafka 中 Partition(分区)是真正保存消息的地方,我们发送的消息都被放在了这里。而我们的 Partition(分区) 又存在于 Topic(主题) 这个概念中,并且我们可以给特定 Topic 指定多个 Partition。
每次添加消息到 Partition(分区) 的时候都会采用尾加法,如上图所示。 Kafka 只能为我们保证 Partition(分区) 中的消息有序。
消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。Kafka 通过偏移量(offset)来保证消息在分区内的顺序性。
解决方案:发送消息的时候指定 key/Partition。
Kafka 中发送 1 条消息的时候,可以指定 topic, partition, key,data(数据) 4 个参数。如果你发送消息的时候指定了 Partition 的话,所有消息都会被发送到指定的 Partition。并且,同一个 key 的消息可以保证只发送到同一个 partition,这个我们可以采用表/对象的 id 来作为 key 。
2. Kafka 如何保证消息不丢失
2.1 生产者丢失消息的情况
2.1.0 生产者弄丢消息场景
acks=1
,只保证写入leader成功,如果在 follower同步成功之前 leader 故障,那么将会丢失数据;- 使用异步模式的时候,当缓冲区满了,如果配置为0(还没有收到确认的情况下,缓冲池一满,就清空缓冲池里的消息),数据就会被立即丢弃掉。
2.1.1 不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。
生产者(Producer) 调用send方法发送消息之后,消息可能因为网络问题并没有发送过去。
所以,我们不能默认在调用send方法发送消息之后消息发送成功了。为了确定消息是发送成功,我们要判断消息发送的结果。但是要注意的是 Kafka 生产者(Producer) 使用 send 方法发送消息实际上是异步的操作,我们可以通过 get()方法获取调用结果,但是这样也让它变为了同步操作(不推荐),可以采用为其添加回调函数的形式,示例代码如下:
producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), Integer.toString(i)), new Callback() {
//回调函数,该方法会在 Producer 收到 ack 时调用,为异步调用
@Override
public void onCompletion(RecordMetadata metadata,Exception exception) {
if (exception == null) {
System.out.println("success->" + metadata.offset());
} else {
exception.printStackTrace();
}
}
2.1.2 参数(acks ,retries,retry.backoff.ms)
- 设置 acks = all
acks
是 Producer 的一个参数,如果设置成 all,则表明所有副本都要接收到该消息之后该消息才算真正成功被发送。 - 重试次数
retries
同样是 Producer 的参数,当出现网络的瞬时抖动时,消息发送可能会失败,此时配置了 retries > 0 的 Producer 能够自动重试消息发送,避免消息丢失。
另外这里推荐为 Producer 的retries (重试次数)设置一个比较合理的值,一般是 3 ,但是为了保证消息不丢失的话一般会设置比较大一点。设置完成之后,当出现网络问题之后能够自动重试消息发送,避免消息丢失。另外,建议还要设置重试间隔,因为间隔太小的话重试的效果就不明显了,网络波动一次你3次一下子就重试完了。 retry.backoff.ms
参数表示消息生产超时失败后重试的间隔时间。
2.1.4
异步方式缓冲区满了,就阻塞在那,等着缓冲区可用,不能清空缓冲区(一旦清空,数据就丢失了)
2.2 Kafka 弄丢了消息
2.2.1 replication.factor 设置副本个数
设置 replication.factor
。这也是 Broker 端的参数。
其实这里想表述的是,最好将消息多保存几份,毕竟目前防止消息丢失的主要机制就是冗余。
2.2.2 min.insync.replicas ISR最少副本数量
设置 min.insync.replicas
> 1。这依然是 Broker 端参数,控制的是消息至少要被写入到多少个副本才算是“已提交”。(设置成大于 1 可以提升消息持久性。在实际环境中千万不要使用默认值 1。)
注意
确保 replication.factor > min.insync.replicas。
如果两者相等,那么只要有一个副本挂机,整个分区就无法正常工作了。我们不仅要改善消息的持久性,防止数据丢失,还要在不降低可用性的基础上完成。
推荐设置成 replication.factor = min.insync.replicas + 1。
2.2.3 unclean.leader.election.enable=false 是否能把非ISR集合中的副本选举为leader副本
2.3 消费者丢失消息的情况(手动Offset:enable.auto.commit
设置成 false)
当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset。自动提交的话会有一个问题,试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并没有被消费,但是 offset 却被自动提交了,发生了消息丢失。
解决办法也比较粗暴,我们手动关闭自动提交 offset(Consumer 端有个参数 enable.auto.commit
设置成 false。),每次在真正消费完消息之后再自己手动提交 offset 。
但是,细心的朋友一定会发现,这样会带来消息被重新消费的问题。比如你刚刚消费完消息之后,还没提交 offset,结果自己挂掉了,那么这个消息理论上就会被消费两次。
3. Kafka 如何保证消息不重复消费
3.1 kafka出现消息重复消费的原因
服务端侧已经消费的数据没有成功提交 offset(根本原因)。
Kafka 侧 由于服务端处理业务时间长或者网络链接等等原因让 Kafka 认为服务假死,触发了分区 rebalance。
3.2 消费者重复消费
- 业务id去重
- 消费消息服务做幂等校验,比如 Redis 的set、MySQL 的主键等天然的幂等功能。这种方法最有效。
- 将
enable.auto.commit
参数设置为 false,关闭自动提交,开发者在代码中手动提交 offset。
3.2 生产者重复消费
生产端重复发送:这个不重要,依赖消费端去重即可。
4. kafka和rabbitmq 的区别
- Broker与Consume交互方式不同
RabbitMQ 采用push的方式
kafka采用pull的方式
2.使用场景
rabbitMQ支持对消息的可靠的传递,支持事务,不支持批量的操作;基于存储的可靠性的要求存储可以采用内存或者硬盘。
kafka具有高的吞吐量,内部采用消息的批量处理,zero-copy机制,数据的存储和获取是本地磁盘顺序批量操作,具有O(1)的复杂度(与分区上的存储大小无关),消息处理的效率很高。(大数据)
来源:https://snailclimb.gitee.io/javaguide/#/docs/high-performance/message-queue/kafka-questions-01
防止消息丢失:https://blog.csdn.net/Shockang/article/details/118469174?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2-118469174-blog-79888144.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2-118469174-blog-79888144.pc_relevant_aa&utm_relevant_index=5
https://www.codetd.com/article/13755160