Producer

发布消息的对象称之为主题生产者(Kafka topic producer)

生产者的流程图如下:

kafka 生产者clientid kafka 生产者retry_元数据

以上是整个生产者发送消息的整个流程。

  1. ProducerInterceptors对消息进行拦截,这里可以对信息做一定的筛选。
  2. Serializer对消息的key和value进行序列化。
  3. Parititioner为消息选择分配分区的策略,可以进行重定义。
  4. RecordAccumulator暂存需要发送消息,到一定量后发送,实现批量发送。
  5. Sender为另起的发送线程从RecordAccumulator拿到消息,然后发送到客户端。
  6. 构造ClientRequset,封装发送的消息。
  7. 把ClientRequset交给NetworkClient,准备发送。
  8. NetworkClient把消息放入KafkaChannel中缓存。
  9. 执行网路I/O,发送请求。
  10. 受到响应,调用ClientRequset中的回调函数。
  11. 调用RecordBatch的回调函数,最终调用每个消息上注册的回调函数。

KafkaProducer提供了四个类方法:

  1. send():发送消息,实际是把消息放在RecordAccumulator中,然后Sender线程发送。
  2. flush():刷新操作,把RecordAccumulator中的所有消息发送完成,阻塞效用的线程。
  3. partitionsFor(String topic):在KafkaProducer中维护一个Metadata对象用于储存kafka集群的元数据,Metadata中的元数据会定期的更新。partitionsFor方法从Metadata中获取制定topic的分区信息。
  4. 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源码剖析》