文章目录
前言
sender的run
/**
* 步骤六:
* 对超时的批次是如何处理的?
*
*/
List<RecordBatch> expiredBatches = this.accumulator.abortExpiredBatches(this.requestTimeout, now);
// update sensors
for (RecordBatch expiredBatch : expiredBatches)
this.sensors.recordErrors(expiredBatch.topicPartition.topic(), expiredBatch.recordCount);
sensors.updateProduceRequestMetrics(batches);
public List<RecordBatch> abortExpiredBatches(int requestTimeout, long now) {
List<RecordBatch> expiredBatches = new ArrayList<>();
int count = 0;
//
for (Map.Entry<TopicPartition, Deque<RecordBatch>> entry : this.batches.entrySet()) {
//获取到每个分区的队列 -》 队列里面对应的批次
Deque<RecordBatch> dq = entry.getValue();
TopicPartition tp = entry.getKey();
// We only check if the batch should be expired if the partition does not have a batch in flight.
// This is to prevent later batches from being expired while an earlier batch is still in progress.
// Note that `muted` is only ever populated if `max.in.flight.request.per.connection=1` so this protection
// is only active in this case. Otherwise the expiration order is not guaranteed.
if (!muted.contains(tp)) {
synchronized (dq) {
// iterate over the batches and expire them if they have been in the accumulator for more than requestTimeOut
RecordBatch lastBatch = dq.peekLast();
//迭代的看每个分区里面的每个批次
Iterator<RecordBatch> batchIterator = dq.iterator();
while (batchIterator.hasNext()) {
RecordBatch batch = batchIterator.next();
boolean isFull = batch != lastBatch || batch.records.isFull();
// check if the batch is expired
//判断一下是否超时
if (batch.maybeExpire(requestTimeout, retryBackoffMs, now, this.lingerMs, isFull)) {
//增加到超时的数据结构里面
expiredBatches.add(batch);
count++;
//从数据结构里面移除
batchIterator.remove();
//释放资源
deallocate(batch);
} else {
// Stop at the first batch that has not expired.
break;
}
}
}
}
}
if (!expiredBatches.isEmpty())
log.trace("Expired {} batches in accumulator", count);
return expiredBatches;
}
public boolean maybeExpire(int requestTimeoutMs, long retryBackoffMs, long now, long lingerMs, boolean isFull) {
boolean expire = false;
String errorMessage = null;
/**
* requestTimeoutMs:代表的是请求发送的超时的时间。默认值是30s.
* now:当前时间
* lastAppendTime:批次的创建的时间(上一次重试的时间)
* now - this.lastAppendTime 大于30秒,说明批次超时了 还没发送出去。
*/
if (!this.inRetry() && isFull && requestTimeoutMs < (now - this.lastAppendTime)) {
expire = true;
//记录异常信息
errorMessage = (now - this.lastAppendTime) + " ms has passed since last append";
/**
* lingerMs: 100ms,无论如何都要把消息发送出去的时间
*
* createdMs:批次创建的时间
*
* 已经大于30秒了。 说明也是超时了。
*
*/
} else if (!this.inRetry() && requestTimeoutMs < (now - (this.createdMs + lingerMs))) {
expire = true;
errorMessage = (now - (this.createdMs + lingerMs)) + " ms has passed since batch creation plus linger time";
/**
* 针对重试
* lastAttemptMs: 上一次重试的时间(批次创建的时间)
* retryBackoffMs: 重试的时间间隔
* 说明也是超时了。
*/
} else if (this.inRetry() && requestTimeoutMs < (now - (this.lastAttemptMs + retryBackoffMs))) {
expire = true;
errorMessage = (now - (this.lastAttemptMs + retryBackoffMs)) + " ms has passed since last attempt plus backoff time";
}
if (expire) {
this.records.close();
//调用done方法
//方法里面传过去了一个TimeoutException的异常。(超时了)
this.done(-1L, Record.NO_TIMESTAMP,
new TimeoutException("Expiring " + recordCount + " record(s) for " + topicPartition + " due to " + errorMessage));
}
return expire;
}
调用done方法,并传入TimeoutException
public void done(long baseOffset, long timestamp, RuntimeException exception) {
log.trace("Produced messages to topic-partition {} with base offset offset {} and error: {}.",
topicPartition,
baseOffset,
exception);
// execute callbacks
/**
*
* 我们发送数据的时候,一条消息就代表一个thunk
* 遍历所以我们当时发送出去消息。
*/
for (int i = 0; i < this.thunks.size(); i++) {
try {
Thunk thunk = this.thunks.get(i);
//如果没有异常
if (exception == null) {
// If the timestamp returned by server is NoTimestamp, that means CreateTime is used. Otherwise LogAppendTime is used.
RecordMetadata metadata = new RecordMetadata(this.topicPartition, baseOffset, thunk.future.relativeOffset(),
timestamp == Record.NO_TIMESTAMP ? thunk.future.timestamp() : timestamp,
thunk.future.checksum(),
thunk.future.serializedKeySize(),
thunk.future.serializedValueSize());
//调用我们发送的消息的回调函数
//大家还记不记得我们在发送数据的时候
//还不是绑定了一个回调函数。
//这儿说的调用的回调函数
//就是我们开发,生产者代码的时候,我们用户传进去的那个
//回调函数。
thunk.callback.onCompletion(metadata, null);//带过去的就是没有异常
//也就是说我们生产者那儿的代码,捕获异常的时候就是发现没有异常。
} else {
//如果有异常就会把异常传给回调函数。
//由我们用户自己去捕获这个异常。
//然后对这个异常进行处理
//大家根据自己公司的业务规则进行处理就可以了。
//如果走这个分支的话,我们的用户的代码是可以捕获到timeoutexception
//这个异常,如果用户捕获到了,做对应的处理就可以了。
thunk.callback.onCompletion(null, exception);
}
} catch (Exception e) {
log.error("Error executing user-provided callback on message for topic-partition {}:", topicPartition, e);
}
}
this.produceFuture.done(topicPartition, baseOffset, exception);
}