持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情
Producer 生产者发送消息流程如下:
-
首先指定消息发送到哪个
Topic -
选择一个
Topic的分区partitiion,默认是轮询来负载均衡也可以指定一个分区
key,根据key的hash值来分发到指定的分区。也可以自定义
partition来实现分区策略。 -
找到这个分区的
leader partition -
与所在机器的
Broker的socket建立通信 -
发送
Kafka自定义协议格式的请求(包含携带的消息)
发送 API 如下:
// 方式一:默认分区策略
producer.send(msg);
// 方式二:指定 key,根据 key 的 hash值去分发到某个分区上
producer.send(key, msg);
Producer 内部实现原理,如图:
(1)分区策略
Kafka的消息组织方式实际上是三级结构:主题 - 分区 - 消息。
所谓分区策略是决定生产者将消息发送到哪个分区的算法。
- 轮询策略
- 随机策略
- 按消息键保序策略
- 其他分区策略:自定义
发送 API 如下:
// 方式一:默认分区策略
producer.send(msg);
// 方式二:指定 key,根据 key 的 hash值去分发到某个分区上
producer.send(key, msg);
1. 轮询策略
也称
Round-robin策略,即顺序分配。Kafka Java生产者API默认提供的分区策略。
举个栗子:一个主题 Topic 下有 3 个分区:
- 第一条被发送到分区 0
- 第二条被发送到分区 1
- 第三条被发送到分区 2,以此类推。
- 当生产第 4 条消息时又会重新开始,即将其分配到分区 0
2. 随机策略
所谓随机就是我们随意地将消息放置到任意一个分区上:
List partitions = cluster.partitionsForTopic(topic);
return ThreadLocalRandom.current().nextInt(partitions.size());
3. 按消息键保序策略
一旦消息被定义了 Key,那么就可以保证同一个 Key 的所有消息都进入到相同的分区里面,由于每个分区下的消息处理都是有顺序的,
List partitions = cluster.partitionsForTopic(topic);
return Math.abs(key.hashCode()) % partitions.size();
问:如何保证消息全局顺序一致?
一个
Topic只有一个分区。
问:如何保证相关业务顺序处理?
- 发送方保证业务顺序性。
- 发送的消息根据
key,发送到同一个分区下。
4. 其他分区策略:自定义
根据 Broker 所在的 IP 地址实现定制化的分区策略:
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
return partitions.stream().filter(p -> isSouth(p.leader().host())).map(PartitionInfo::partition).findAny().get();
(2)压缩
为了减少网络开销以及磁盘存储,一般都会使用压缩。
压缩解压缩流程:Producer 端压缩 -> Broker 端保持 -> Consumer 端解压缩。
何时压缩?Producer 端
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("acks", "all");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
// 开启GZIP压缩
props.put("compression.type", "gzip");
Producer<String, String> producer = new KafkaProducer<>(props);
何时解压缩?Consumer 端
各种压缩算法对比:
- 吞吐量方面:
LZ4>Snappy>zstd和GZIP - 压缩比方面:
zstd>LZ4>GZIP>Snappy
最佳实践:
-
建议开启
zstd压缩。如果客户端机器
CPU资源有很多富余,强烈建议开启zstd压缩,这样能极大地节省网络资源消耗。
(3)TCP 建立连接
采用 TCP 而不是 HTTP 作为所有请求通信的底层协议的原因:
- 可以利用
TCP本身提供的一些高级功能,比如多路复用请求、同时轮询多个连接。 HTTP库在很多编程语言中都略显简陋。
Kafka 生产者使用流程:
- 构造生产者对象所需的参数对象
- 利用第 1 步的参数对象,创建
KafkaProducer对象实例 - 使用
KafkaProducer的send方法发送消息 - 调用
KafkaProducer的close方法关闭生产者并释放各种系统资源
Properties props = new Properties();
props.put("bootstrap.servers", "192.168.226.150:9092");
props.put("acks", "all");
props.put("retries", 1);
props.put("batch.size", 16384);
props.put("key.serializer", StringSerializer.class.getName());
props.put("value.serializer", StringSerializer.class.getName());
KafkaProducer<String, String> producer producer
= new KafkaProducer<String, String>(props);
producer.send(new ProducerRecord<String, String>(record.topic(),
record.value().toString()));
producer.close();
1)何时创建 TCP 连接?
-
在创建
KafkaProducer实例时:-
生产者应用会在后台创建并启动一个名为
Sender的线程,该Sender线程开始运行时,首先会创建与Broker的连接。 -
此时不知道要连接哪个
Broker,kafka会通过METADATA请求获取集群的元数据,连接所有的Broker
不建议把集群中所有的
Broker信息都配置到bootstrap.servers中,通常指定 3~4 台就足以了。 -
因为
Producer一旦连接到集群中的任一台Broker,就能拿到整个集群的Broker信息,故没必要为bootstrap.servers指定所有的Broker。
- 还可能在更新元数据后,或在消息发送时:
因为这两个地方并非总是创建
TCP连接。 当Producer更新了集群的元数据信息之后,如果发现与某些Broker当前没有连接,那么它就会创建一个TCP连接。 同样地,当要发送消息时,Producer发现尚不存在与目标Broker的连接,也会创建一个。
2)何时关闭 TCP 连接?
Producer 端关闭 TCP 连接的方式有两种:
- 用户主动关闭
// 1. 主动断开
producer.close();
// 2. 暴力删除
kill -9
kafka自动关闭
// 自动关闭跟 producer 端参数有关, 默认 9 分钟内没有任何请求通过,就会关闭
connection.max.idles.ms
// TCP 连接是在 Broker 端被关闭的,但这个关闭连接请求是客户端发起的,对 TCP 而言这是被动的关闭,被动关闭会产生大量的CLOSE_WAIT连接。
(4)发送给 Broker 遇到异常,则重试
发送不管是异步还是同步,都可能让你处理异常,常见异常如下:
-
LeaderNotAvailableException异常:leader分区不可用了-
产生原因:某个机器挂了,要等待选出新
leader分区,才能继续写入。平时重启
Broker进程,肯定会导致leader切换,会导致写入报错。 -
解决方案:重试发送即可。
-
-
NotControllerException异常:Controller所在Broker挂了- 产生原因:某个机器挂了,等待
Controller重新选举。 - 解决方案:重试发送即可。
- 产生原因:某个机器挂了,等待
-
NetworkException异常:网络异常- 产生原因:断网、网络分区、丢包等等。
- 解决方案:重试发送即可。
综上,如果重试几次之后还不行,那么就要交给人工处理。