深入Kafka源码03:生产者Producer之send方法核心流程、项目异常体系构建、send步骤一之元数据拉取加载过程

1、Kafka的元数据:元数据在Kafka中是很
摘要由CSDN通过智能技术生成

1、send之异步消息发送步骤

具体步骤:

  1. 处理数据源
  2. 对k/v进行序列化
  3. 对消息选择适合的分区
  4. 根据元数据,封装分区对象
  5. 确保消息没超过单条消息最大默认值1MB
  6. 绑定消息的回调函数
  7. 将消息放到累加器中
  8. 启动线程发送消息
    /**
     * 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
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pub.ryan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值