一、安装&启动

安装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.msmax.poll.recordsmax.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(一) —— 基本概念及使用的更多相关文章

  1. 顶级Apache Kafka术语和概念

    1.卡夫卡术语 基本上,Kafka架构  包含很少的关键术语,如主题,制作人,消费者, 经纪人等等.要详细了解Apache Kafka,我们必须首先理解这些关键术语.因此,在本文“Kafka术语”中, ...

  2. 【kafka学习笔记】kafka的基本概念

    在了解了背景知识后,我们来整体看一下kafka的基本概念,这里不做深入讲解,只是初步了解一下. kafka的消息架构 注意这里不是设计的架构,只是为了方便理解,脑补的三层架构.从代码的实现来看,kaf ...

  3. Kafka 温故(二):Kafka的基本概念和结构

    一.Kafka中的核心概念 Producer: 特指消息的生产者Consumer :特指消息的消费者Consumer Group :消费者组,可以并行消费Topic中partition的消息Broke ...

  4. kafka系列 -- 基础概念

    kafka是一个分布式的.分区化.可复制提交的发布订阅消息系统 传统的消息传递方法包括两种: 排队:在队列中,一组用户可以从服务器中读取消息,每条消息都发送给其中一个人. 发布-订阅:在这个模型中,消 ...

  5. Kafka学习之(一)了解一下Kafka及关键概念和处理机制

    Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模小打的网站中所有动作流数据.优势 高吞吐量:非常普通的硬件Kafka也可以支持每秒100W的消息,即使在非常廉价的商用机器上也能做 ...

  6. Kafka的基本概念

    Kafka的前身是由LinkedIn开源的一款产品,2011年初开始开源,加入了 Apache 基金会,2012年从 Apache Incubator 毕业变成了 Apache 顶级开源项目. Top ...

  7. Kafka的基本概念与安装指南(单机+集群同步)

    最近在搞spark streaming,很自然的前端对接的就是kafka.不过在kafka的使用中还是遇到一些问题,比如mirrormaker莫名其妙的丢失数据[原因稍后再说],消费数据offset错 ...

  8. kafka学习笔记——基本概念与安装

    Kafka是一个开源的,轻量级的.分布式的.具有复制备份.基于zooKeeper协调管理的分布式消息系统. 它具备以下三个特性: 能够发布订阅流数据: 存储流数据时,提供相应的容错机制 当流数据到达时 ...

  9. Kafka 文档引言

    原文地址:https://kafka.apache.org/documentation.html#semantics 1.开始 1.1 引言 Kafka是一个分布式,分区队列,冗余备份的消息存储服务. ...

  10. 大数据组件原理总结-Hadoop、Hbase、Kafka、Zookeeper、Spark

    Hadoop原理 分为HDFS与Yarn两个部分.HDFS有Namenode和Datanode两个部分.每个节点占用一个电脑.Datanode定时向Namenode发送心跳包,心跳包中包含Datano ...

随机推荐

  1. Jmeter学习笔记(十六)——HTTP请求之content-type

    一.HTTP请求Content-Type 常见的媒体格式类型如下: text/html : HTML格式 text/plain :纯文本格式 text/xml : XML格式 image/gif :g ...

  2. UML类图的几种关系总结

    本文摘自:UML类图关系总结 在UML类图中,常见的有以下几种关系: 泛化(Generalization),  实现(Realization),关联(Association),聚合(Aggregati ...

  3. 用户在浏览器输入URL回车之后,浏览器都做了什么

    在直接列出执行的步骤之前先来普及几个知识,相信了解完这些知识之后会对前后端的交互有更深入的理解. 1.TCP连接 TCP:Transmission Control Protocol, 传输控制协议,是 ...

  4. mysql学习之基础篇01

    大概在一周前看了燕十八老师讲解的mysql数据库视频,也跟着学了一周,我就想把我这一周所学的知识跟大家分享一下:因为是第一次写博客,所以可能会写的很烂,请大家多多包涵.文章中有不对的地方还请大家指出来 ...

  5. cpio命令

    RPM包中文件提取 cpio命令主要有三种基本模式:"-o"模式指的是copy-out模式,就是把数据备份到文件库中:"-i"模式指的是copy-in模式,就是 ...

  6. 使用Cloudera Manager搭建Kudu环境

    使用Cloudera Manager搭建Kudu环境 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 1>.点击添加服务进入CM服务安装向导 2>.选择需要安装的kudu ...

  7. js对属性的操作

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  8. 大数据之路week07--day06 (Sqoop 在从HDFS中导出到关系型数据库时的一些问题)

    问题一: 在上传过程中遇到这种问题: ERROR tool.ExportTool: Encountered IOException running export job: java.io.IOExce ...

  9. 《你们都是魔鬼吗》第八次团队作业:第三天Alpha冲刺

    <你们都是魔鬼吗>第八次团队作业:Alpha冲刺 项目 内容 这个作业属于哪个课程 任课教师博客主页链接 这个作业的要求在哪里 作业链接地址 团队名称 你们都是魔鬼吗 作业学习目标 完成最 ...

  10. 《The One!团队》第八次作业:ALPHA冲刺(四)

    项目 内容 作业所属课程 所属课程 作业要求 作业要求 团队名称 < The One !> 作业学习目标 (1)掌握软件测试基础技术.(2)学习迭代式增量软件开发过程(Scrum) 第四天 ...