kafka原理之生产者

本文介绍了Kafka生产者的API使用,包括异步和同步API的示例,以及如何处理本地连接问题。讨论了生产者的分区策略,包括默认策略和自定义分区器,并探讨了提高吞吐量的配置选项,如batch.size和linger.ms。同时,文章强调了数据可靠性和幂等性的设置,如acks配置和幂等性生产。最后,提到了事务原理和数据有序性的实现。
摘要由CSDN通过智能技术生成

batch.size:只有数据累计到batch.size后,sender才会发送数据。默认16k
linger.ms:如果迟迟没有达到batch.size,sender等待linger.ms设置时间之后,发送数据。单位:ms,默认0(没有延迟)
acks设置:

  • 0:不需要等待数据落盘应答;
  • 1:leader落盘后应答;
  • -1(all):leader和follower落盘后应答;

一、ApI调用

(1)、异步API:

import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class KafkaProducerTest {

    public static void main(String[] args){
        //配置
        Properties properties = new Properties();
        //连接,如果是集群,不需要全部都配置,只需要配置几个
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092,hadoop102:9092");
        //设置序列化方式
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        //创建连接对象
        KafkaProducer kafkaProducer = new KafkaProducer<>(properties);
        //发送数据--向first发送10条数据
        for (int i = 1; i <= 10; i++){
            kafkaProducer.send(new ProducerRecord("first","test1", "hello-kafka" + i));
        }
        //关闭资源
        kafkaProducer.close();
    }
}

image.png

(2)、带回调的异步API:

import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class KafkaProducerTest {

	public static void main(String[] args){
		//配置
		Properties properties = new Properties();
		//连接,如果是集群,不需要全部都配置,只需要配置几个
		properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.56.88:9092");
		//设置序列化方式
		properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
		properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
		//创建连接对象
		KafkaProducer kafkaProducer = new KafkaProducer<>(properties);
		//发送数据--向first发送10条数据
		for (int i = 1; i <= 10; i++){
			kafkaProducer.send(new ProducerRecord("first", "test1", "hello-kafka" + i), new Callback() {
				@Override
				public void onCompletion(RecordMetadata recordMetadata, Exception e) {
					if(e == null){
						System.out.println("topic:"+recordMetadata.topic()+";partition:"+ recordMetadata.partition());
					}
				}
			});
		}
		//关闭资源
		kafkaProducer.close();
	}
}

控制台输出:
image.png
image.png

(3)、同步API:

同步API只是比异步API多加了一个 .get()

import org.apache.kafka.clients.producer.*;
import java.util.Properties;
import java.util.concurrent.ExecutionException;

public class KafkaProducerTest {

	public static void main(String[] args) throws ExecutionException, InterruptedException {
		//配置
		Properties properties = new Properties();
		//连接,如果是集群,不需要全部都配置,只需要配置几个
		properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.56.88:9092");
		//设置序列化方式
		properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
		properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
		//创建连接对象
		KafkaProducer kafkaProducer = new KafkaProducer<>(properties);
		//发送数据--向first发送10条数据
		for (int i = 1; i <= 10; i++){
			kafkaProducer.send(new ProducerRecord("first", "test1", "同步API-发送数据" + i)).get();
		}
		//关闭资源
		kafkaProducer.close();
	}
}

image.png

(4)、解决本地无法连接虚拟机中kafka的问题

这个问题表现为,在虚拟机中,使用kafka-console-consumer.sh和kafka-console-producer.sh是可以正常连接和发送数据的。但是本地使用java代码,一直没有发送数据。
如果是集群,不要只启动一台测试!!!!

方案一:

如果本地代码是使用主机名连接,如properties.put("bootstrap.servers", "hadoop102:9092")。修改本地的ip主机名映射,如图,在hosts文件(win路径:C:\Windows\System32\drivers\etc)中添加
image.png

方案二

如果本地代码是使用ip地址连接,如properties.put("bootstrap.servers", "192.168.10.102:9092")。修改/opt/module/kafka_2.13-3.0.0/config/server.properties中的advertised.listeners配置,如图:
image.png

二、生产者分区

创建一个名为first的topic,设置3个分区,3个副本,如图:

image.png

(1)、好处:

  • 1.便于合理使用存储资源,每个Partition在一个Broker上存储,可以把海量的数据按照分区切割成一块一块数据存储在多台Broker上。合理控制分区的任务,可以实现负载均衡的效果。
  • 2.提高并行度,生产者可以以分区为单位发送数据,消费者可以以分区为单位消费数据

(2)、默认分区策略

  • 1.如果指定分区,发往指定分区
import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class KafkaProducerTest {

	public static void main(String[] args){
		//配置
		Properties properties = new Properties();
		//连接,如果是集群,不需要全部都配置,只需要配置几个
		properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092,hadoop103:9092");
		//设置序列化方式
		properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
		properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
		//创建连接对象
		KafkaProducer kafkaProducer = new KafkaProducer<>(properties);
		//发送数据--向first发送10条数据
		for (int i = 1; i <= 10; i++){
			//指定分支发送,发往二号分区
			kafkaProducer.send(new ProducerRecord("first",2, "key" + i, "hello-kafka" + i), new Callback() {
				@Override
				public void onCompletion(RecordMetadata recordMetadata, Exception e) {
					if(e == null){
						System.out.println("topic:"+recordMetadata.topic()+";partition:"+ recordMetadata.partition());
					}
				}
			});
		}
		//关闭资源
		kafkaProducer.close();
	}
}

image.png

  • 2.未指定分区,有key,用key的hash值对topic的partition数量取余,得到partition
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
public class KafkaProducerTest {
	public static void main(String[] args){
		//配置
		Properties properties = new Properties();
		//连接,如果是集群,不需要全部都配置,只需要配置几个
		properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092,hadoop103:9092");
		//设置序列化方式
		properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
		properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
		//创建连接对象
		KafkaProducer kafkaProducer = new KafkaProducer<>(properties);
		//发送数据--向first发送10条数据
		for (int i = 1; i <= 10; i++){
			//未指定分区,使用key的来决定分区
			kafkaProducer.send(new ProducerRecord("first","key" + i, "hello-kafka" + i), new Callback() {
				@Override
				public void onCompletion(RecordMetadata recordMetadata, Exception e) {
					if(e == null){
						System.out.println("topic:"+recordMetadata.topic()+";partition:"+ recordMetadata.partition());
					}
				}
			});
		}
		//关闭资源
		kafkaProducer.close();
	}
}

image.png

  • 3.没有指定分区,也没有key,使用粘性分区(一批数据,一次一个分区)。尽可能一直用一个分区,该分区的batch.size/linger.ms已到,再随机选择一个分区,且和上一次的分区不同
import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class KafkaProducerTest {

	public static void main(String[] args){
		//配置
		Properties properties = new Properties();
		//连接,如果是集群,不需要全部都配置,只需要配置几个
		properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092,hadoop103:9092");
		//设置序列化方式
		properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
		properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
		//创建连接对象
		KafkaProducer kafkaProducer = new KafkaProducer<>(properties);
		//发送数据--//粘性分区测试,需要数据量大一点才能看出效果
		for (int i = 1; i <= 500; i++){
			//不指定分区,也不指定key
			kafkaProducer.send(new ProducerRecord("first", "hello-kafka" + i), new Callback() {
				@Override
				public void onCompletion(RecordMetadata recordMetadata, Exception e) {
					if(e == null){
						System.out.println("topic:"+recordMetadata.topic()+";partition:"+ recordMetadata.partition());
					}
				}
			});
		}
		//关闭资源
		kafkaProducer.close();
	}
}

(3)、自定义分区策略

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import org.apache.kafka.common.PartitionInfo;

import java.util.List;
import java.util.Map;
public class MyPartitioner implements Partitioner {
	/**
	 *
	 * @param topic
	 * @param key 发送的key
	 * @param keyBytes
	 * @param value 发送的value
	 * @param valueBytes
	 * @param cluster
	 * @return
	 */
	@Override
	public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
		List<PartitionInfo> partitionInfos = cluster.availablePartitionsForTopic(topic);
		//分区数量
		int num = partitionInfos.size();
		//根据value与分区数求余的方式得到分区ID
		return Math.abs(value.hashCode()) % num;
	}

	@Override
	public void close() {

	}

	@Override
	public void configure(Map<String, ?> map) {

	}
}

import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class KafkaProducerTest {

	public static void main(String[] args){
		//配置
		Properties properties = new Properties();
		//连接,如果是集群,不需要全部都配置,只需要配置几个
		properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092,hadoop103:9092");
		//设置序列化方式
		properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
		properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
		//自定义分区
		properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, MyPartitioner.class);
		//创建连接对象
		KafkaProducer kafkaProducer = new KafkaProducer<>(properties);
		//发送数据--向first发送20条数据
		for (int i = 1; i <= 20; i++){
			//原本key一致,应该发往相同的分区,但是因为使用了自定义分区
            //使用自定义分区规则决定分区
			kafkaProducer.send(new ProducerRecord("first", "key","hello-kafka" + i), new Callback() {
				@Override
				public void onCompletion(RecordMetadata recordMetadata, Exception e) {
					if(e == null){
						System.out.println("topic:"+recordMetadata.topic()+";partition:"+ recordMetadata.partition());
					}
				}
			});
		}
		//关闭资源
		kafkaProducer.close();
	}
}

image.png

三.提高吞吐量

  • batch.size:批次大小,默认16。增加到32k
  • linger.ms: 等待时间,默认0(无延迟)。 修改为5-100ms
  • compression.type:压缩方式(gZip、snappy、Lz4、Zstd)。修改为snappy
  • buffer.memory:RecordAccumulator缓冲区大小,默认32m。修改为64m

四.数据可靠性

通过配置acks来实现不同的数据可靠性

(1)、acks(0)

生产者发送数据,不需要等到数据落盘应答。
数据可靠性:数据丢失

(2)、acks(1)

生产者发送数据,等待leader收到数据并落盘应答。
数据可靠性:比0可靠,但还是会丢数(leader接收到数据,落盘并响应,但是follower还没有同步数据并落盘,如果leader挂掉后,重新选出一个leader,但新leader没有对应数据)

(3)、acks(-1/all)

生产者发送数据,等待leader和follower全都收到数据并落盘应答
数据可靠性:需要有一定的条件(分区副本数 ≥ 2 + isr队列应答最小副本数 ≥ 2)

  • isr队列(in-sync replicas set):和leader保持同步的follower+leader集合(leader:0,isr:0,1,2)
  • ISR队列应答最小副本数:min.insync.replicas

解决(ack设置为-1/all时,某一个follower挂了,导致迟迟没有它的应答)
设置replica.log.time.max.ms参数,默认30ms。达到设置值,将follower踢出isr队列

(4)、总结

akc=0,很少使用
ack=1,一般用于传输日志,允许丢个别数据
ack=-1/all,用于可靠性要求高的场景:钱

akc设置数据可靠性传输效率
0
1中等中等
-1/all

五.数据重复

ack设置为(-1/all)时,follower同步完后,leader准备应答时,忽然挂了。follower成为leader后,重新接收数据,导致数据重复

(1)、至少一次(at least one)

akc=-1/allmin.insync.replice ≥ 2。保证数据不丢失,不保证数据不重复

(2)、最多一次(at most one)

ack = 0。保证数据不重复,不保证不丢失

(3)、精确一次(exactly ont)

幂等性(不管producer发多少次,broker都只有持久化一份数据)+事务+至少一次
幂等性参数:enable.idempotence=true(默认开启)

(4)、幂等判重标准

<PID,partition,SeqNumber>:幂等只能在单分区单会话内不重复
PID:kafka每次重启,都会分配一个新的PID
partition:分区号
Sequence Number:单调自增

(5)、完善幂等的PID问题

在使用事务功能时,自定义一个唯一的transactionalId。这样即使重启后,也可以根据transactionalId继续处理

六.事务原理

  • 开启事务,必须开启幂等性
  • 确认事务信息存储主体的分区(默认50个):transactionalId的hashCode值%50

七、数据有序

(1)、多分区有序

如果想要多分区有序,可以把所有数据都拉倒消费者中,然后排序

(2)、单分区有序

  • 1.x版本之前,通过设置max.in.flight.requests.per.connection = 1,不需要考虑是否开启幂等
  • 1.x版本之后,如果没有开启幂等,同1.x配置。 如果开启幂等,设置max.in.flight.requests.per.connection ≤ 5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值