文章目录
概述
Apache Kafka是一个分布式的流数据平台,代表三层含义:
- Publish/Subscribe: 消息队列系统 MQ(Message Queue)
- Process: 流数据的实时处理(Stream Process)
- Store: 流数据会以一种安全、容错冗余存储机制存放到分布式集群中
架构
Cluster
: kafka支持一到多个服务构成的分布式集群,每一个服务实例称为Broker
Topic
: 某一个分类的消息的集合,如:订单的topic、商品的topic等Partition
: 一个Topic有若干个分区(Partition)构成,分区的数量在创建Topic时手动指定Replication
: 分区副本,是Partition的冗余备份分区,当Partition不可用时,ZooKeeper会自动将Replication(Follower)分区升级为Partition(Leader)分区Offset
: 分区中的Record的位置标示,每一个消费者都会记录自己的消费位置(offset)
安装
- 修改网卡
# 修改网卡
vim /etc/sysconfig/network-scripts/ifcfg-ens37
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=static
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=ens37
DEVICE=ens37
ONBOOT=yes
IPADDR=192.168.24.11
# 重启网卡服务
service network restart
- 安装java环境
- 安装zookeeper
# 解压zk
tar -zxvf apache-zookeeper-3.6.0-bin.tar.gz -C /home/software
# 拷贝
cd /home/software/apache-zookeeper-3.6.0-bin/conf
cp zoo_sample.cfg zoo.cfg
# 修改配置文件,单机模式下默认配置即可
vim zoo.cfg
# 启动zk
bin/zkServer.sh start conf/zoo.cfg
QuorumPeerMain # zk进程
# 关闭zk
bin/zkServer.sh stop conf/zoo.cfg
- 安装Kafka
# 解压
tar -zxvf kafka_2.13-2.4.1.tgz -C /home/software/
# 修改配置文件,如果是集群安装同样需要这些配置
vim /home/software/kafka_2.13-2.4.1/config/server.properties
# 如果是集群,broker.id按照节点改变即可
broker.id=0
listeners=PLAINTEXT://:9092
log.dirs=/home/software/kafka_2.13-2.4.1/data
zookeeper.connect=zk01:2181,zk02:2181,zk03:2181
# 启动服务
bin/kafka-server-start.sh -daemon config/server.properties
Kafka # kafka进程
# 关闭服务
bin/kafka-server-stop.sh config/server.properties
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
基本使用
# 创建topic
bin/kafka-topics.sh --bootstrap-server 192.168.24.11:9092 --create --topic testTopic --partitions 1 --replication-factor 1
# 展示topic列表
bin/kafka-topics.sh --bootstrap-server 192.168.24.11:9092 --list
# 描述topic
bin/kafka-topics.sh --bootstrap-server 192.168.24.11:9092 --topic testTopic --describe
# 删除topic
bin/kafka-topics.sh --bootstrap-server 192.168.24.11:9092 --topic testTopic --delete
查看消费者组
# 查看消费者组列表
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list
# 查看消费者组消费情况
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group g1
生产者和消费者
# 生产者
bin/kafka-console-producer.sh --broker-list 192.168.24.11:9092 --topic testTopic
# 消费者
bin/kafka-console-consumer.sh --bootstrap-server 192.168.24.11:9092 --topic testTopic --from-beginning
Java API
Maven依赖
<!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.2.0</version>
</dependency>
生产者
package kafka.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
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.TimeUnit;
/**
* @ClassName ProducerTest
* @Description: 生产者
* @Author Lyb
* @Date 2020/7/6
**/
public class ProducerTest extends Thread {
private static String topic = "testTopic";
private int messageNumToSend = 1000;
public ProducerTest(){}
public ProducerTest(int messageNumToSend){
this.messageNumToSend = messageNumToSend;
}
public Producer createProducer(){
Properties prop = new Properties();
prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.24.11:9092");
prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class);
return new KafkaProducer<String,String>(prop);
}
@Override
public void run() {
Producer producer =createProducer();
int i = 0;
while(i<1000){
producer.send(new ProducerRecord<Integer, String>(topic, "message:"+i++));
try{
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedException e){
e.printStackTrace();
}
}
close(producer);
}
public void close(Producer producer){
producer.close();
}
public static void main(String[] args) {
ProducerTest producerTest = new ProducerTest(100);
producerTest.run();
}
}
消费者
package kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
/**
* @ClassName ConsumerTest
* @Description: 消费者
* @Author Lyb
* @Date 2020/7/6
**/
public class ConsumerTest extends Thread{
private String topic;
public ConsumerTest(String topic){
super();
this.topic =topic;
}
@Override
public void run() {
Properties prop = createPropertes();
prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
KafkaConsumer<String,String> consumer = new KafkaConsumer(prop);
consumer.subscribe(Collections.singletonList(topic));
while(true){
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(100));
for (ConsumerRecord<String, String> record : records) {
System.out.println(record.key()+" " + record.value() + " " + record.offset());
}
}
}
private Properties createPropertes(){
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.24.11:9092");
properties.put("zookeeper.connect", "192.168.24.11:2181");
properties.put("group.id", "g1");
return properties;
}
public static void main(String[] args) {
new ConsumerTest("testTopic").start();
}
}
高级部分
偏移量控制
Kafka消费者在订阅Topic时,会自动拉取Topic中新产生的数据。首次消费时使用默认的偏移量消费策略lastest(最新)
偏移量消费策略:
- lastest: 如果有已提交的offset,从已提交的offset之后消费消息。如果无提交的offset,从最后的offset之后消费数据
- earliest: 如果有已提交的offset,从已提交的offset之后消费消息。如果无提交的offset,从最早的offset消费消息
// 注意:此配置项 修改偏移量消费策略的默认行为
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");
Kafka消费者消费位置offset,默认采用自动提交的方式,将消费位置提交保存到特殊Topic__consumer_offsets
中
自动提交策略:
// 默认自动提交消费的位置offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true);
// 默认每隔5秒提交一次消费位置
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,5000);
通常情况需要手动提交消费位置:
为什么需要手动提交消费位置(offset)的原因?
原因:如果自动提交消费位置,有可能在进行业务处理时出现错误,会造成数据没有被正确处理。
手动提交消费位置,可以保证数据一定能够被完整的正确处理。
// 关闭消费位置offset的自动提交功能
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);
// 手动提交消费位置
consumer.commitSync();
消费方式
订阅
// 订阅(消费)Topic所有的分区
consumer.subscribe(Arrays.asList("t3"));
指定消费分区
// 指定消费Topic的特定分区
consumer.assign(Arrays.asList(new TopicPartition("testTopic",0)));
重置消费位置
consumer.assign(Arrays.asList(new TopicPartition("testTopic",0)));
// 重置消费位置
consumer.seek(new TopicPartition("testTopic",0),1);
消费者组
组内分发,组外广播
生产者的批量发送
kafka生产者产生的多条数据共享同一个连接,发送保存到Kafka集群,这种操作方式称为Batch(批处理)。
批处理相比于传统的发送方式,资源利用率更为高效,是一种比较常用的生产者优化策略。
使用方式
# 生产者方 添加如下配置项即可
# 两个条件 满足其一即可
batch.size = 16384Bytes 16kb// 缓冲区大小
linger.ms = 毫秒值 // 缓冲区中数据的驻留时长
// java代码
prop.put(ProducerConfig.BATCH_SIZE_CONFIG,16384);
prop.put(ProducerConfig.LINGER_MS_CONFIG,2000);
生产者幂等操作
幂等:
指的多次操作,影响结果是一致的,这种操作方式就被成为幂等操作。使用Kafka生产者幂等操作的原因,kafka生产者在重试发送生产数据时,多次重试操作只会在Kafka的分区队列的末尾写入一条记录
ack三种策略:
properties.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true); // 开启幂等操作支持
properties.put(ProducerConfig.ACKS_CONFIG,"all"); // ack时机 -1或者all 所有 1 leader 0 立即应答
properties.put(ProducerConfig.RETRIES_CONFIG,5); // 重复次数
properties.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 3000); // 请求超时时间
kafka的事务
数据库事务: 一个连接中多个操作不可分割,是一个整体,要么同时成功,同时失败。
Kafka的事务:类似于数据库事务,每一个事务操作都需要一个唯一的事务ID(Transaction-ID
),并且事务默认的隔离级别为READ_UNCOMMITTED
和READ_COMMITTED
生产者事务
生产者事务: Kakfka生产者生产的多条数据是一个整体,不可分割,要么同时写入要么同时放弃
要求
- kafka生产者提供唯一的事务ID
- 必须开启kafka的幂等性支持
事务操作
- 初始化事务
- 开启事务
- 正确操作 提交事务
- 操作失败 回滚事务
生产者API
package 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.UUID;
/**
* @ClassName ProducerTransaction
* @Description: 生产者事务
* @Author Lyb
* @Date 2020/7/14
**/
public class ProducerTransaction {
public static void main(String[] args) {
//1. 准备Kafka生产者配置信息
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.24.11:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class);
// 事务ID, 唯一不可重复
properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, UUID.randomUUID().toString());
// 开启幂等操作支持
properties.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);
properties.put(ProducerConfig.ACKS_CONFIG,"all"); // ack时机 -1或者all 所有 1 leader 0 立即应答
properties.put(ProducerConfig.RETRIES_CONFIG,5); // 重复次数
properties.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 3000); // 请求超时时间
//2. 创建kafka生产者对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 初始化事务
producer.initTransactions();
// 开启事务
producer.beginTransaction();
try {
//3. 生产记录并将其发布
for (int i = 50; i < 60; i++) {
if(i == 56) {
// 人为制造错误
int m = 1/0;
}
// key不为null 第一种策略
ProducerRecord<String, String> record = new ProducerRecord<>("testTopic", UUID.randomUUID().toString(),"Hello Kafka"+i);
// key为null 轮询策略
producer.send(record);
}
// 提交事务
producer.commitTransaction();
} catch (Exception e) {
e.printStackTrace();
// 取消事务
producer.abortTransaction();
} finally {
//4. 释放资源
producer.flush();
producer.close();
}
}
}
消费者API
// 其余代码 一致
// 修改消费者默认的事务隔离级别
properties.put(ConsumerConfig.ISOLATION_LEVEL_CONFIG,"read_committed");
生产消费并存事务(consume-transform-produce)
指消费和生产处于同一个事务环境中,要么消费生产同时成功,要么同时失败
要求
- kafka生产者提供唯一的事务ID
- 必须开启kafka的幂等性支持
- 关闭
offset
的自动提交功能 - 不能调用手动提交的方法,如:
consumer.commitSync();