Kafka 消息队列

一、Kafka 概述

Apache KafkaApache软件基金会:
   开源的流处理平台,该平台提供了消息的订阅与发布的消息队列。
   
用途:
	一般用作系统间解耦、异步通信、削峰填谷等作用。
	同时Kafka又提供了Kafka streaming插件包实现了实时在线流处理。
相比较一些专业的流处理框架不同,Kafka Streaming计算是运行在应用端,具有简单、入门要求低、部署方便等优点

二、消息队列

消息:系统间通信介质,存在形式多样化一般使用 字节序列化、Json、xml等
队列:先进先出 FIFO 数据结构

消息队列(Message Queue):用于系统间通讯的一个组件。
作用:
    1、削峰填谷,解决一些 并发处理、缓解数据库的压力等。
    2、系统间的解耦

消息中间件:ActiveMQ-单机、ZoreMQRocketMQKafka MQ Apache
削峰填谷:业务场景用户注册模块,在用户注册成功后,业务系统需要给用户发送一个通知短信,通知用户登录邮箱
去激活刚注册的用户信息
	由于短信通知和邮件发送是一个比较耗时的操作,所以在这里没必要将短信和邮件发送作为注册模块的流程,
使用Message Queue功能可以将改业务和主业务注册分离,这样可以缩短用户浏览器和服务建立的链接时间,
同时也能满足发送短信和邮件的业务

在这里插入图片描述

系统间的解耦:使用Message Queue作为缓冲队列实现业务系统服务间的解耦,也就意味着即使服务在运行期间库存系统宕机
也并不会影响订单系统的正常运行。

在这里插入图片描述

三、Kafka 架构

Kafka集群以Topic(内部分区)形式负责管理集群中的Record,每一个Record属于一个Topic。Topic以日志分区形式持久化Record。
在Kafka集群中,Topic的每一个分区都一定会有1个Borker担当该分区的Leader,其他的Broker担当该分区的follower(取决于分区的副本因子)。一旦对应分区的Lead宕机,kafka集群会给当前的分区指定新的Borker作为该分区的Leader。分区的Leader的选举是通过Zookeeper一些特性实现的。
Leader负责对应分区的读写操作,Follower负责数据备份操作。

在这里插入图片描述

四、Kafka集群安装与基本使用

-1、准备三台物理主机主机名分别是CentOSA|CentOSB|CentOSC
-2 、关闭防火墙
	[root@CentOSX ~]# service iptables stop
	iptables: Setting chains to policy ACCEPT: filter          [  OK  ]
	iptables: Flushing firewall rules:                         [  OK  ]
	iptables: Unloading modules:                               [  OK  ]
	[root@CentOSX ~]# chkconfig iptables off
-3、安装JDK,配置JAVA_HOME,JDK1.8+
	[root@CentOSX ~]# rpm -ivh jdk-8u171-linux-x64.rpm 
	[root@CentOSX ~]# ls -l /usr/java/
	total 4
	lrwxrwxrwx. 1 root root   16 Mar 26 00:56 default -> /usr/java/latest
	drwxr-xr-x. 9 root root 4096 Mar 26 00:56 jdk1.8.0_171-amd64
	lrwxrwxrwx. 1 root root   28 Mar 26 00:56 latest -> /usr/java/jdk1.8.0_171-amd64
	[root@CentOSX ~]# vi .bashrc 
	JAVA_HOME=/usr/java/latest
	PATH=$PATH:$JAVA_HOME/bin
	CLASSPATH=.
	export JAVA_HOME
	export PATH
	export CLASSPATH
	[root@CentOSX ~]# source ~/.bashrc # 加载环境变量
4、校准物理主机时钟,确保时间一致。
	[root@CentOSX ~]# yum install -y ntp
	[root@CentOSX ~]# service ntpd start # 启动时钟服务器
	Starting ntpd:                                             [  OK  ]
	[root@CentOSX ~]#  ntpdate time.windows.com # 更新本地时间
	11 Jun 13:46:16 ntpdate[1276]: the NTP socket is in use, exiting
	[root@CentOSX ~]# chkconfig ntpd on # 设置时间同步服务器开机自启动

5、配置主机名和IP的映射关系,这是必须的,因为Kafka默认只认主机名,
最后别忘记关闭防火墙。
	192.168.111.128 CentOSA
	192.168.111.129 CentOSB
	192.168.111.130 CentOSC
6、安装Zookeeper集群,并且保证Zookeeper能正常运行。
	[root@CentOSX ~]# tar -zxf zookeeper-3.4.6.tar.gz -C /usr/
	[root@CentOSX ~]# mkdir /root/zkdata

	[root@CentOSA ~]# echo 1 >> /root/zkdata/myid
	[root@CentOSB ~]# echo 2 >> /root/zkdata/myid
	[root@CentOSC ~]# echo 3 >> /root/zkdata/myid
	
	[root@CentOSX ~]# touch /usr/zookeeper-3.4.6/conf/zoo.cfg
	[root@CentOSX ~]# vim /usr/zookeeper-3.4.6/conf/zoo.cfg
	
	tickTime=2000
	dataDir=/root/zkdata
	clientPort=2181
	initLimit=5
	syncLimit=2
	
	server.1=CentOSA:2887:3887
	server.2=CentOSB:2887:3887
	server.3=CentOSC:2887:3887
	
	[root@CentOSX ~]# /usr/zookeeper-3.4.6/bin/zkServer.sh start zoo.cfg
	[root@CentOSX ~]# /usr/zookeeper-3.4.6/bin/zkServer.sh status zoo.cfg
	JMX enabled by default
	Using config: /usr/zookeeper-3.4.6/bin/../conf/zoo.cfg
	Mode: `follower|leader`
	[root@CentOSX ~]# jps
	5879 `QuorumPeerMain`
	7423 Jps
7、安装kakfa
	[root@hadoopX ~]# tar -zxf kafka_2.11-2.2.0.tgz -C /usr
	[root@hadoop2 ~]# vi /usr/kafka_2.11-2.2.0/config/server.properties
	############################# Server Basics #############################
	broker.id=[0|1|2]
	############################# Socket Server Settings #############################
	listeners=PLAINTEXT://CentOS[A|B|C]:9092
	############################# Log Basics #############################
	# A comma separated list of directories under which to store log files
	log.dirs=/usr/kafka-logs
	############################# Zookeeper #############################
	zookeeper.connect=CentOSA:2181,CentOSB:2181,CentOSC:2181

8、启动服务
	[root@CentOSX ~]# cd /usr/kafka_2.11-2.2.0/
	[root@CentOSX kafka_2.11-2.2.0]# ./bin/kafka-server-start.sh -daemon config/server.properties
	
9、测试环境
	 9.1、创建topic:
		[root@CentOSA kafka_2.11-2.2.0]# ./bin/kafka-topics.sh 
		                                --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 
		                                --create 
		                                --topic topic01 
		                                --partitions 3 
		                                --replication-factor 3
		这里partitions指定日志分区数目,replication-factor指定分区日志的副本因子。

   9.2、生产消息
	 [root@CentOSB kafka_2.11-2.2.0]# ./bin/kafka-console-producer.sh --broker-list CentOSA:9092,CentOSB:9092,CentOSC:9092 --topic topic01
	     > Hello Kafka
 
 9.3、消费者订阅topic01
	[root@CentOSA kafka_2.11-2.2.0]# ./bin/kafka-console-consumer.sh   --bootstrap-server CentOSA:9092,CentOSB:9092,CentOSC:9092 
	                               --topic topic01

	topic 分区,副本集描述: 



在这里插入图片描述

启动Kafka服务:
	清空zookeeper所有kafka的数据,删除除zookeeper以外所有节点
		[root zk] ./bin/zkCli.sh -server CentOS8:2181
		[root zk] ls /
		[root zk] rmr /cluster   ...

	删除kafka配置log.dirs目录下的所有数据
		[root@CentOSX ~]# rm -rf /usr/kafka-logs
  • 基本指令
[root@CentOSA kafka_2.11-2.2.0]# ./bin/kafka-topics.sh --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 --list	
[root@CentOSC kafka_2.11-2.2.0]# ./bin/kafka-topics.sh 
				--describe 
				--zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 
				--topic topic01

查看所有topic列表
	./bin/kafka-topics.sh --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 --list
查看指定topic信息
	./bin/kafka-topics.sh --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 --describe --topic t_cdr
控制台向topic生产数据
	./bin/kafka-console-producer.sh --broker-list CentOSA:2181,CentOSB:2181,CentOSC:2181 --topic t_cdr
控制台消费topic的数据
	./bin/kafka-console-consumer.sh --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 --topic t_cdr --from-beginning

五、Topic 和 日志

Kafka集群是通过日志形式持久化Topic中的Record
Record会根据分区策略计算得到的指定分区并存储到相应分区的文件中。

每个分区都是一个有序的,不可变的记录序列,不断附加到结构化的commit-log中。
每个分区文件会为Record进去分区的顺序进行编排。
每个分区中的Record都有一个id,该id标示了该record进入分区的先后顺序,通常将该id称为record在分区中的offset,偏移量从0开始,依次递增。

在这里插入图片描述

消息的时效性:
	Kafka集群持久地保留所有已发布的记录 - 无论它们是否已被消耗 - 使用可配置的保留时间。(默认七天168小时)
	例如,如果保留策略设置为2天,则在发布记录后的2天内,它可供使用,之后将被丢弃以释放空间。
	Kafka的性能在数据大小方面实际上是恒定的,因此长时间存储数据不是问题。

消费者消费规则:
事实上,基于每个消费者保留的唯一元数据是该消费者在日志中的偏移或位置。
这种offset由消费者控制:通常消费者在读取记录时会线性地增加其偏移量,

但事实上,由于消费者控制位置,它可以按照自己喜欢的任何顺序消费记录。
例如,消费者可以重置为较旧的偏移量以重新处理过去的数据,或者跳到最近的记录并从“现在”开始消费。

在这里插入图片描述
在这里插入图片描述

六、生产者

生产者负责发送Record到Kafka集群中的Topic中。在发布消息的时候,根据分区算法得到record被分配到的分区。

Record分区计算方案有三种:
	①如果用户没有指定分区,但是指定了key信息,生产者会根据hash(key)%分区数计算该Record所属分区信息,
	②如果生产者在发送消息的时候并没有key,也没有指定分区数,生产者会使用轮训策略选择分区信息。
	③如果指定了分区信息,就按照指定的分区信息选择对应的分区;当分区参数确定以后生产者会找到相应分区的Leader节点将Record记录写入到Topic日志存储分区中。

七、消费者

消费者对Topic中消息的消费方式是以Group为单位进行消费,Kafka服务器会自动的按照组内和组间对消费者消费的分区进行协调。

	- 组内(均分分区),确保一个组内的消费者不可重复消费分区中的数据,一般来说一个组内的消费者实例对的数目应该小于或者等于分区数目。

	- 组间(广播形式)消费,确保所有组都可以拿到当前Record。组间数据之间可以保证对数据的独立消费。

注意:分区数决定了体统吐数据的并行度。分区数是四,组内消费者大于四如为五,吐数据也只有四个消费者进行消费。如果如下图C4宕机,则组内消费分区进行消费重新分配。

在这里插入图片描述

八、Topic之DDl

1、创建Tocpic

[root@CentOSA kafka_2.11-2.2.0]# 
./bin/kafka-topics.sh --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 --create --topic topic01 --partitions 3 --replication-factor 3

--单节点
./bin/kafka-topics.sh --zookeeper CentOS8:2181 --create --topic topic01 --partitions 1 --replication-factor 1

2、Topic列表及详细信息

[root@CentOSA kafka_2.11-2.2.0]# 
./bin/kafka-topics.sh --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 --list

--单节点
./bin/kafka-topics.sh --zookeeper CentOS8:2181 --list
			
[root@CentOSC kafka_2.11-2.2.0]# 
./bin/kafka-topics.sh  --describe  --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181  --topic topic01
--单节点
./bin/kafka-topics.sh  --describe  --zookeeper CentOS8:2181  --topic topic01
	
						
Topic:topic01   PartitionCount:3        ReplicationFactor:3     Configs:
Topic: topic01  Partition: 0    Leader: 2       Replicas: 1,2,0 Isr: 2,0,1
Topic: topic01  Partition: 1    Leader: 2       Replicas: 2,0,1 Isr: 2,0,1
Topic: topic01  Partition: 2    Leader: 2       Replicas: 0,1,2 Isr: 2,0,1

3、删除Topic

[root@CentOSA kafka_2.11-2.2.0]# 
./bin/kafka-topics.sh  --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181  --delete  --topic topic01
--单节点
./bin/kafka-topics.sh  --zookeeper CentOS8:2181  --delete  --topic topic01
	
注意:如果用户没有配置delete.topic.enable=true,则Topic删除不起作用。

4、生产消费

--启动,停止
kafka-server-start.sh -daemon /opt/install/kafka_2.11-2.2.0/config/server.properties
kafka-server-stop.sh -daemon /opt/install/kafka_2.11-2.2.0/config/server.properties

--创建topic
./bin/kafka-topics.sh --zookeeper CentOS8:2181 --create --topic topic01 --partitions 1 --replication-factor 1

查看所有topic列表
./bin/kafka-topics.sh --zookeeper CentOS8:2181 --list

查看指定topic信息
./bin/kafka-topics.sh --zookeeper CentOS8:2181 --describe --topic topic01

控制台向topic生产数据
./bin/kafka-console-producer.sh --broker-list CentOS8:9092 --topic topic01

控制台消费topic的数据
./bin/kafka-console-consumer.sh --bootstrap-server CentOS8:9092 --topic topic01

九、Kafka API

1、引入Maven依赖

		<dependency>
		    <groupId>org.apache.kafka</groupId>
		    <artifactId>kafka-clients</artifactId>
		    <version>2.2.0</version>
		</dependency>
		
		<dependency>
		    <groupId>org.slf4j</groupId>
		    <artifactId>slf4j-api</artifactId>
		    <version>1.7.25</version>
		</dependency>
		<dependency>
		    <groupId>log4j</groupId>
		    <artifactId>log4j</artifactId>
		    <version>1.2.17</version>
		</dependency>
		
		<dependency>
		    <groupId>org.slf4j</groupId>
		    <artifactId>slf4j-log4j12</artifactId>
		    <version>1.7.5</version>
		</dependency>

2、引入log4j.properies

	### set log levels ###
	log4j.rootLogger = info,stdout 
	### 输出到控制台 ###
	log4j.appender.stdout = org.apache.log4j.ConsoleAppender
	log4j.appender.stdout.Target = System.out
	log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
	log4j.appender.stdout.layout.ConversionPattern =%p %d %c %m %n

3、在Windos配置主机名和IP映射关系

192.168.111.128 CentOSA
192.168.111.129 CentOSB
192.168.111.130 CentOSC

4、生产

 //0.配置生产者了连接属性
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");

        //1.创建Kafka生产者
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

        //2.构建ProducerRecord
        for (int i=0;i<10;i++){
            DecimalFormat decimalFormat = new DecimalFormat("000");
            ProducerRecord<String, String> record = new ProducerRecord<String, String>("topic04", decimalFormat.format(i), "value" + i);
            //3.发送消息
            producer.send(record);
        }
        //4.清空缓冲区
        producer.flush();
        //5.关闭生产者
        producer.close();

5、消费者

  //0.配置生产者了连接属性
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
        props.put(ConsumerConfig.GROUP_ID_CONFIG,"group1");

        //1.创建Kafka消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);

        //2.订阅topics
        consumer.subscribe(Arrays.asList("topic04"));
        //3.死循环读取消息
        while(true){
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
            if(records!=null && !records.isEmpty()){
                for (ConsumerRecord<String, String> record : records) {
                    int partition = record.partition();
                    long offset = record.offset();
                    long timestamp = record.timestamp();
                    String key = record.key();
                    String value = record.value();
                    System.out.println(partition+"\t"+offset+"\t"+timestamp+"\t"+key+"\t"+value);
                }
            }
        }

6、管理Topic

6、1创建topic

Properties props=new Properties();
props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
AdminClient client= KafkaAdminClient.create(props);

client.createTopics(Arrays.asList(new NewTopic("topic01",3, (short) 3)));

client.close();

6、2 查看topic信息

	Properties props=new Properties();
 props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
	AdminClient client= KafkaAdminClient.create(props);
	
	ListTopicsResult topicsResult = client.listTopics();
	for (String name : topicsResult.names().get()) {
	    System.out.println(name);
	}
	client.close();

6、3 获取topic详细信息

Properties props=new Properties();
props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
AdminClient client= KafkaAdminClient.create(props);

DescribeTopicsResult topicsResult = client.describeTopics(Arrays.asList("topic01"));
Map<String, KafkaFuture<TopicDescription>> values = topicsResult.values();
for (Map.Entry<String, KafkaFuture<TopicDescription>> entry : values.entrySet()) {
    System.out.println("key:"+entry.getKey());
    TopicDescription topicDescription = entry.getValue().get();
    System.out.println(topicDescription);
}
client.close();

6、4删除topic信息

Properties props=new Properties();
props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
AdminClient client= KafkaAdminClient.create(props);
client.deleteTopics(Arrays.asList("topic01"));

client.close();

十、消费者消费偏移量控制

10.1、手动设置消费偏移量

默认当用户使用subscribe方式订阅topic消息, 默认首次offset策略是latest。
	当用户第一次订阅topic在消费者订阅之前的数据是无法消费到 消息的。
	用户可以配置消费端参数auto.offset.reset控制kafka消费者行为。

原理:
 	消费端在使用consumer.poll数据的时候,底层会定时的向Kafka服务器提交消费的偏移量。默认消费端的offset是自动提交的,
 	默认配置:
 	enable.auto.commit	= true
    auto.commit.interval.ms	= 5000 //每隔5秒 提交一次
 	用户如果不希望自动提交偏移量可以配置 如下参数
	props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"false");

注意:如果用户使用subscribe方式订阅topic,在消费端必须指定group.id,这样Kafka才能够实现消费端负载均衡以及实现组内均分组件广播。

Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
props.put(ConsumerConfig.GROUP_ID_CONFIG,"group2");

props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");
 //默认值 latest  earliest 从topic的 初始record进行消费

10.2、subscribe 改自动提交为手动提交

 //0.配置生产者了连接属性
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
        props.put(ConsumerConfig.GROUP_ID_CONFIG,"group1");

        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);
        //1.创建Kafka消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);

        //2.订阅topics
        consumer.subscribe(Arrays.asList("topic03"));
        //3.死循环读取消息
        while(true){
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
            if(records!=null && !records.isEmpty()){
                Map<TopicPartition, OffsetAndMetadata> offsetMeta=new HashMap<>();
                for (ConsumerRecord<String, String> record : records) {
                    int partition = record.partition();
                    long offset = record.offset();
                    long timestamp = record.timestamp();
                    String key = record.key();
                    String value = record.value();
                    System.out.println(partition+"\t"+offset+"\t"+timestamp+"\t"+key+"\t"+value);//业务逻辑的处理 

                    TopicPartition part = new TopicPartition("topic03", partition);
                    OffsetAndMetadata oam=new OffsetAndMetadata(offset+1);//设置下一次读取起始位置
                    offsetMeta.put(part,oam);
                }
                consumer.commitSync(offsetMeta);
            }
        }


10.3、指定消费某一个分区

通过assign方式kafka对消费者的组管理策略失效。也就是说用户可以无需配置组ID

如果使用assign方式使用手动提交offset没有意义,因为程序在启动的时候可以通过seek指定对应分区的偏移量。因此在asssign模式下一般不做任何offset管理
 //0.配置生产者了连接属性
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
        
        //1.创建Kafka消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);

        //2.指定分区
        consumer.assign(Arrays.asList(new TopicPartition("topic04",1)));
        consumer.seek(new TopicPartition("topic04",1),1);
        //3.死循环读取消息
        while(true){
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
            if(records!=null && !records.isEmpty()){
                for (ConsumerRecord<String, String> record : records) {
                    int partition = record.partition();
                    long offset = record.offset();
                    long timestamp = record.timestamp();
                    String key = record.key();
                    String value = record.value();
                    System.out.println(partition+"\t"+offset+"\t"+timestamp+"\t"+key+"\t"+value);
                }
            }
        }

10.4、Kafka发送/接收Object

实现序列化和反序列化

public class ObjectCodec implements Deserializer<Object>, Serializer<Object> {
    @Override
    public void configure(Map<String, ?> configs, boolean isKey) {
        
    }

    @Override
    public byte[] serialize(String topic, Object data) {
        return SerializationUtils.serialize((Serializable) data);
    }

    @Override
    public Object deserialize(String topic, byte[] data) {
        return SerializationUtils.deserialize(data);
        // common-lang3 包
    }

    @Override
    public void close() {

    }
}

十一、生产者(发送消息)幂等性

幂等性:多次操作最终的影响等价与一次操作,这种操作为幂等性操作
	所有的读操作一定是幂等的.所有的写操作一定不是幂等的. 

生产者和broker默认有acks应答机制,
	如果当生产者发送完数据给broker之后如果没有在规定的时间内收到应答,
	生产者可以考虑重发数据.可以通过一下配置参数提升生产者的可靠性.
	
		acks = all // 0 无需应答  n 应答个数 -1所有都需要
		retries = 3 // 表示重试次数
		request.timeout.ms = 3000 //等待应答超时时间
		enable.idempotence = true //开启幂等性

//0.配置生产者了连接属性
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");

        props.put(ProducerConfig.ACKS_CONFIG,"all");//等待所有从机应答
        props.put(ProducerConfig.RETRIES_CONFIG,3);//重试3次
        props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG,3000);//等待3s应答  默认30s
        props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);//开启幂等性

        //1.创建Kafka生产者
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

        //2.构建ProducerRecord
        for (int i=15;i<20;i++){
            DecimalFormat decimalFormat = new DecimalFormat("000");
            User user = new User(i, "name" + i, i % 2 == 0);
            ProducerRecord<String, String> record = new ProducerRecord<String, String>("topic06", decimalFormat.format(i), "user"+i);
            //3.发送消息
            producer.send(record);
        }
        //4.清空缓冲区
        producer.flush();
        //5.关闭生产者
        producer.close();

十二、生产者批量发送

生产者会尝试缓冲record,实现批量发送,通过一下配置控制发送时机
记住如果开启可batch,一定在关闭producer之前需要flush。
	batch.size = 16384 //默认16KB 缓冲16kb数据本地
	linger.ms = 2000 //默认逗留时间 2s
	
  //0.配置生产者了连接属性
    Properties props = new Properties();
    props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
    props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
    props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");

    props.put(ProducerConfig.ACKS_CONFIG,"all");
    props.put(ProducerConfig.RETRIES_CONFIG,3);
    props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG,3000);
    props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);

    props.put(ProducerConfig.BATCH_SIZE_CONFIG,1024);//1kb缓冲区
    props.put(ProducerConfig.LINGER_MS_CONFIG,1000);//设置逗留时常


    //1.创建Kafka生产者
    KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

    //2.构建ProducerRecord
    for (int i=15;i<20;i++){
        DecimalFormat decimalFormat = new DecimalFormat("000");
        User user = new User(i, "name" + i, i % 2 == 0);
        ProducerRecord<String, String> record = new ProducerRecord<String, String>("topic06", decimalFormat.format(i), "user"+i);
        //3.发送消息
        producer.send(record);
    }
    //4.清空缓冲区
    producer.flush();
    //5.关闭生产者
    producer.close();

十三、生产者事务

只有生产者场景

kafka生产者事务指的是在发送多个数据的时候,保证多个Record记录发送的原子性。
	如果有一条发送失败就回退,但是需要注意在使用kafka事务的时候需要调整消费者的事务隔离级别设置为read_committed,
	因为kafka默认的事务隔离策略是read_uncommitted

开启事务
	transactional.id=transaction-1 //必须保证唯一
    enable.idempotence=true //开启kafka的幂等性
    
//1.创建Kafka生产者
        KafkaProducer<String, String> producer = buildKafkaProducer();

        //2.初始化事务和开启事务
        producer.initTransactions();
        producer.beginTransaction();
        try {
            for (int i=5;i<10;i++){
                DecimalFormat decimalFormat = new DecimalFormat("000");
                User user = new User(i, "name" + i, i % 2 == 0);
                ProducerRecord<String, String> record = new ProducerRecord<String, String>("topic07", decimalFormat.format(i), "user"+i);
                producer.send(record);
            }
            producer.flush();
            //3.提交事务]
            producer.commitTransaction();
        } catch (Exception e) {
            System.err.println(e.getMessage());
            //终止事务
            producer.abortTransaction();
        }
        //5.关闭生产者
        producer.close();
//0.配置生产者了连接属性
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");

        props.put(ProducerConfig.ACKS_CONFIG,"all");
        props.put(ProducerConfig.RETRIES_CONFIG,3);
        props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG,3000);
        props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);

        props.put(ProducerConfig.BATCH_SIZE_CONFIG,1024);//1kb缓冲区
        props.put(ProducerConfig.LINGER_MS_CONFIG,1000);//设置逗留时常

        //开启事务
        props.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"transaction-"+UUID.randomUUID().toString());
        return new KafkaProducer<String, String>(props);
        

十四、如何保证kafka不丢数据

1、使用 send(msg, callback)来发送消息
2、设置生产者acks机制 :
   acks=0
   0如果设置为零,那么生产者将完全不会管服务器是否收到消息(式具有最大的吞吐量,
一般建议直接配合 send(msg)使用)

   acks=1
  当leader接受到消息就会直接给客户端返回成功

   acks=all 或者 acks=-1
   当leader接受到消息,并同步到了一定数量的follower,才向生产者发生成功的消息。

3、设置生产者重试机制 
   配置生产者 发送消息的重试次数,一般为三。

十五、如何保证kafka不重复消费数据

  • kafka重复消费的问题,主要的原因还是在指定的时间内,没有进行kafka的位移提交,
  • 导致根据上一次的位移重新poll出新的数据,而这个数据就是上一次没有消费处理完全的(即没有进行offset提交的),这也是导致kafka重复数据的原因
	改为代码中就是,代码中会指定一个session-time来进行kafka数据的poll,供consumer进行消费处理..一次进行poll的数据量由maxpoolrecordsconfig来决定,这个数值就是决定了一次poll的数据量大小...

	手动提交offset时,当第一次poll出maxrecords条记录给程序时,程序需要进行处理(数据清理什么的),在session-time内,程序没有处理完这些数据,即还没有进行offset的commit,kafka又会根据上一次的offset进行poll出数据,其实这个数据就是我们刚刚没有消费处理完成的,这就导致了我们这次会有maxrecords条数据是重复的了

决这个问题的简单方法:
   就是调整session-time和
   max.poll.records.config的值,这个的均值,
   
	即最优化的方程式就是:
      maxpollrecordsconfig/代码处理单条数据时间=session-time.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值