Kafka(一) —— 基本概念及使用
一、安装&启动
安装Kafka(使用内置Zookeeper)
在Kafka官网下载安装包kafka_2.11-1.0.0.tgz
### 解压
tar zxvf kafka_2.11-1.0.0.tgz
#### 启动内置的zookeeper
.bin/zookeeper-server-start.sh ../config/zookeeper.properties
#### 启动kafka
./bin/kafka-server-start.sh ../config/server.properties
#### 启动kafka,在后台运行
./bin/kafka-server-start.sh -daemon ../config/server.properties
不使用内置的Zookeeper
Zk官方文档
https://zookeeper.apache.org/doc/current/index.html
二、终端命令
创建主题
./kafka-topics.sh --create --zookeeper localhost:2181 --topic test --partitions 1 --replication-factor 1
查看主题
./kafka-topics.sh --describe --zookeeper localhost:2181 --topic test
生产消息
./kafka-console-producer.sh --broker-list localhost:9092 --topic test
消费消息
./kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
三、生产
引入依赖
<!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>1.0.0</version>
</dependency>
生产消息
public class KafkaProducerDemo {
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
// ack = 0 producer不理睬broker的处理结果
// ack = all or -1 broker将消息写入本地日志,且ISR中副本也全部同步完,返回响应结果
// ack = 1 默认参数值,broker写入本地日志,无需等待ISR
props.put("acks", "-1");
props.put("retries", 3);
//单位byte,当batch满了,producer会发送batch中的消息,还要参考linger.ms参数
props.put("batch.size", 16384);
//控制消息发送的延时行为,让batch即使没满,也可以发送batch中的消息
props.put("linger.ms", 10);
//producer端缓存消息缓冲区的大小
props.put("buffer.memory", 33554432);
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("max.blocks.ms", "3000");
Producer<String, String> producer = new KafkaProducer<String, String>(props);
for (int i = 0; i < 100; i++) {
try {
producer.send(new ProducerRecord<String, String>("testfzj", Integer.toString(i), Integer.toString(i))).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
producer.close();
System.out.println("发送完成");
}
}
使用自定义拦截器
(1)发送的value前统一加一个时间戳
/**
* 增加时间戳 拦截器
* @author Michael Fang
* @since 2019-11-12
*/
public class TimeStampPrependerInterceptor implements ProducerInterceptor<String, String> {
/**
* 会创建一个新的Record
*
* @param producerRecord
* @return
*/
@Override
public ProducerRecord<String, String> onSend(ProducerRecord<String, String> producerRecord) {
return new ProducerRecord<String, String>(
producerRecord.topic(),
producerRecord.partition(),
producerRecord.timestamp(), producerRecord.key(),
System.currentTimeMillis() + "," + producerRecord.value().toString());
}
@Override
public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> map) {
}
}
(2)发送完成后进行成功统计
/**
* 发送后成功统计 拦截器
* @author Michael Fang
* @since 2019-11-12
*/
public class CounterInterceptor implements ProducerInterceptor {
private int errorCounter = 0;
private int successCounter = 0;
@Override
public ProducerRecord onSend(ProducerRecord producerRecord) {
return producerRecord;
}
/**
* 这两个参数不可能同时为空
* e = null 说明发送成功
* recordMetadata = null 说明发送失败
*
* @param recordMetadata
* @param e
*/
@Override
public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {
if (e == null) {
successCounter++;
} else {
errorCounter++;
}
}
@Override
public void close() {
//打印结果
System.out.println("Success sent: " + successCounter);
System.out.println("Failed sent: " + errorCounter);
}
@Override
public void configure(Map<String, ?> map) {
}
}
(3)Producer代码中增加属性配置,使其拦截器生效
Properties props = new Properties();
List<String> interceptors = new ArrayList<>();
interceptors.add("com.fonxian.kafka.TimeStampPrependerInterceptor");
interceptors.add("com.fonxian.kafka.CounterInterceptor");
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);
效果


使用自定义分区器
创建一个4个分区的主题
./kafka-topics.sh --create --zookeeper localhost:2181 --topic test-partition-1 --partitions 4 --replication-factor 1
产生的消息的key为(0-99),消息的key能被10整除的全部放到最后一个分区
/**
* 自定义分区器
*
* @author Michael Fang
* @since 2019-11-13
*/
public class GetServenPartitioner implements Partitioner {
private Random random;
@Override
public int partition(String topic, Object keyObj, byte[] keyBytes, Object valueObj, byte[] valueBytes1, Cluster cluster) {
String key = (String) keyObj;
//获取分区数
List<PartitionInfo> partitionInfoList = cluster.availablePartitionsForTopic(topic);
int partitionCount = partitionInfoList.size();
//最后一个分区的分区号
int lastPartition = partitionCount - 1;
//将能被10整除的key-value,发送到最后一个分区
if(Integer.valueOf(key) % 10 == 0){
return lastPartition;
}else{
return random.nextInt(lastPartition);
}
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> map) {
random = new Random();
}
}
结果
在broker上执行命令
./kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list localhost:9092 --topic test-partition-1
得到结果

四、消费
消费消息
public class KafkaConsumerDemo {
public static void main(String[] args) {
String topicName = "testfzj";
String gorupId = "test-group";
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", gorupId);
//是否自动提交
props.put("enable.auto.commit", "true");
// 自动提交的间隔
props.put("auto.commit.interval.ms", "1000");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList(topicName));
try {
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
consumer.close();
}
}
}
指定分区消费消息
// 见上面分区器的部分,定义4个分区的topic
// 使用分区器将能被10整除的key,放到最后一个分区
String topicName = "test-partition-1";
Properties props = new Properties();
//配置成从头开始消费
//earliest 从最早的位移开始消费
//latest 从最新处位移开始消费
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
//是否自动提交位移
//默认是true,自动提交
//设置成false,适合有“精确处理一次”语义的需求,用户自行处理位移。
props.put("enable.auto.commit", "true");
//获取最后一个分区
List<PartitionInfo> partitionInfoList = consumer.partitionsFor(topicName);
//指定最后一个分区
consumer.assign(Arrays.asList(new TopicPartition(topicName, partitionInfoList.size() - 1)));
try {
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
System.out.printf("topic = %s, partition = %d, offset = %d, key = %s, value = %s%n",record.topic(),record.partition(), record.offset(), record.key(), record.value());
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
consumer.close();
}
运行结果

__consumer_offsets


Rebalance
rebalance规定一个consumer group如何分配订阅topic所有分区,分配的这个过程就叫rebalance。
(1)谁来执行rebalance
组协调者coordinator执行rebalance操作,负责促成组内所有成员达成新的分区分配方案。
(2)何时触发
- 当组成员发生变化,新的consumer加入或consumer退出
- 订阅的topic数发生变更,例如使用正则匹配topic,突然加入新的topic
- 订阅的topic分区数发生变更
(3)分配策略
以8个partition(p1-p8),4个consumer(c1 - c4)举例。
- range策略
- 将分区划分成固定大小的分区段,依次分配给每个分区。例如将p1、p2分配给c1。
- round-robin策略
- 将分区按顺序排开,依次分配给各个consumer。例如将p1、p5分配给c1。
- sticky策略
(4)rebalance generation
为了隔离每次rebalance的数据,防止无效的offset提交。引入rebalance generation(届)的概念。
每次rebalance完成后,consumer都会升一届。当新的届的consumer产生,则consumer group不会接受旧的届提交的offset。
例如上一届的consumer因为网络延时等原因延时提交了offset,新的一届consumer已经产生,这时,上一届consume提交的offset,将会被consumer group拒绝,会出现ILLEGAL_GENERATION异常。
(5)调优案例:频繁rebalance
线上频繁进行rebalance,会降低consumer端的吞吐量。
原因是,consumer的处理逻辑过重,导致处理时间波动大,coordinator会经常认为某个consumer挂掉,进行rebalance操作。同时consumer又会重新申请加入group,又会引发rebalance操作。
调整request.timeout.ms、max.poll.records、max.poll.interval.ms来避免不必要的rebalance。
五、SpringBoot整合kafka
文档:https://docs.spring.io/spring-kafka/docs/2.3.3.RELEASE/reference/html/
引入依赖、配置
依赖
<!-- Inherit defaults from Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.59</version>
</dependency>
<!-- Add typical dependencies for a web application -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
配置
application.properties
server.port=9001
spring.application.name=kafka-demo
#============== kafka ===================
# 指定kafka 代理地址,可以多个
spring.kafka.bootstrap-servers=127.0.0.1:9092
#=============== provider =======================
spring.kafka.producer.retries=0
# 每次批量发送消息的数量
spring.kafka.producer.batch-size=16384
spring.kafka.producer.buffer-memory=33554432
# 指定消息key和消息体的编解码方式
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
#=============== consumer =======================
# 指定默认消费者group id
spring.kafka.consumer.group-id=user-log-group
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.enable-auto-commit=true
spring.kafka.consumer.auto-commit-interval=100
# 指定消息key和消息体的编解码方式
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
生产者
@SpringBootApplication
public class Application {
@Autowired
private KafkaTemplate kafkaTemplate;
private static final String TOPIC = "test-partition-1";
@PostConstruct
public void init() {
for (int i = 0; i < 10; i++) {
kafkaTemplate.send(TOPIC, "key:" + i, "value:" + i);
}
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
消费者
@Component
public class Consume {
@KafkaListener(topics = "test-partition-1")
public void consumer(ConsumerRecord consumerRecord){
Optional<Object> kafkaMassage = Optional.ofNullable(consumerRecord);
if(kafkaMassage.isPresent()){
ConsumerRecord record = (ConsumerRecord)kafkaMassage.get();
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
}
}
结果

六、常见问题
org.apache.kafka.common.errors.TimeoutException
使用Java客户端生产消息,出现此异常提示。
原因:外网访问,需要修改server.properties参数,将IP地址改为公网的IP地址,然后重启服务
advertised.listeners=PLAINTEXT://59.11.11.11:9092
可参考 https://www.cnblogs.com/snifferhu/p/5102629.html
参考文档
《Apache Kafka实战》
《深入理解Kafka:核心设计与实践原理》
springboot集成Kafka
Spring for Apache Kafka
Kafka(一) —— 基本概念及使用的更多相关文章
- 顶级Apache Kafka术语和概念
1.卡夫卡术语 基本上,Kafka架构 包含很少的关键术语,如主题,制作人,消费者, 经纪人等等.要详细了解Apache Kafka,我们必须首先理解这些关键术语.因此,在本文“Kafka术语”中, ...
- 【kafka学习笔记】kafka的基本概念
在了解了背景知识后,我们来整体看一下kafka的基本概念,这里不做深入讲解,只是初步了解一下. kafka的消息架构 注意这里不是设计的架构,只是为了方便理解,脑补的三层架构.从代码的实现来看,kaf ...
- Kafka 温故(二):Kafka的基本概念和结构
一.Kafka中的核心概念 Producer: 特指消息的生产者Consumer :特指消息的消费者Consumer Group :消费者组,可以并行消费Topic中partition的消息Broke ...
- kafka系列 -- 基础概念
kafka是一个分布式的.分区化.可复制提交的发布订阅消息系统 传统的消息传递方法包括两种: 排队:在队列中,一组用户可以从服务器中读取消息,每条消息都发送给其中一个人. 发布-订阅:在这个模型中,消 ...
- Kafka学习之(一)了解一下Kafka及关键概念和处理机制
Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模小打的网站中所有动作流数据.优势 高吞吐量:非常普通的硬件Kafka也可以支持每秒100W的消息,即使在非常廉价的商用机器上也能做 ...
- Kafka的基本概念
Kafka的前身是由LinkedIn开源的一款产品,2011年初开始开源,加入了 Apache 基金会,2012年从 Apache Incubator 毕业变成了 Apache 顶级开源项目. Top ...
- Kafka的基本概念与安装指南(单机+集群同步)
最近在搞spark streaming,很自然的前端对接的就是kafka.不过在kafka的使用中还是遇到一些问题,比如mirrormaker莫名其妙的丢失数据[原因稍后再说],消费数据offset错 ...
- kafka学习笔记——基本概念与安装
Kafka是一个开源的,轻量级的.分布式的.具有复制备份.基于zooKeeper协调管理的分布式消息系统. 它具备以下三个特性: 能够发布订阅流数据: 存储流数据时,提供相应的容错机制 当流数据到达时 ...
- Kafka 文档引言
原文地址:https://kafka.apache.org/documentation.html#semantics 1.开始 1.1 引言 Kafka是一个分布式,分区队列,冗余备份的消息存储服务. ...
- 大数据组件原理总结-Hadoop、Hbase、Kafka、Zookeeper、Spark
Hadoop原理 分为HDFS与Yarn两个部分.HDFS有Namenode和Datanode两个部分.每个节点占用一个电脑.Datanode定时向Namenode发送心跳包,心跳包中包含Datano ...
随机推荐
- Jmeter学习笔记(十六)——HTTP请求之content-type
一.HTTP请求Content-Type 常见的媒体格式类型如下: text/html : HTML格式 text/plain :纯文本格式 text/xml : XML格式 image/gif :g ...
- UML类图的几种关系总结
本文摘自:UML类图关系总结 在UML类图中,常见的有以下几种关系: 泛化(Generalization), 实现(Realization),关联(Association),聚合(Aggregati ...
- 用户在浏览器输入URL回车之后,浏览器都做了什么
在直接列出执行的步骤之前先来普及几个知识,相信了解完这些知识之后会对前后端的交互有更深入的理解. 1.TCP连接 TCP:Transmission Control Protocol, 传输控制协议,是 ...
- mysql学习之基础篇01
大概在一周前看了燕十八老师讲解的mysql数据库视频,也跟着学了一周,我就想把我这一周所学的知识跟大家分享一下:因为是第一次写博客,所以可能会写的很烂,请大家多多包涵.文章中有不对的地方还请大家指出来 ...
- cpio命令
RPM包中文件提取 cpio命令主要有三种基本模式:"-o"模式指的是copy-out模式,就是把数据备份到文件库中:"-i"模式指的是copy-in模式,就是 ...
- 使用Cloudera Manager搭建Kudu环境
使用Cloudera Manager搭建Kudu环境 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 1>.点击添加服务进入CM服务安装向导 2>.选择需要安装的kudu ...
- js对属性的操作
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- 大数据之路week07--day06 (Sqoop 在从HDFS中导出到关系型数据库时的一些问题)
问题一: 在上传过程中遇到这种问题: ERROR tool.ExportTool: Encountered IOException running export job: java.io.IOExce ...
- 《你们都是魔鬼吗》第八次团队作业:第三天Alpha冲刺
<你们都是魔鬼吗>第八次团队作业:Alpha冲刺 项目 内容 这个作业属于哪个课程 任课教师博客主页链接 这个作业的要求在哪里 作业链接地址 团队名称 你们都是魔鬼吗 作业学习目标 完成最 ...
- 《The One!团队》第八次作业:ALPHA冲刺(四)
项目 内容 作业所属课程 所属课程 作业要求 作业要求 团队名称 < The One !> 作业学习目标 (1)掌握软件测试基础技术.(2)学习迭代式增量软件开发过程(Scrum) 第四天 ...