kafka操作1

用java代码向kafka异步发送消息

注意关闭linux虚拟机防火墙,保证主机能够ping通linux,修改运行代码的主机hosts文件

# win10hosts文件目录
C:\Windows\System32\drivers\etc
# 依赖
<dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>这里的版本对应kafka版本</version>
</dependency>
package com.thenema.kafka.producer;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;

/**
 * @author 29467
 * @date 2022/10/13 20:25
 */
public class CustomerProducer {
    public static void main(String[] args) {
        // properties文件
        Properties properties = new Properties();
        // 连接集群 bootstrap.server
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092, hadoop102:9092");

        // 选择对应的key和value序列化类型 key.serializer
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        // 创建kafka生产者对象
        KafkaProducer<String,String> kafkaProducer = new KafkaProducer<>(properties);

        // 发送数据
        for(int i = 0; i< 5; i++){
            kafkaProducer.send(new ProducerRecord<>("first","hello"+i));
        }

        // 关闭资源
        kafkaProducer.close();
    }
}

启动kafka的消费者打印
./kafka-console-consumer.sh --bootstrap-server hadoop101:9092 --topic first
运行代码可见消费者打印的消息
在这里插入图片描述

kafka回调异步发送

package com.thenema.kafka.producer;

import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;

/**
 * @author 29467
 * @date 2022/10/13 20:55
 */
public class CustomerProducerCallback {
    public static void main(String[] args) {
        // properties文件
        Properties properties = new Properties();
        // 连接集群 bootstrap.server
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092, hadoop102:9092");

        // 选择对应的key和value序列化类型 key.serializer
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        // 创建kafka生产者对象
        KafkaProducer<String,String> kafkaProducer = new KafkaProducer<>(properties);

        // 发送数据
        for(int i = 0; i< 5; i++){
            kafkaProducer.send(new ProducerRecord<>("first", "hello" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    if(e == null){
                        System.out.println("主题:"+recordMetadata.topic()+"分区:"+recordMetadata.partition());
                    }
                }
            });
        }

        // 关闭资源
        kafkaProducer.close();
    }
}

在这里插入图片描述
在这里插入图片描述
可见回调函数打印的结果在控制台

kafka同步发送

package com.thenema.kafka.producer;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;
import java.util.concurrent.ExecutionException;

/**
 * @author 29467
 * @date 2022/10/13 21:08
 */
public class CustomerProduceSync {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // properties文件
        Properties properties = new Properties();
        // 连接集群 bootstrap.server
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092, hadoop102:9092");

        // 选择对应的key和value序列化类型 key.serializer
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        // 创建kafka生产者对象
        KafkaProducer<String,String> kafkaProducer = new KafkaProducer<>(properties);

        // 发送数据
        for(int i = 0; i< 5; i++){
            kafkaProducer.send(new ProducerRecord<>("first","hello"+i)).get();
        }
        System.out.println("发送完毕");

        // 关闭资源
        kafkaProducer.close();
    }
}

分区

在这里插入图片描述

kafka分区好处

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

ProducerRecord类

  1. 指明partition的情况下,直接将指明的值作为partition的值,例如partition=0,所有的数据写入分区0
  2. 没有指明partition值但有key的情况下,将key的hash值与topic的partition数进行取余得到partition值。例如:key1的hash值为5,key2的hash值为6,topic的partition数为2,那么key1对应的value1写入1号分区,key2对应的value2写入0号分区。
  3. 既没有partition值又没有key值的情况下,kafka采用sticky partition(黏性分区器),会随机选择一个分区,并尽可能一直使用该分区,待该分区的batch已满或者已完成,kafka再随机一个分区进行使用。(和上一次的分区不同)。例如:第一次随机选择0号分区,等0号分区当前批次满了(默认16k)或者linger.ms设置的时间到了,kafka会再随机一个分区进行使用(如果还是0则会继续随机)。

自定义分区器

继承Partitioner类,重写partition方法

package com.thenema.kafka.producer;

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

import java.util.Map;

/**
 * @author 29467
 * @date 2022/10/14 15:19
 */
public class MyPartitioner implements Partitioner {
    @Override
    public int partition(String s, Object o, byte[] bytes, Object o1, byte[] bytes1, Cluster cluster) {
        // 获取数据
        String msgValue = o1.toString();

        int partition;

        if(msgValue.contains("thenema")){
            partition = 0;
        }else{
            partition = 1;
        }
        return partition;
    }

    @Override
    public void close() {

    }

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

    }
}

然后在调用的producer类中的properties添加使用

properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "com.thenema.kafka.producer.MyPartitioner");

生产者提高吞吐量

batch.size:批次大小,默认为16k
linger.ms:等待时间,修改为5-100ms
compression.type:压缩snappy
RecordAccumulator:缓冲区大小,修改为64m

供参考的java代码

package com.thenema.kafka.producer;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;

/**
 * @author 29467
 * @date 2022/10/14 16:00
 */
public class CustomerProducerParameters {
    public static void main(String[] args) {
        // 配置
        Properties properties = new Properties();
        // 连接kafka集群
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop101:9092, hadoop102:9092");

        // 序列化
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        // 缓冲区大小(33554432b,32mb)
        properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);

        // 批次大小(16kb)
        properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);

        // linger.ms(等待时间1ms)
        properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);

        // 压缩(gzip,snappy等等)
        properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
        
        // 创建生产者
        KafkaProducer<String,String> kafkaProducer = new KafkaProducer<>(properties);
        
		// 发送数据
		kafkaProducer.send(new ProducerRecord<>("first","I am the value"));
    }
}

数据的可靠性

ACK应答级别

0:生产者发送过来的数据,不需要等数据落盘应答
数据可靠性分析:丢数
1:生产者发送过来的数据,Leader收到数据后应答
数据可靠性分析:丢数
-1(all):生产者发送过来的数据,Leader和ISR队列里面的所有节点收齐数据后应答。
Leader维护了一个动态的in-sync replica set(ISR),意为和Leader保持同步的Follower+Leader集合(leader:0,isr:0,1,2)。
如果Follower长时间未向Leader发送通信请求或同步数据,则该Follower将被踢出ISR。该时间阈值由replica.lag.time.max.ms参数设定,默认为30s,例如2超时,(leader:0,isr:0,1)
这样就不用等长时间联系不上或者已经故障的节点。

数据可靠性分析

如果分区副本设置为1个,或者ISR里应答的最小副本数量(min.insync.replicas 默认为1)设置为1,和ack=1的效果是一样的,仍然有丢数的风险(leader:0,isr:0)
数据完全可靠条件=ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2

可靠性总结

acks=0,生产者发送过来的数据就不管了,可靠性差,效率高
acks=1,生产者发送过来的数据Leader应答,可靠性中等,效率中等。
acks=-1,生产者发送过来的数据Leader和ISR队列里面的所有Follower应答,可靠性高,效率低。
在生产环境中,acks=0很少使用,acks=1一般用于传输普通日志,允许丢失个别数据;acks=-1一般用于传输和钱相关的数据,对可靠性要求比较高的场景。

参考java代码
package com.thenema.kafka.producer;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;

/**
 * @author 29467
 * @date 2022/10/13 20:25
 */
public class CustomerProducerAcks {
    public static void main(String[] args) {
        // properties文件
        Properties properties = new Properties();
        // 连接集群 bootstrap.server
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092, hadoop102:9092");

        // 选择对应的key和value序列化类型 key.serializer
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        // acks
        properties.put(ProducerConfig.ACKS_CONFIG,"1");

        // 重试次数
        properties.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, 3);

        // 创建kafka生产者对象
        KafkaProducer<String,String> kafkaProducer = new KafkaProducer<>(properties);

        // 发送数据
        for(int i = 0; i< 5; i++){
            kafkaProducer.send(new ProducerRecord<>("first","hello"+i));
        }

        // 关闭资源
        kafkaProducer.close();
    }
}

kafka数据重复

至少一次:ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2
最多一次:ACK级别设置为0
至少一次可以保证数据不丢失,但是不能保证数据不重复
最多一次可以保证数据不重复,但是不能保证数据不丢失
精确一次:对于一些非常重要的信息,要求数据既不能重复,也不能丢失

幂等性

幂等性指Producer不论向Broker发送多少次重复数据,Broker端都只会持久化一条,保证了不重复。
精确一次 = 幂等性 + 至少一次(ack=-1 + 分区副本数>=2 + ISR最小副本数量>=2)
重复数据的判断标准:具有<PID, Partition, SeqNumber>相同主键的消息提交时,Broker只会持久化一条。其中PID是Kafka每次重启都会分配一个新的,Partition表示分区号,Sequence Number是单调自增的。
所以幂等性只能保证的是在单分区单会话内不重复。
使用幂等性:开启参数enable.idempotence默认为true,false为关闭

kafka事务原理

说明:开启事务,必须开启幂等性。
Producer在使用事务功能前,必须先自定义一个唯一的transactional.id。有了transactional.id,即使客户端挂了,他重启后也能继续处理未完成的事务。
java参考代码

package com.thenema.kafka.producer;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;

/**
 * @author 29467
 * @date 2022/10/13 20:25
 */
public class CustomerProducerTransactions {
    public static void main(String[] args) {
        // properties文件
        Properties properties = new Properties();
        // 连接集群 bootstrap.server
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092, hadoop102:9092");

        // 选择对应的key和value序列化类型 key.serializer
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"transactional_id_01");

        // 创建kafka生产者对象
        KafkaProducer<String,String> kafkaProducer = new KafkaProducer<>(properties);

        // 初始化
        kafkaProducer.initTransactions();
        // 开始事务
        kafkaProducer.beginTransaction();
        try{
            // 发送数据
            for(int i = 0; i< 5; i++){
                kafkaProducer.send(new ProducerRecord<>("first","hello"+i));
            }
            // 提交事务
            kafkaProducer.commitTransaction();
        }catch (Exception e){
            // 中止事务
            kafkaProducer.abortTransaction();
        }finally {
            // 结束事务
            kafkaProducer.close();
        }

        // 关闭资源
        kafkaProducer.close();
    }
}

数据有序

单分区:有序
多分区:分区与分区间无序

数据乱序

  1. kafka在1.x版本之前保证数据单分区有序,条件如下:max.in.flight.requests.per.connection=1(不需要考虑是否开启幂等性)
  2. kafka在1.x以后版本保证数据单分区有序,条件如下:开启幂等性:max.in.flight.requests.per.connection需要设置小于等于5。未开启幂等性:max.in.flight.requests.per.connection需要设置为1。
  3. 在kafka1.x以后,启用幂等性后,kafka服务端会缓存producer发来的最近的5个request的元数据,故无论如何,都可以保证最近5个request的数据都是有序的。

zookeeper中存储的kafka信息

使用kafka的bin目录下的zookeeper-shell.sh
连接zookeeper
zookeeper-shell.sh hadoop:2181
在zookeeper的服务端存储的Kafka相关信息

  1. /kafka/brokers/ids 记录有哪些服务器
  2. /kafka/brokers/topics/first/partitions/0/state 记录谁是Leader,有哪些服务器可用

kafka broker总体工作流程

  1. broker启动后在zk中注册
  2. broker向zookeeper注册controller,谁先注册谁是leader
  3. 由选举出来的controller监听brokers节点变化
  4. controller决定leader选举
  5. AR是kafka分区中所有副本的统称
  6. 选举规则:在isr中存活为前提,按照AR中排在前面的优先。
  7. controller将节点信息上传到zookeeper
  8. 其他controller从zk同步相关信息
  9. 假设broker中的leader挂了之后,Controller会监听到节点的变化,选举新的leader,在isr中存在的节点,按照AR中的顺序从前往后选。最后更新leader以及ISR。

服务器上下线

可以在zookeeper的kafka/brokers/ids下看到已连接的kafka

服役一个新节点

  1. 新建一个服务器,完成相关设置后连接zookeeper集群
  2. 创建一个要均衡的主题
vim topics-to-move.json
{
	"topics": [
		{"topic":"first"}
	],
	"version":1
}
  1. 生成一个负载均衡的计划bin/kafka-reassign-partitions.sh --bootstrap-server hadoop101:9092 --topics-to-move-json-filetopics-to-move.json --broker-list "0,1,2,3" --generate
  2. 创建副本存储计划(所有的副本存储在broker0、broker1、broker2、broker3中)vim increase-replication-factor.json
输入以下内容
{"version" :1, "partitions" : [ { "topic" : "first" , "partition" :0, "replicas" : [3,1], "log dirs" : [ "any" , "any"] } , { "topic" : "first", "partition" :1, "replicas": [0,2], "log_dirs":[ "any" , "any"] },{ "topic" : "first" , "partition":2, "replicas": [1,3], "log_dirs": [ "any", "any"]}, { "topic":"first" , "partition" : 3, "replicas": [2,0], "log_dirs" : [ "any" , "any"] },{"topic" : "first" , "partition" :4, "replicas": [3,2], "log dirs" : [ "any", "any"] } , { "topic" :"first", "partition" :5, "replicas": [0,3] , "log dirs": ["any" , "any"] },{ "topic" : "first" , "partition":6, "replicas": [1,0], "log_dirs" : [ "any" , "any"]}]}
  1. 执行副本储存计划bin/kafka-reassign-partitions.sh --bootstrap-server hadoop101:9092 --reassignment-json-file increase-replication-factor.json --execute
  2. 验证副本计划bin/kafka-ressign-partitions.sh --bootstarp-server hadoop101:9092 --reassignment-json-file increase-replication-factor.json --verify

退役旧的节点

  1. 执行负载均衡的操作
  2. 先按照退役一台节点,生成执行计划,然后按照退役时操作流程执行负载均衡
  3. 创建一个要负载均衡的主题
vim topics-to-move.json
{
	"topics": [
		{"topic":"first"}
	],
	"version":1
}
  1. 创建执行计划bin/kafka-reassign-partitions.sh --bootstrap-server hadoop101:9092 --topics-to-move-json-filetopics-to-move.json --broker-list "0,1,2" --generate
  2. 创建副本存储计划(所有的副本存储在broker0、broker1、broker2中)
vim increase-replication-factor.json
{"version":1, "partitions":[ { "topic":"first" , "partition":0, "replicas":[2,0,1], "log_dirs":[ "any" , "any" , "any"]}, { "topic": "first" , "partition":1, "replicas": [ 0,1,2], "log dirs":[ "any" , "any" , "any"] },{ "topic":"first" , "partition":2, "replicas":[1,2,0],"log dirs": [ "any", "any", "any"]}]}
  1. 执行副本存储计划bin/kafka-reassign-partitions.sh --bootstrap-server hadoop101:9092 --reassignment-json-file increase-replication-factor.json --execute
  2. 验证副本存储计划bin/kafka-ressign-partitions.sh --bootstarp-server hadoop101:9092 --reassignment-json-file increase-replication-factor.json --verify
  3. 执行停止命令
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值