1、send之异步消息发送步骤
具体步骤:
- 处理数据源
- 对k/v进行序列化
- 对消息选择适合的分区
- 根据元数据,封装分区对象
- 确保消息没超过单条消息最大默认值1MB
- 绑定消息的回调函数
- 将消息放到累加器中
- 启动线程发送消息
/**
* Implementation of asynchronously send a record to a topic.
* 异步消息发送核心方法
*/
private Future<RecordMetadata> doSend(ProducerRecord<K, V> record, Callback callback) {
TopicPartition tp = null;
try {
throwIfProducerClosed();
// first make sure the metadata for the topic is available
/**
* 步骤一:对源数据的处理
* 同步等待拉取元数据 。
* maxBlockTimeMs最多能等待多久
*/
ClusterAndWaitTime clusterAndWaitTime;
try {
clusterAndWaitTime = waitOnMetadata(record.topic(), record.partition(), maxBlockTimeMs);
} catch (KafkaException e) {
if (metadata.isClosed())
throw new KafkaException("Producer closed while send in progress", e);
throw e;
}
/**
* remainingWaitMs 还剩余多少时间可以去使用
* clusterAndWaitTime.waitedOnMetadataMs 代表的是拉取元数据用了多少时间
* maxBlockTimeMs 用了多少时间=还剩余多少时间可以使用。
*/
long remainingWaitMs = Math.max(0, maxBlockTimeMs - clusterAndWaitTime.waitedOnMetadataMs);
// 更新集群的的元数据
Cluster cluster = clusterAndWaitTime.cluster;
/**
* 步骤二:
* 对消息的Key和value进行序列化。
*/
byte[] serializedKey;
try {
serializedKey = keySerializer.serialize(record.topic(), record.headers(), record.key());
} catch (ClassCastException cce) {
throw new SerializationException("Can't convert key of class " + record.key().getClass().getName() +
" to class " + producerConfig.getClass(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG).getName() +
" specified in key.serializer", cce);
}
byte[] serializedValue;
try {
serializedValue = valueSerializer.serialize(record.topic(), record.headers(), record.value());
} catch (ClassCastException cce) {
throw new SerializationException("Can't convert value of class " + record.value().getClass().getName() +
" to class " + producerConfig.getClass(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG).getName() +
" specified in value.serializer", cce);
}
/**
* 步骤三:
* 根据分区器选择消息应该发送的分区,选择合适的分区
*/
int partition = partition(record, serializedKey, serializedValue, cluster);
/**
* 步骤四:
* 根据元数据信息,封装分区对象
* tp:TopicPartition
*/
tp = new TopicPartition(record.topic(), partition);
setReadOnly(record.headers());
Header[] headers = record.headers().toArray();
int serializedSize = AbstractRecords.estimateSizeInBytesUpperBound(apiVersions.maxUsableProduceMagic(),
compressionType, serializedKey, serializedValue, headers);
/**
* 步骤五:
* 确认消息的大小是否超出了最大值
* KafkaProducer初始化时指定了参数,代表Producer最大能发送的一条消息有多大
* 默认最大的是1M,我们都会去重新配置它的大小
*/
ensureValidRecordSize(serializedSize);
long timestamp = record.timestamp() == null ? time.milliseconds() : record.timestamp();
log.trace("Sending record {} with callback {} to topic {} partition {}", record, callback, record.topic(), partition);
// producer callback will make sure to call both 'callback' and interceptor callback
/**
* 步骤六:
* 为每一个异步的消息绑定一个回调函数
*/
Callback interceptCallback = new InterceptorCallback<>(callback, this.interceptors, tp);
if (transactionManager != null && transactionManager.isTransactional())
transactionManager.maybeAddPartitionToTransaction(tp);
/**
* 步骤七:
* 把消息放到accumulator(就是一块32M的内存空间)
* 然后由accumulator将消息封装成为一批一批的去进行发送
*/
RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey,
serializedValue, headers, interceptCallback, remainingWaitMs);
//唤醒条件:批次满了或新创建出来新的批次,让它发送数据
if (result.batchIsFull || result.newBatchCreated) {
log.trace("Waking up the sender since topic {} partition {} is either full or getting a new batch", record.topic(), partition);
/**
* 步骤八:
* 唤醒sender线程,他才是真正发送数据的线程
*/
this.sender.wakeup();
}
return result.future;
2、大型项目中的自定义异常的设计思路
思路:在底层代码面不处理直接往上抛,只自定义相应的异常以便用户可以清楚知道问题具体出现的问题,交到核心的代码块中去统一捕获并进行相应的处理
检查单条消息是否超过阈值:
RecordTooLargeException
RecordTooLargeException
/**
* Validate that the record size isn't too large
*/
private void ensureValidRecordSize(int size) {
if (size > this.maxRequestSize)
//自定义了异常。并把异常往上抛,统一在核心代码中进行捕获
throw new Record