kafka生产者producer

本文深入探讨了Java客户端KafkaProducer的实现,包括构造过程、元数据管理和发送消息的流程。介绍了RecordAccumulator、sender线程的工作原理,以及各种配置参数的作用,如元数据更新时间、重试间隔、acks等。还提到了消息发送的序列化、分区策略以及可能遇到的消息丢失和乱序问题,强调了业务场景对消息正确性的保障。
摘要由CSDN通过智能技术生成

java Client:

元数据:

public final class Cluster {
    private final List<Node> nodes;
    private final Set<String> unauthorizedTopics;
    private final Set<String> internalTopics;

    private final Map<TopicPartition, PartitionInfo> partitionsByTopicPartition;

    private final Map<String, List<PartitionInfo>> partitionsByTopic;
    private final Map<String, List<PartitionInfo>> availablePartitionsByTopic;


    private final Map<Integer, List<PartitionInfo>> partitionsByNode;

    private final Map<Integer, Node> nodesById;

public class PartitionInfo {
    private final String topic;
    private final int partition;
    private final Node leader;
    private final Node[] replicas;
    private final Node[] inSyncReplicas; //用处?

KafkaProducer

先看下构造,做了一些参数初始化,关键类的初始化;

Metadata(支持topic超时机制),metadata.update(创建node通过指定的broker),通知sender线程;

RecordAccumulator初始化(内存池大小,压缩格式,重试间隔,批次发送时间阈值)

初始化sender(是否保证消息顺序,重试次数(处理batch,如果有异常,且小于重试次数,重新把批次加入到队列),ack(0,发送不管;1,等leader响应;-1,写入所有副本))

启动sender线程;

发送数据:

先看下消息生成,ProducerRecord:

    private final String topic;
    private final Integer partition;
    private final K key;
    private final V value;
    private final Long timestamp;

我们再生成消息的时候,可以指定这些值,也可以指定部分;

  1. send--》doSend
  2. waitOnMetadata,等待获取元数据
    1. metadata添加topic,cluster获取partition info,如果存在则返回,否则-》
    2. 唤醒sender线程
    3. 循环获取partion count,直到不等0或者,超时或异常,awaitUpdate-》循环判断version,没有增加,直到超时
    4. 返回数据后,cluster判断topic是否授权,未授权,异常;
    5. 如果获取的partition数目小于指定的partition,异常;
  3. 对key,value进行序列化
  4. partition-》
    1. record指定partition,则返回,否则,
    2. 获取分区,如果有key,key%partitions(总共partition数目);
    3. 如果没有指定key,如果有可用的partition(partition info的leader不为null,则添加到available partitions中),根据递增值%可用个数,轮询,否则,递增值%partitions;
  5. ensureValidRecordSize,验证大小,若超过最大请求大小maxRequestSize1M,异常;如果超过totalMemorySize 32M,异常;
  6. accumulator.append
    1. ConcurrentMap<TopicPartition, Deque<RecordBatch>> batches;(topic和partition组合key)获取batch queue;putIfAbsent,寻找实现
    2. CopyOnWriteMap,实现了上面的map,如果不存在则添加,否则获取,该类有些不一样
      1. 首先get方法没有锁,put方法有锁,这种复杂的数据结构,读也要加上锁,防止结构错误
      2. 这里get避免加锁原因是,put时候,Map<K, V> copy = new HashMap<K, V>(this.map);把原map数据拷贝过去,然后
      3. 调用this.map = Collections.unmodifiableMap(copy);并且map变量是volatile,
      4. 这样做get方法使用旧的map也没有结构上的异常,也可以立即使用新的map;
    3. 如果批次满了,或者创建新的批次,则唤醒sender,io在sender线程,唤醒发送数据;

sender线程:

  • poll操作:
  1. maybeUpdate封装请求,满足一定时间情况才做后续处理,否则直接返回-》leastLoadedNode,从连接池选择目前未完成请求个数最小的node,如果是0,就使用,否则继续查找,找到node
  2. //   node info
        private final int id;
        private final String idString;
        private final String host;
        private final int port;
        private final String rack;
  3. 如果可以发送请求,封装成,MetadataRequest,发送请求;否则,查看是否可以建立链接,建立链接;
  4. 执行io操作,selector.poll
  5. handleCompletedSends(把发送出去的数据,存储在completedSends),处理该结构,遍历,在inFlightRequests查找node下面第一个请求,如果不期望broker响应,不处理,否则头部弹出该节点,添加到responses
  6. handleCompletedReceives,处理响应-》maybeHandleCompletedReceive,version+1-》Metadata.update,notifyall唤醒等待获取元数据的线程;添加到responses
    1. metadata.update
    2.         this.needUpdate = false;
              this.lastRefreshMs = now;
              this.lastSuccessfulRefreshMs = now;
              this.version += 1;
    3. 这里遍历topics,key就是topic,判断value,如果是-1,更新value(更新时间),超时,删除,(超时说明该topic长时间未使用了,刷新改时间的方法是add,把value置为-1);
    4. 覆盖cluster,唤醒等待的线程;
  • 从头看一遍做了什么
  1. 获取cluster,ready-》
    1. 遍历所有topic|partition获取队列第一个batche(topicpartition,queue,组成的map)获取partition的leader信息
    2. 如果leader为null同时queue不为空,加入unknowLeaderTopics
    3. 临时set,readdyNodes去重判断,不重复,则获取deque的第一个batch,继续处理
    4. 判断是否满了(deque.size()>1或batch满了),或者超时,或者内存池空,或者服务要关闭,或者flushInProgress
    5. 同时,如果满足(如果是重试的批次,是否达到重试间隔),才能把leader node添加到readyNodes,(也就是如果batch超时,该queue后面都不会发送)
    6. return new ReadyCheckResult(readyNodes,nextReadyCheckDelayMs,unknownLeaderTopics);
  2.         if (!result.unknownLeaderTopics.isEmpty()) {
                // The set of topics with unknown leader contains topics with leader election pending as well as
                // topics which may have expired. Add the topic again to metadata to ensure it is included
                // and request metadata update, since there are messages to send to the topic.
                for (String topic : result.unknownLeaderTopics)
                    this.metadata.add(topic);
                this.metadata.requestUpdate();
            }
  3. 上面是对没有获取到leader的node进行获取元数据标识,请求更新,设置 needUpdate为true;
  4. 遍历readyNodes,判断node网络状态,client.ready(node,now),如果检查有问题(如网络),从readyNodes删除
  5. 对每个批次group,封装为一个请求,然后发送;

网络封装结构图:

核心参数(名字可能完整):

  • metadata.max.age.ms:元数据更新时间,默认300s    
  • retry.backoff.ms:更新cluster时间间隔,还表示batch重试时间间隔,默认100ms
  • 上面两个搭配使用,有些时间处理逻辑,防止请求元数据太频繁,也防止需要请求时时间太长;
  • retries: 重试次数
  • acks:1,leader写入返回;0,不关心返回;-1:isr集合副本全部写入返回;
  • 如果acks是-1,当isr只有leader时,退化为acks=1,此时保证数据可靠性,需要配合min.insync.replicas参数,大于该值才算提交数据

  • linger.ms: 批次多久发送,默认0,批次满了发送
  • max.request.size:一条消息大小,默认1M
  • buffer.memory:缓存大小(内存池),batch获取使用,默认32M
  • batch.size:批次大小
  • receive.buffer.bytes 接收缓冲区大小
  • send.buffer.bytes 发送缓冲区大小

  • compression.type 设置压缩格式
  • connections.max.idle.ms 链接空闲多久关闭,默认9分钟
  • max.in.flight.requests.per.connection,每个链接发送后,容忍多少条没有响应,默认5,(acks非0才有效,因为重试机制,可能会乱序,设置1可以保证有序,但会影响吞吐)

问题:

  • 对于无状态消息,没问题;
  • 对于有状态的,就要通过key或指定partition来映射到目标机器
    • 这里虽然发送的是批消息,但也会出现下面问题
    • 这里涉及到消息丢失,比如A、B消息,A丢失,B到,这时候,要通过业务服务自己保证正确性;
    • 根据业务情况判断,消息重要性,是否开启重试,如果开启会出现,A、B消息,A丢失,B到,然后A再到,这样要业务保证正确性
    • 其实对于这个问题,即使没有kafka,端对端,也会有同样的问题,需要根据业务场景来决定,是否重发(这里要注意,如何判断是否要重发?是发送失败?(一般也会重发几次,没啥问题,并且不会出现乱序情况,因为是顺序执行,这种情况会出现消息丢失;)还是对方没响应?(这种情况会出现乱序))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

MyObject-C

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

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

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

打赏作者

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

抵扣说明:

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

余额充值