Producer
发布消息的对象称之为主题生产者(Kafka topic producer)
生产者的流程图如下:
以上是整个生产者发送消息的整个流程。
- ProducerInterceptors对消息进行拦截,这里可以对信息做一定的筛选。
- Serializer对消息的key和value进行序列化。
- Parititioner为消息选择分配分区的策略,可以进行重定义。
- RecordAccumulator暂存需要发送消息,到一定量后发送,实现批量发送。
- Sender为另起的发送线程从RecordAccumulator拿到消息,然后发送到客户端。
- 构造ClientRequset,封装发送的消息。
- 把ClientRequset交给NetworkClient,准备发送。
- NetworkClient把消息放入KafkaChannel中缓存。
- 执行网路I/O,发送请求。
- 受到响应,调用ClientRequset中的回调函数。
- 调用RecordBatch的回调函数,最终调用每个消息上注册的回调函数。
KafkaProducer提供了四个类方法:
- send():发送消息,实际是把消息放在RecordAccumulator中,然后Sender线程发送。
- flush():刷新操作,把RecordAccumulator中的所有消息发送完成,阻塞效用的线程。
- partitionsFor(String topic):在KafkaProducer中维护一个Metadata对象用于储存kafka集群的元数据,Metadata中的元数据会定期的更新。partitionsFor方法从Metadata中获取制定topic的分区信息。
- close():关闭Producer对象,设置close标志,等待RecordAccumulator中所有消息发送完成,关闭Sender线程。
KafkaProducer对象初始化:
import org.apache.kafka.clients.producer.Producer;
public class KafkaProducer<K, V> implements Producer<K, V> {
//clientID的生成器,如果没有明确制定的client的ID,就使用这个字段生成一个ID。每次需要获取clientID时用getAndIncrement实现。
private static final AtomicInteger PRODUCER_CLIENT_ID_SEQUENCE = new AtomicInteger(1);
//分区选择器,实现消息分配的策略。
private final Partitioner partitioner;
//消息的最大长度,可配置max.request.size
private final int maxRequestSize;
//发送单个消息的缓冲区大小buffer.memory
private final long totalMemorySize;
//收集缓冲的消息,等待Sender线程发送
private final RecordAccumulator accumulator;
//发送消息的Sender任务,实现了Runnable借口,在ioThread中执行。
private final Sender sender;
//Metrics:Kafka性能监控
private final Metrics metrics;
//执行Sender任务中发送消息的线程。
private final Thread ioThread;
//压缩算法,可选的是none、gzip、snappy、lz4。针对RecordAccumulator中多条消息进行的压缩,消息越多,压缩效果越好。
private final CompressionType compressionType;
//序列化函数
private final Serializer<K> keySerializer;
private final Serializer<V> valueSerializer;
//整个Kafka集群的元数据
private final Metadata metadata;
//等待更新Kafka集群元数据的最大时长
private final long maxBlockTimeMs;
//发送消息的超时时间,也就是从消息发送到受到ACK响应的最长时长
private final int requestTimeoutMs;
//ProducerInterceptor的集合,一系列的消息拦截器
private final ProducerInterceptors<K, V> interceptors;
//配置对象,使用反射来初始化KafkaProducer配置的相对对象。
private final ProducerConfig producerConfig;
KafkaProducer(Map<String, Object> configs,
Serializer<K> keySerializer,
Serializer<V> valueSerializer,
ProducerMetadata metadata,
KafkaClient kafkaClient,
ProducerInterceptors interceptors,
Time time) {
//初始化一些配置参数...
//...
//利用发射实例化配置的partition类(对消息进行分区的实现),keySerializer和valueSerializer(序列化函数)
this.partitioner = config.getConfiguredInstance(ProducerConfig.PARTITIONER_CLASS_CONFIG, Partitioner.class);
this.keySerializer = config.getConfiguredInstance(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, Serializer.class);
this.valueSerializer = config.getConfiguredInstance(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, Serializer.class);
//创建并更新Kafka集群的元数据
this.metadata = new Metadata(retryBackoffMs, config.getLong(ProducerConfig.METADATA_MAX_AGE_CONFIG));
List<InetSocketAddress> addresses = ClientUtils.parseAndValidateAddresses(config.getList(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG));
this.metadata.update(Cluster.bootstrap(addresses), time.milliseconds());
//创建RecordAccumulator
this.accumulator = new RecordAccumulator(config.getInt(ProducerConfig.BATCH_SIZE_CONFIG),
this.totalMemorySize,
this.compressionType,
config.getLong(ProducerConfig.LINGER_MS_CONFIG),
retryBackoffMs,
metrics,
time);
//创建NetworkClient,这个是KafkaProducer网络的核心。
NetworkClient client = new NetworkClient(
new Selector(
//connections.max.idle.ms:多少毫秒之后关闭闲置的连接。
config.getLong(ProducerConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG),
this.metrics,
time,
"producer",
channelBuilder),
this.metadata,
clientId,
//max.in.flight.requests.per.connection:阻塞之前,客户端单个连接上发送的未应答请求的最大数量。注意,如果此设置设置大于1且发送失败,
//则会由于重试(如果启用了重试)会导致消息重新排序的风险。
config.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION),
//reconnect.backoff.ms: 尝试重新连接到给定主机之前等待的基本时间量。这避免了在循环中高频率的重复连接到主机。
// 这种回退适应于客户端对broker的所有连接尝试。
config.getLong(ProducerConfig.RECONNECT_BACKOFF_MS_CONFIG),
//send.buffer.bytes: 发送数据时,用于TCP发送缓存(SO_SNDBUF)的大小。如果值为 -1,将默认使用系统的。
config.getInt(ProducerConfig.SEND_BUFFER_CONFIG),
//receive.buffer.bytes: 读取数据时使用的TCP接收缓冲区(SO_RCVBUF)的大小。如果值为-1,则将使用OS默认值。
config.getInt(ProducerConfig.RECEIVE_BUFFER_CONFIG),
this.requestTimeoutMs,
time);
this.sender = new Sender(client,
this.metadata,
this.accumulator,
//max.in.flight.requests.per.connection:阻塞之前,客户端单个连接上发送的未应答请求的最大数量。
//注意,如果此设置设置大于1且发送失败,则会由于重试(如果启用了重试)会导致消息重新排序的风险。
config.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION) == 1,
//max.request.size请求的最大大小(以字节为单位)。此设置将限制生产者的单个请求中发送的消息批次数,以避免发送过大的请求。这也是最大消息批量大小的上限。
// 请注意,服务器拥有自己的批量大小,可能与此不同。
config.getInt(ProducerConfig.MAX_REQUEST_SIZE_CONFIG),
/*
acks=0 如果设置为0,那么生产者将不等待任何消息确认。消息将立刻添加到socket缓冲区并考虑发送。在这种情况下不能保障消息被服务器接收到。并且重试机制不会生效(因为客户端不知道故障了没有)。每个消息返回的offset始终设置为-1。
acks=1,这意味着leader写入消息到本地日志就立即响应,而不等待所有follower应答。在这种情况下,如果响应消息之后但follower还未复制之前leader立即故障,那么消息将会丢失。
acks=all 这意味着leader将等待所有副本同步后应答消息。此配置保障消息不会丢失(只要至少有一个同步的副本或者)。这是最强壮的可用性保障。等价于acks=-1。
*/
(short) parseAcks(config.getString(ProducerConfig.ACKS_CONFIG)),
//retries:设置一个比零大的值,客户端如果发送失败则会重新发送。
// 注意,这个重试功能和客户端在接到错误之后重新发送没什么不同。
// 如果max.in.flight.requests.per.connection没有设置为1,有可能改变消息发送的顺序,因为如果2个批次发送到一个分区中,并第一个失败了并重试,但是第二个成功了,那么第二个批次将超过第一个。
config.getInt(ProducerConfig.RETRIES_CONFIG),
this.metrics,
new SystemTime(),
clientId,
this.requestTimeoutMs);
//启动Sender线程
String ioThreadName = "kafka-producer-network-thread" + (clientId.length() > 0 ? " | " + clientId : "");
this.ioThread = new KafkaThread(ioThreadName, this.sender, true);
this.ioThread.start();
}
}
参考书籍:《Apache Kafka源码剖析》