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();
}
}
(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();
}
}
控制台输出:
(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();
}
}
(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)
中添加
方案二
如果本地代码是使用ip地址
连接,如properties.put("bootstrap.servers", "192.168.10.102:9092")
。修改/opt/module/kafka_2.13-3.0.0/config/server.properties
中的advertised.listeners
配置,如图:
二、生产者分区
创建一个名为first的topic,设置3个分区,3个副本,如图:
(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();
}
}
- 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();
}
}
- 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();
}
}
三.提高吞吐量
batch.size
:批次大小,默认16。增加到32klinger.ms
: 等待时间,默认0(无延迟)。 修改为5-100mscompression.type
:压缩方式(gZip、snappy、Lz4、Zstd)。修改为snappybuffer.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/all
,min.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