Análisis del proceso de producción de datos del cliente Java de Kafka, desde el envío del código de implementación del tipo hasta el código de implementación del serializador.

1. Envío de mensajes

1. Análisis del proceso de producción de datos del cliente de Kafka Java

  1. Para construir un primer ProducerRecordobjeto que se pueda declarar tema Tema , partición Partición , clave Clave y valor Valor , temas y valores que se deben declarar, no se pueden especificar particiones y claves.
  2. Llame al método send () para enviar el mensaje.
  3. Debido a que el mensaje se va a transmitir en la red, debe ser serializado La función del serializador es serializar la clave y los objetos de valor del mensaje en una matriz de bytes.
  4. Los siguientes datos de la partición son, si entre los ProducerRecordobjetos especificados, la partición no hará nada directamente al retorno de la partición especificada; si no, entonces la partición se seleccionará en función de una clave de partición, una buena elección Después de la partición, el productor sabe a qué tema y partición enviar los registros.
  5. Luego, este registro se agregará a un lote de registros y todos los mensajes de este lote se enviarán al mismo tema y partición. Habrá un hilo separado para enviar estos lotes de registros al Broker correspondiente.
  6. El corredor recibe correctamente el mensaje, lo que indica que la transmisión se ha realizado correctamente y devuelve los metadatos del mensaje (incluida la información sobre el tema y la partición y el desplazamiento registrado en la partición) . El envío falló, puede optar por volver a intentarlo o lanzar una excepción directamente.

Paquetes dependientes <kafka.version>2.0.0</kafka.version>

<dependency>
	<groupId>org.apache.kafka</groupId> 
	<artifactId>kafka_${
    
    scala.version}</artifactId> 
	<version>${
    
    kafka.version}</version> 
	<exclusions> 
		<exclusion> 
			<groupId>org.apache.zookeeper</groupId> 
			<artifactId>zookeeper</artifactId> 
		</exclusion> 
		<exclusion> 
			<groupId>org.slf4j</groupId> 
			<artifactId>slf4j-log4j12</artifactId> 
		</exclusion> 
		<exclusion> 
			<groupId>log4j</groupId> 
			<artifactId>log4j</artifactId> 
		</exclusion> 
	</exclusions> 
</dependency>

2. Configuración de parámetros necesaria

Ver el código base:com.heima.kafka.chapter2.KafkaProducerAnalysis

public static Properties initConfig() {
    
     
	Properties props = new Properties(); 
	// 该属性指定 brokers 的地址清单,格式为 host:port。清单里不需要包含所有的 broker 地址, 
	// 生产者会从给定的 broker 里查找到其它 broker 的信息。——建议至少提供两个 broker 的信息,因为一旦其中一个宕机,生产者仍然能够连接到集群上。 
	props.put("bootstrap.servers", brokerList); 
	// 将 key 转换为字节数组的配置,必须设定为一个实现了 org.apache.kafka.common.serialization.Serializer 接口的类, 
	// 生产者会用这个类把键对象序列化为字节数组。 
	// ——kafka 默认提供了 StringSerializer和 IntegerSerializer、 ByteArraySerializer。当然也可以自定义序列化器。 
	props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); 
	// 和 key.serializer 一样,用于 value 的序列化 
	props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
	// 用来设定KafkaProducer对应的客户端ID,默认为空,如果不设置KafkaProducer会自动 生成一个非空字符串。 
	// 内容形式如:"producer-1" 
	props.put("client.id", "producer.client.id.demo"); 
	return props; 
}
Properties props = initConfig(); 
	KafkaProducer<String, String> producer = new KafkaProducer<>(props); 
// 	KafkaProducer<String, String> producer = new KafkaProducer<>(props, 
// new StringSerializer(), new StringSerializer()); 
//生成 ProducerRecord 对象,并制定 Topic,key 以及 value 
	ProducerRecord<String, String> record = new ProducerRecord<>(topic, "hello, Kafka!"); 
	try {
    
    
		// 发送消息 
		producer.send(record);
	}

3. Tipo de entrega

Enviar y olvidar

producer.send(record)

Enviar sincrónicamente

//通过send()发送完消息后返回一个Future对象,然后调用Future对象的get()方法等待kafka响应 
//如果kafka正常响应,返回一个RecordMetadata对象,该对象存储消息的偏移量 
// 如果kafka发生错误,无法正常响应,就会抛出异常,我们便可以进行异常处理 
producer.send(record).get();

Envío asincrónico

producer.send(record, new Callback() {
    
     
	public void onCompletion(RecordMetadata metadata, Exception exception) {
    
     
	if (exception == null) {
    
     
		System.out.println(metadata.partition() + ":" + metadata.offset()); 
	} 
} 
});

4. Serializador

Los mensajes deben serializarse para ser transmitidos en la red, y la función del serializador es solo eso.

Kafka proporciona el serializador de cadenas predeterminado ( org.apache.kafka.common.serialization.StringSerializer), así como los serializadores integer ( IntegerSerializer) y byte array ( BytesSerializer). Todos estos serializadores implementan la interfaz ( org.apache.kafka.common.serialization.Serializer) y básicamente pueden satisfacer las necesidades de la mayoría de los escenarios.

5. Serializador personalizado

Ver el código base:com.heima.kafka.chapter2.CompanySerializer

/**
	* 自定义序列化器
	*/
public class CompanySerializer implements Serializer<Company> {
    
     
	@Override 
	public void configure(Map configs, boolean isKey) {
    
     
	}
	
	@Override 
	public byte[] serialize(String topic, Company data) {
    
     
		if (data == null) {
    
     
			return null; 
		}
		byte[] name, address; 
		try {
    
    
			if (data.getName() != null) {
    
     
				name = data.getName().getBytes("UTF-8"); 
			} else {
    
     
				name = new byte[0]; 
			}
			if (data.getAddress() != null) {
    
     
				address = data.getAddress().getBytes("UTF-8"); 
			} else {
    
     
				address = new byte[0]; 
			}
			ByteBuffer buffer = ByteBuffer. allocate(4 + 4 + name.length + address.length); 
			buffer.putInt(name.length); 
			buffer.put(name); 
			buffer.putInt(address.length); 
			buffer.put(address); 
			return buffer.array(); 
		} catch (UnsupportedEncodingException e) {
    
     
			e.printStackTrace(); 
		}
		return new byte[0]; 
	}
	
	@Override 
	public void close() {
    
     
	} 
}
  • Utilice un serializador personalizado

Ver el código base:com.heima.kafka.chapter2.ProducerDefineSerializer

public class ProducerDefineSerializer {
    
     
	public static final String brokerList = "localhost:9092"; 
	public static final String topic = "heima"; 
	
	public static void main(String[] args) throws ExecutionException, InterruptedException {
    
     
		Properties properties = new Properties(); 
		properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); 
		properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, CompanySerializer.class.getName()); 
// properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
// ProtostuffSerializer.class.getName()); 
	properties.put("bootstrap.servers", brokerList); 
	
	KafkaProducer<String, Company> producer = new KafkaProducer<>(properties);
	Company company = Company.builder().name("kafka") .address("北京").build(); 
// Company company = Company.builder().name("hiddenkafka") 
// .address("China").telphone("13000000000").build(); 
	ProducerRecord<String, Company> record = new ProducerRecord<>(topic, company); 
	producer.send(record).get(); 
	} 
}

6. Particionador

El propio Kafka tiene su propia estrategia de partición. Si no se especifica, se utilizará la estrategia de partición predeterminada:

Kafka asigna particiones de acuerdo con la clave del mensaje, es decir hash(key) % numPartitions. Si la clave es la misma, se asignará a una partición unificada.

org.apache.kafka.clients.producer.internals.DefaultPartitionerAnálisis de código fuente

public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
    
     
	List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); 
	int numPartitions = partitions.size(); 
	if (keyBytes == null) {
    
     
		int nextValue = this.nextValue(topic); 
		List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic); 
		if (availablePartitions.size() > 0) {
    
     
			int part = Utils.toPositive(nextValue) % availablePartitions.size(); 
			return ((PartitionInfo)availablePartitions.get(part)).partition(); 
		} else {
    
     
			return Utils.toPositive(nextValue) % numPartitions; 
		} 
	} else {
    
     
		return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions; 
	} 
}
  • Particionador personalizado ver código base com.heima.kafka.chapter2.DefinePartitioner
/**
	* 自定义分区器 
	*/ 
public class DefinePartitioner implements Partitioner {
    
     
	private final AtomicInteger counter = new AtomicInteger(0); 
	
	@Override 
	public int partition(String topic, Object key, byte[] keyBytes,Object value, byte[] valueBytes, Cluster cluster) {
    
     
	List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); 
	int numPartitions = partitions.size(); 
	if (null == keyBytes) {
    
     
		return counter.getAndIncrement() % numPartitions; 
		} else return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions; 
	}
	@Override 
	public void close() {
    
     
	}
	@Override 
	public void configure(Map<String, ?> configs) {
    
     
	} 
}
  • Para implementar un particionador personalizado debe ProducerConfig.PARTITIONER_CLASS_CONFIGimplementarse a través de parámetros de configuración
// 自定义分区器的使用 
props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,DefinePartitioner.class.getNam e());

7. Interceptor

Producer Interceptor ( interceptor) es una característica bastante nueva , que junto con el interceptor del lado del consumidor se introdujeron en la versión 0.10 de Kafka y se utilizan principalmente para implementar una lógica de control personalizada en el lado del cliente .

El interceptor productor se puede utilizar para realizar un trabajo preparatorio antes de enviar el mensaje.

escenas que se utilizarán

  1. Filtra los mensajes no calificados de acuerdo con una regla determinada
  2. Modificar el contenido del mensaje
  3. Requisitos estadísticos

Ver código base: Interceptor personalizadocom.heima.kafka.chapter2.ProducerInterceptorPrefix

/**
	* 自定义拦截器 
	*/ 
public class ProducerInterceptorPrefix implements ProducerInterceptor<String, String> {
    
     
	private volatile long sendSuccess = 0; 
	private volatile long sendFailure = 0; 
	
	@Override 
	public ProducerRecord<String, String> onSend( ProducerRecord<String, String> record) {
    
     
		String modifiedValue = "prefix1-" + record.value(); 
		return new ProducerRecord<>(record.topic(), record.partition(), record.timestamp(), record.key(), modifiedValue, record.headers()); 
// if (record.value().length() < 5) { 
// throw new RuntimeException();
// } 
// return record; 
	}
	
	@Override 
	public void onAcknowledgement( RecordMetadata recordMetadata, Exception e) {
    
     
		if (e == null) {
    
     
			sendSuccess++; 
		} else {
    
     
			sendFailure++; 
		} 
	}
	
	@Override 
	public void close() {
    
     
		double successRatio = (double) sendSuccess / (sendFailure + sendSuccess); 
		System.out.println("[INFO] 发送成功率=" + String.format("%f", successRatio * 100) + "%"); 
	}
	
	@Override 
	public void configure(Map<String, ?> map) {
    
     
	} 
}
  • Después de implementar un interceptor personalizado, debe especificar el interceptor en los parámetros de configuración. El valor predeterminado de este parámetro está vacío, de la siguiente manera:
// 自定义拦截器使用 
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,ProducerDefineSerializer.cla ss.getName());
  • Manifestación:

Receptor remitente





Los estudiantes que necesiten el artículo de referencia "Manual técnico de Kafka" pueden agregar el asistente VX: C18173184271 Observaciones:CSDN Java_Caiyo ¡Obtenga información gratuita de Java!

Supongo que te gusta

Origin blog.csdn.net/Java_Caiyo/article/details/112648290
Recomendado
Clasificación