一. 问题场景以及解决的问题
哪些异常可以重试
如何实现重试
场景:发送消息到Broker时抛出异常,并且是允许重试的异常——>就会进行最大重试retries参数 指定的次数
二. 机制解读
1. 允许重试的必要条件
a) 重试次数少于参数retries指定的值
b) 异常是RetriableException类型 或者 TranscationManager允许重试
2. RetriableException—通过重试 使用较短时间可以成功的异常
3. TransactionManager允许重试——异常不满足类型,但此处允许,也会进行重试
|——此参数是KafkaProducer中构造Sender传入的
|——transactionManager.canRetray(response,batch)
private static TransactionManager configureTransactionState(ProducerConfig config, LogContext logContext, Logger log) {
TransactionManager transactionManager = null;
boolean userConfiguredIdempotence = false;
// 用户设置的Properties参数中是否有'enable.idempotence',如果有的话, 就用用户配置的
if (config.originals().containsKey(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG)) {
userConfiguredIdempotence = true;
}
// 用户设置的Properties参数中是否有'transactional.id',如果有的话, 就用用户配置的
boolean userConfiguredTransactions = false;
if (config.originals().containsKey(ProducerConfig.TRANSACTIONAL_ID_CONFIG)) {
userConfiguredTransactions = true;
}
// 得到参数'enable.idempotence'的值
boolean idempotenceEnabled = config.getBoolean(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG);
// 如果用户显示配置enable.idempotence为false,并且又配置了transactional.id,就会抛出这个异常
if (!idempotenceEnabled && userConfiguredIdempotence && userConfiguredTransactions) {
throw new ConfigException("Cannot set a " + ProducerConfig.TRANSACTIONAL_ID_CONFIG + " without also enabling idempotence.");
}
// 如果用户配置了transactional.id,那么idempotenceEnabled就认为是true(与)
if (userConfiguredTransactions) {
idempotenceEnabled = true;
}
// 只有用户配置了transactional.id,且enable.idempotence没有设置为false,这里才为true,就会构造一个有效的TransactionManager;从这里可知,如果用户没有配置transactional.id,那么TransactionManager为null
if (idempotenceEnabled) {
// 构造TransactionManager的几个重要参数
String transactionalId = config.getString(ProducerConfig.TRANSACTIONAL_ID_CONFIG);
int transactionTimeoutMs = config.getInt(ProducerConfig.TRANSACTION_TIMEOUT_CONFIG);
long retryBackoffMs = config.getLong(ProducerConfig.RETRY_BACKOFF_MS_CONFIG);
transactionManager = new TransactionManager(logContext, transactionalId, transactionTimeoutMs, retryBackoffMs);
... ...
}
return transactionManager;
}
4. 如何实现重试
4.1 原理图
1|——创建KafkaProducer对象后,就会创建一个后台线程KafkaThread,它会扫描判断RecordAccumlator中是否有消息
2|——调用KafkaProducer.send()发送消息,把消息保存到RecordAccumlator中(append)
3|——后台线程 扫描i到RecordAccumlator中有消息后,将消息发送到Broker (真正发送)
4|——如果发送失败,判断是否需要重试。如果允许重试,把消息保存到RecordAccumlator中,等待后台线程再次扫描 |——发送的代码在Sender.java——|
4.2 RecordAccumlator——保存消息/重发消息的核心
|——核心数据结构是Deque,双端队列
|——发送消息之前,保存发送的消息
|——异步线程启动后,从这里取数据发送到Broker
|——当出错允许重试后,重新保存消息