kafka
概念
消息:kafka的数据单元。
批次:一组消息,这些消息属于同一个主题和分区。
消息模式:有许多可选的数据结构,例如JSON,XML,Avro等。
主题:kafka的消息通过主题来分类。
分区:一个主题可以被分为若干个分区。
生产者:创建消息。
消费者:读取消息。
偏移量:另一种元数据,不断递增的整数值,创建消息时,kafka会将它加到消息中,在给定的分区中,每个消息的偏移量都是唯一的。
消费者群组:一个或多个消费者共同读取同一个主题,群组保证每个分区只能被一个消费者使用。
消费者对分区的所有权关系:消费者与分区之间的映射。
broker:一个独立的kafka服务器,接受来自生产者的消息,为消息设置偏移量,提交消息保存到磁盘。为消费者提供服务,对读取分区的请求做出响应,返回已经提交到磁盘上的消息。
集群:broker是集群的组成部分。
Zookeeper:kafka利用zookeeper保存相应的元数据信息,元数据信息包括代理节点信息、kafka集群信息、旧版消费者信息以及消费偏移量信息、主题信息、分区状态信息、分区副本分配方案信息、动态配置信息等。
ISR:即(zookeeper动态维护了一个)保存同步的副本列表。
安装和配置
安装
参考官方文档 快速开始:http://kafka.apache.org/documentation/#quickstart
配置
1、常规配置
broker.id:每一个broker的唯一标识符。
port:监听端口号(默认9092)。
zookeeper.connect :指定 保存broker元数据的Zookeeper地址。
log.dirs:把所有消息保存到磁盘上。指定存放日志片段的目录。
num.recovery.threads.per.data.dir:(服务器正常启动、服务器崩溃后重启、服务器正常关闭)使用可配置的线程池来处理日志片段。
auto.create.topics.enable: 自动创建主题。
2、主题
num.partitions :新创建的主题将包含多少个分区。
log.retention.ms : 根据时间来决定数据可以被保留多久(默认:168)。
log.retention.bytes :通过保留的消息字节数来判断消息。作用在每一个分区上,分区大小。
log.segments.bytes :日志片段的默认大小(默认是1GB)。
log.segment.ms : 日志片段关闭的时间参数。
message.max.bytes :限制单个消息的大小。(默认1MB)
生产者
创建生产者
1、三个必选属性
bootstrap.servers(host:port)
key.serializer
value.serializer
2、可选参数
详情请参考文档:http://kafka.apache.org/documentation/#producerconfigs
发送消息
1、发送并忘记
2、同步发送 producer.send().get();
3、异步发送 producer.send(record,new producerCallback())
producerCallback实现Callback接口,重写onCompletion 可处理异常。
序列化器
1、自定义序列化器,实现Serializer接口,重写serialize方法。
2、使用Avro序列化, Avro通过与语言无关的schema来定义。
import com.kafka.demo.bean.StockInfo;
import com.kafka.demo.mySelf.StockPartitioner;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import org.junit.jupiter.api.Test;
import java.text.DecimalFormat;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
/**
* 股票行情生产者
*/
public class QuotationProducer {
private static final Logger log = Logger.getLogger("QuotationProducer");
private static final int MSG_SIZE =100;
private static final String TOPIC = "stock-quotation";
private static final String BROKER_LIST = "localhost:9092";
private static KafkaProducer<String,String> producer = null;
static {
Properties config = initConfig();
producer = new KafkaProducer<String,String>(config);
}
private static Properties initConfig() {
final Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,BROKER_LIST);
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
//引入自定义分区器
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, StockPartitioner.class);
return properties;
}
private static StockInfo createQuotationInfo(){
final StockInfo stockInfo = new StockInfo();
Integer stockCode = 60100+ new Random().nextInt(10);
float random = (float) Math.random();
if(random /2 <0.5){
random = -random;
}
final DecimalFormat decimalFormat = new DecimalFormat(".00");
stockInfo.setCurrentPrice(Float.valueOf(decimalFormat.format(11+random)));
stockInfo.setPreClosePrice(11.80f);
stockInfo.setOpenPrice(11.5f);
stockInfo.setLowPrice(10.5f);
stockInfo.setHighPrice(12.5f);
stockInfo.setStockCode(stockCode.toString());
stockInfo.setTradeTime(System.currentTimeMillis());
stockInfo.setStockName("股票-"+stockCode);
return stockInfo;
}
/**
* 单线程生产者 异步发送消息 没有回调
*/
@Test
public void seedAsyncTest(){
ProducerRecord<String,String> record = null;
StockInfo stockInfo = null;
try {
int num =0;
for (int i=0;i<MSG_SIZE ;i++){
stockInfo = createQuotationInfo();
record = new ProducerRecord<String, String>(TOPIC,null,stockInfo.getTradeTime(),stockInfo.getStockCode(),stockInfo.toString());
//异步发送消息
producer.send(record);
if(num++ % 10 == 0){
Thread.sleep(2000L);
}
}
}catch (InterruptedException e){
log.info(e.getMessage());
}finally {
producer.close();
}
}
/**
* 单线程 异步回调
*/
@Test
public void seedAsyncCallbackTest(){
ProducerRecord<String,String> record = null;
StockInfo stockInfo = null;
try {
int num =0;
for (int i=0;i<MSG_SIZE ;i++){
stockInfo = createQuotationInfo();
record = new ProducerRecord<String, String>(TOPIC,null,stockInfo.getTradeTime(),stockInfo.getStockCode(),stockInfo.toString());
//异步发送消息,并执行回调函数
producer.send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if(e != null){
//发送异常记录异常信息
log.info("发送异常------->>>>>>>"+e.getMessage());
}
if(recordMetadata != null){
log.info("发送成功------->>>>>>>"+"偏移量:"+recordMetadata.offset()+ "分区:"+recordMetadata.partition());
}
}
});
if(num++ % 10 == 0){
Thread.sleep(2000L);
}
}
}catch (InterruptedException e){
log.info(e.getMessage());
}finally {
producer.close();
}
}
/**
* 多线程发送消息
*/
@Test
public void threadQuotationProducerTest(){
ProducerRecord<String,String> record = null;
StockInfo stockInfo = null;
//五个线程
final ExecutorService executor = Executors.newFixedThreadPool(5);
final long l = System.currentTimeMillis();
try {
for (int i=0;i<MSG_SIZE;i++){
stockInfo = createQuotationInfo();
record = new ProducerRecord<>(TOPIC, null, stockInfo.getTradeTime(), stockInfo.getStockCode(), stockInfo.toString());
executor.submit(new KafkaProducerThread(producer,record));
}
}catch (Exception e){
log.info("异常"+e.toString());
}finally {
producer.close();
executor.shutdown();
}
}
}
消费者
消费者和消费者群组
- kafka消费者从属于消费者群组,一个消费者群组的消费者订阅的是同一个主题,每个消费者接收主题一部分分区的消息。两个消费者群组之间互补影响。
- 一个topic 可以配置几个partition,produce发送的消息分发到不同的partition中,consumer接受数据的时候是按照group来接受,kafka确保每个partition只能同一个group中的同一个consumer消费,如果想要重复消费,那么需要其他的组来消费。
- 分区的所有权从一个消费者转移到另一个消费者,称为再均衡。(当消费者群组里新增或移除消费者时会自动触发再均衡)
- 消费者通过向被指派为群组协调器的borker(不同的群组可以有不用的协调器)发送 心跳 来维持它们和群组的从属关系以及它们对分区的所有权关系。
- 消息轮询是消费者API的核心。它会处理所有的细节,包括群组协调,分区再均衡,发送心跳和获取数据。
- 一个消费者使用一个线程。
- customer.poll(100); 它总是返回生产者写入Kafka,但还没有被消费者读取过的记录。很重要,消费者必须持续对kafka进行轮询;
- 消费者的配置查看官方文档;
- 更新分区当前位置的操作叫做提交。消息在分区里的位置叫做偏移量。
- 消费者是如何提交偏移量的?答:消费者往一个叫做_consumer_offset的特殊主题发送消息,消息里包含每个分区的偏移量。消费者发生崩溃或者有新的消费者加入群组,就会触发再均衡 。
- 提交方式
- 自动提交 :enable.auto.commit = true (基于时间间隔,auto.commit.interval.ms=5s(默认))
- 手动提交当前偏移量 :comsumer.commitSync() 将会提交由poll()返回的最新偏移量。
- 异步提交 consumer.commitAsync()
- 异步提交并处理其他事事情:consumer.commitAsync(new OffsetCommitCallback(){ });
- 同步和异步组合提交,轮询处使用异步提交,最后使用同步提交。
- 再均衡监听器
- 消费者再退出和进行分区再均衡之前,会做一些清理工作。
- 消费者调用订阅方法subscribe()的时候,传入一个ConsumerRebalanceListener实例。
- 实现两个方法
- onPartitionsRevoked() 会在再均衡开始之前和消费者停止消息之后被调用。如果在这里提交偏移量,下一个接管分区的消费者就知道该从哪里开始读取了。
- onPartitionsAssigned() 会在重新分配分区之后和消费者开始读取消息之前被调用。
@Test
public void subscribeTopic(){
consumer.subscribe(Arrays.asList("stock-quotation"), new ConsumerRebalanceListener() {
@Override
public void onPartitionsRevoked(Collection<TopicPartition> collection) {
//提交消费者已拉取的消息偏移量
consumer.commitSync();
}
@Override
public void onPartitionsAssigned(Collection<TopicPartition> collection) {
//重置消费者对各分区已消费的偏移量到已提交的偏移量
long committedOffset = -1;
//final Map<TopicPartition, OffsetAndMetadata> committed = consumer.committed((Set<TopicPartition>) collection);
for (TopicPartition topicPartition:collection){
//获取该分区已消费的偏移量
final long offset = consumer.committed(topicPartition).offset();
consumer.seek(topicPartition,offset+1);
}
}
});
}
- 从特定偏移量处开始处理记录
- seekToBeginning()
- seekToEnd()
- 使用实现ConsumerRebalanceListener类和seek()方法
- onPartitionsRevoked() 在处理完毕之后,将记录和偏移量插入数据库。然后在即将失去分区所有权之前提交事务。确保成功保存这些信息。
- onPartitionsAssigned() 在分配到新的分区的时候,重新定位这些记录。
- seek()查找保存在数据库中的偏移量。
- 消费者优雅退出循环
- 通过在另一个线程中调用consumer.wakeup()。
- 如果在主线程中,调用addShutDonwn(new Thread())使用consumer.wakeup()。
- 调用wakeup(), 会导致poll()方法抛出wakeupException。
- 关闭消费者
- 反序列化器
- 自定义反序列化器,实现Deserializer接口。
- Avro反序列化器
- 使用kafkaAvroDeserializer来反序列化Avro消息。
- schema.registry.url
- ConsumerRecords指定反序列化类型。
- rocord.value()使用。
- 独立消费者
- 一个消费者订阅主题(并加入消费者群组),或者自己分配分区,但是不能同时做这两件事。
- 消费者自己分配分区,不会触发再均衡。(consumer.assgin();)
//1.请求可用的分区
List<PartitionInfo> partitionInfos = consumer.partitionsFor("topic");
List<TopicPartition> topicPartitions = new ArrayList<>();
for (PartitionInfo partitionInfo : partitionInfos) {
TopicPartition topicPartition = new TopicPartition(partitionInfo.topic(), partitionInfo.partition());
topicPartitions.add(topicPartition);
}
//
consumer.assign(topicPartitions);
深入kafak
控制器
- 集群中第一个启动的borker通过在Zookeeper中创建一个临时节点 /controller 让自己成为控制器。
- 其余节点在控制器节点上创建Zookeeper watch对象,它们可以收到这个节点的变更通知。确保集群中一次只有一个控制器存在。
- ”脑裂“是指两个节点同时认为自己是当前的控制器,控制器使用epoh来避免”脑裂“。
复制(kafka架构的核心,它可以在个别节点失效时,仍然能保证Kafka的可用性和持久性)
- kafka使用主题来组织数据,每个主题被分为若干个分区,每个分区有多个副本。那些副本被保存在broker上,每个broker可以保存成百上千个属于不同主题和分区的副本。
- 副本(分为首领副本和跟随者副本):
- 首领副本:每个分区都有一个首领副本,为了保证一致性,所有生产者请求和消费者请求都会经过这个副本。
- 跟随者副本:跟随着副本不处理来自客户端的请求,它们唯一的任务就是从首领哪里复制消息,保证和首领一致的状态。
- 首领的另一个任务就是搞清楚哪些跟随着的状态和自己是一致的。(状态一致被称为同步副本)
- 同步副本和不同步的副本。(当首领失效时,只有同步副本才有可能晋升为首领副本)。
处理请求
- rokeer的大部分工作是处理客户端、分区副本和控制器发送给分区首领的请求。kafka提出了一个二进制协议(基于TCP);
- 标准消息头
- Request Type(API key)
- Request version(客户端请求的版本)
- Correlation ID (一个具有唯一性的数字,用于标识请求消息)
- Client ID (用于标识发送请求的客户端)
- 请求类型 (都是发送给分区的首领副本)
- 生产请求
- 生产者发送的请求,包含客户端要写入broker的消息。
- 消息被写入本地磁盘,它依赖于复制功能,来保证消息的持久性。
- 客户端怎么知道该往哪里发送请求呢?【元数据请求:这种请求包含了客户端感兴趣的主题列表,服务器的响应消息里指明了这些主题所包含的分区,每个分区都有哪些副本,以及哪个副本是首领。元数据请求可以发给任意一个broker,因为所有broker都缓存了这些信息】
- 获取请求
- 客户端发送请求,向broker请求主题分区里具有特定偏移量的消息;
- 消费者和跟随者副本需要从borker读取消息时发送的请求;
- 使用零复制技术向客户端发送消息,kafka直接把消息从文件(Linux文件系统缓存)发送到网络通道,不经过任何中间缓冲区。避免了字节复制,也不需要管理内存缓冲区,从而获得更好的性能。
- 下图分别为kafak处理请求和客户端路由请求
- 生产请求
- kafka的存储细节
- kafka的基本存储单元是分区。
- 保留数据是kafka的一个基本特性。kafka管理员为每个主题配置了数据保留期限(数据的时间或者大小)。
- kafka的消息和偏移量保存在文件里,保存在磁盘上的数据格式与从生产者发送过来或者发送给消费者的消息格式是一样的。
可靠的数据传输
kafka的复制机制和分区的多副本架构是kafka可靠性保证的核心。
kafka对可靠性保证的定义,消息只有被写入到所有同步副本之后,才被认为是已提交的。
影响broker消息存储的可靠性的3个配置
1、复制系数
replication.factor(主题级别的复制系数) :每个分区总共会被3个不同的broker复制3次。
default.replication.factor:(broker级别的复制系数)
2、不完全的首领选举
unclean.leader.election 只能在broker级别(实际上是在集群范围内)进行配置。为true,允许不 同步的副本成为首领。
3、最少同步副本
min.insync.replicas (主题和broker级别):当前同步副本数量小于最小同步副本数量时,broker 就会停止接收生产者的请求,接收生产者的请求会收到(NotEnoughPrlicasException异常),消费者仍然可以读取已有的数据。
在可靠性的系统里使用生产者
1、发送确认 生产者选择的确认模式
acks=0 :生产者通过网络把消息发送出去,就认为消息已成功写入kafka。
acks=1 : 首领在收到消息并把它写入到分区数据文件(不一定同步到磁盘上)时会返回确认或错误相应。
acks=all : 首领在返回确认或错误响应之前,会等待所有同步副本都收到消息。
2、配置生产者的重试参数
生产者向broker发送消息时,broker可以返回一个成功响应码或者一个错误响应码。错误响应码分为两种,一种是可重试错误,一种是不可重试错误。
在可靠的系统中使用消费者
消费者唯一要做的就是跟踪哪些消息是已经读取过的,哪些是没有读取过的,这是在读取消息时不丢失消息的关键。
1、已提交偏移量:消费者发送给kafka的偏移量,用于确认他已经收到并处理好的消息位置。
2、已提交消息:已经被写入所有同步副本并且对消费者可见的消息(与之前的已提交信息是不一样的)。
消费者可靠性配置
group.id:如果两个消费者具有相同的group.id,订阅同一个主题,那么每个消费者会分到主题分区的一个子集。
auto.offset.reset(在没有偏移量可提交时或者请求的偏移量在broker上不存在时,消费者会做些什么):
earlies:从分区的开始位置读取数据。
latest:从分区的末尾开始读取数据。
enable.auto.commit: 消费者基于任务调度自动提交偏移量。
auto.commit.interval.ms:如果选择了自动提交偏移量,通过该参数配置提交的频率。
显示提交偏移量
1、总是在处理完事件后再提交偏移量。
2、提交频度
3、确保对提交的偏移量心里有数。
4、再均衡
5、消费者可能需要重试。
(案例:30处理失败,31处理成功)
方式一:提交最后一个处理成功的偏移量,然后把还没有处理好的消息保存到缓冲区。
方式二:把错误写入到一个独立的主题,然后继续,一个独立的消费者群组负责从该主题上读取错误消息,并进行重试(类似其他消息系统的死信队列)。
6、消费者可能需要维护状态。
7、长时间处理。
使用线程池来处理数据,使用多个线程可以进行并行处理,加快处理速度。再把数据移交给线程池去处理之后,可以暂停消费者,然后保存轮询,但不会获取新数据,直到工作线程处理完成。
8、仅一次传递。
实现仅一次处理最简单且最常用的办法是把结果写入到一个支持唯一键的系统里。
构建数据管道
ETL表示提取-转换-加载
数据管道最重要的作用之一:解耦数据源和数据池
极力建议将kafka当成是一个支持数据集成(使用Connect)、应用集成(使用生产者和消费者)、和流式处理的平台。
1、Connect 是kafka的一部分,它为在kafka和外部数据存储系统之间移动数据提供了一种可靠且可伸缩的方式。它为连接器插件提供了一组API和一个运行时。
Connect运行:
(分布式)bin/connect-distributed.sh config/connect-distributed.properties
(单机)bin/connect-standalone.sh
bootstrap.servers(与Connect协同工作的broker服务器)
group.id(具有相同group.id的worker属于同一个Connect集群)
key.converter和value.converter
2、深入理解Connect
1、连接器和任务
连接器(负责):
决定需要运行多少个任务
按照任务来拆分数据复制
从worker进程获取任务配置并将其传递下去
任务(负责):
将数据移入或移出kafka
2、worker进程(负责):
worker进程是连接器和任务的“容器”
3、转化器和Connect的数据模型
源连接器只负责基于dataAPI生成数据对象,连接器通过DataAPI将数据返回给worker进程,worker进程使用指定的转化器将这些数据对象转换格式并保存到kafka,可用的转化器(Avro,JSON,String)
对于目标转换器,在从kafka中读取数据时,worker使用指定的转换器将各种格式的数据转换成DataAPI格式的对象,然后将它们传给目标转换器,目标转换器将它们插入到目标系统。
4、偏移量管理(保证一定程度上的行为一致性)
源连接器返回给worker进程的记录包含了一个逻辑分区和一个逻辑偏移量(源系统的分区和偏移量)。worker进程将其发给kafka,kafka确认记录保存成功,worker进程将偏移量保存下来。
目标连接器从kafka上读取包含了主题,分区和偏移量信息的记录,然后调用连接器的put()方法,该方法将记录保存到目标系统里,如果保存成功,连接器就会通过消费者客户端将偏移量提交到kafka里。
跨集群数据镜像
集群键的数据复制叫做镜像。
kafka内置的跨集群复制工具叫做MirrorMaker。
跨数据中心的一些架构原则
1、每个数据中心至少需要一个集群
2、每两个数据中心之间的数据复制要做到每个事件仅复制一次(除非出现错误重试)。
3、如果有可能,尽量从远程数据中心读取数据,而不是向远程数据中写入数据。
架构方案
1、Hub和Spoke架构
每个区域数据中心的数据都会被镜像到中央数据中心上,镜像进程会读取每一个区域数据中心的数据,并将它们重写生成到中心集群上。
2、双活架构
每两个数据中心之间都需要镜像,而且都是镜像。(会有冲突、延迟等问题)。
3、主备集群
4、延展集群
延展集群不需要镜像,延展集群使用kafka内置的复制机制在集群的broker之间同步数据。
延展集群是单个集群。(保证灾备站点与主站点保持100%的同步)。
MirroerMaker
-
用于两个数据中心之间镜像数据。
-
包含了一组消费者(又称为流),同属于一个群组,并从主题中读取数据。
-
MirrorMaker为每一个消费者分配一个线程,消费者从源集群的主题和分区上读取数据,然后通过公共生产者将数据发送到目标集群上。
-
每一个单独的MirrorMaker进程都有一个单独的生产者。
1、基本命令行参数
consumer.config :指定消费者的配置文件
pruducer.config :指定生产者的配置文件
new.cosumer : 消费者版本(建议0.9版本)
new.streams :一个流就是一个消费者,MirrorMaker将使用这些流填充同一个生产者。
whitelist : 正则表达式,代表了需要进行镜像的主题名字
2、根据不同的需求将MirrorMaker 运行在目标数据中心或源数据中心。
3、延迟监控、度量指标监控、canary
4、MirrorMaker调优
跨数据中心运行,对网络进行优化:
1、增加TCP的缓冲区大小
2、启用时间窗口自动伸缩
3、减少TCP慢启动时间,
生产者调优:
max.in.flight.requests.per.connection :不在乎消息的次序的时候,提升吞吐量。
linger.ms和batch.size :增加一些延迟来提升吞吐量 或配置更大的批次。
消费者调优:
range :(用于确定将哪些分区分配给哪个消费者的算法)最好改为round robin算法。
fetch.max.bytes 和fetch.min.bttes 和 fetch.max.wait :消费者可在每个请求里读取更多的数据
5、其他跨集群镜像方案
uReplicator
Replicator
管理kafka
主题操作
1、创建主题
kafka-topics.sh --zookeeper <zookeeper connect> --create --topic <topicName> --replication-factor 2 --partitions 8
(创建主题 包含8个分区,每个分区有2个副本) (自动化脚本 --if-not-exists)
2、增加分区
kafka-topics.sh --zookeeper <zookeeper connect> --alter --topic <topicName> --partitions 16
(分区数量增加到16)
若要减少分区数量,删除主题,重新创建
3、删除主题
kafka-topics.sh --zookeeper <zookeeper connect> --delete --topic <topicName>
4、列出所有主题
kafka-topics.sh --zookeeper <zookeeper connect> --list
5、列出主题详细信息
kafka-topics.sh --zookeeper <zookeeper connect> --describe
消费者群组
旧版本消费者 信息保存在Zookeeper中,新版本保存在broker上。
1、新版本列出并描述群组
kafka-consumer-groups.sh --new-consumer --bootstrap-server <Zookeeper Connect> --list
2、删除群组或删除主题的偏移量
kafka-consumer-groups.sh --zookeeper <zookeeper connect> --delete --group (--topic <topicname>)
在进行删除操作之前,需要先关闭消费者,或者不让他们读取即将被删除的主题。
偏移量管理
导出偏移量 :kafka-run-class.sh kafka.tools.ExportZkOffsets --zkconnect <zookeeper connect> --group <groupName> --output-file offsets
导入偏移量 (必须先关闭所有的消费者):kafka-run-class.sh kafka.tools.ImportZkOffsets --zkconnect <zookeeper connect> --input-file offsets
3、动态配置变更
1、覆盖主题的默认配置
kafka-configs.sh --zookeeper <Zookeeper connect> --alter --entity topics --entity-name <topicName> --add-config <key>=<value>
2、覆盖客户端的默认配置(对于kafka客户端来说,只能覆盖生产者配额和消费者配额参数)
可配置每个borker的生产速率或消费速率
kafka-configs.sh --zookeeper <Zookeeper connect> --alter --entity-type clients --entity-name <client ID> --add-config <key>=<value>
producer_bytes_rate (字节每秒)
consumer_bytes_rate
3、列出被覆盖的配置(只显示被覆盖的配置)
kafka-configs.sh --zookeeper <Zookeeper connect> --describe --entity-type topics --entity-name <topicName>
4、移除被覆盖的配置(恢复到默认)
kafka-configs.sh --zookeeper <zookeeper connect> --alter --entity-type topics --entity-name <topicName>
4、分区管理(一:重新选举首领,二:将分区分配给broker)
1、首选的首领选举
kafka-preferred-replica-election.sh --zookeeper <zookeeper connect>
2、修改分区副本
场景(一:主题分区在整个集群里的不均衡分布造成了集群负载的不均衡,二:broker离线造成分区不同步,三:新加入的broker需要从集群里获得负载)
(以下命令将产生两个JSON, 分别描述了当前的分区分配情况以及建议的分区分配方案)
kafka-reassign-partitions.sh --zookeeper <zookeeper connect> --generate --topics-to-move-json-file topics.json --broker-list 0,1
使用reassign.json执行建议的分区方案
kafka-reassign-partitions.sh --zookeeper <zookeeper connect> --execute --reassignment-json-file reassign.json
验证reassign.json文件里指定的分区重分配情况
kafka-reassign-partitions.sh --zookeeper <zookeeper connect> --verify --reassignment-json-file reassign.json
3、修改复制系数
4、转储日志片段
显示消息的概要信息 :kafka-run-class.sh kafka.tools.DumpLogSegments --files 0000000.log
显示消息的数据内容:kafka-run-class.sh kafka-tools.DumpLogSegments --files 0000000.log --print-data-log
也可用于验证日志片段的索引文件
kafka-run-class.sh kafka-tools.DumpLogSegments --files 0000000.index,0000000.log --index-sanity-check
--index-sanity-check 将会检查无用的索引
--verify-index-only 将会检查索引的匹配度
5、副本验证
kafka-replica-verification.sh --broker-list <broker.name:port ,....> --topic-white-list '副本name'
5、消费和生产
1、控制台消费者
kafka-console-consumer.sh --zookeeper <Zookeeper connect> --topic <topicname>
2、控制台生产者
kafka-console-producer.sh --broker.list <broker.name:port ,...> --topic <topicname>
监控kafka
流式处理
kafka两个流API,一个是Processor API ,一个是Streams DSL
使用DSL API 一般会先用StreamBuilder创建一个拓扑(一个有向图,包含了各个转换过程,将会被应用在流的事件上)。
使用拓扑创建一个kafkaStreams对象
1、流式处理的一些概念
1、时间概念(注意时区问题)
事件时间:所追踪事件的发生时间和记录的创建时间。
日志追加时间:事件保存到broker的时间
处理时间:应用程序在收到事件之后要对其进行处理的时间。
2、状态
本地状态或内部状态:只能被单个应用程序实例访问。
外部状态:使用外部的数据存储来维护。(应尽量避免使用外部存储)
3、时间窗口
大部分针对流的操作都是基于时间窗口的。
窗口的大小
窗口移动的频率
窗口的可更新时间多长
2、流式处理的设计模式
1、单个事件处理
map或filter模式
2、使用本地状态
聚合信息,特别是基于时间窗口进行聚合
3、多阶段处理和重分区
4、使用外部查找——流和表的连接
外部数据和流集成在一起
5、流与流的连接
6、乱序的shijian
7、重新处理
//配置KafkaStreams引擎
final Properties kfapro = new Properties();
kfapro.put(StreamsConfig.APPLICATION_ID_CONFIG,"wordcount");
kfapro.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,"127.0.0.1:9092");
kfapro.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName());
kfapro.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG,Serdes.String().getClass().getName());
//拓扑
final StreamsBuilder streamsBuilder = new StreamsBuilder();
final KStream<Object, Object> stream = streamsBuilder.stream("topic-name");
final KStream<Object, Object> the = stream.filter((key, value) -> (!value.equals("the"))).toTable().toStream();
the.to("topic-out");
//运行它
final KafkaStreams kafkaStreams = new KafkaStreams(streamsBuilder.build(), kfapro);
kafkaStreams.start();
Thread.sleep(3000);
kafkaStreams.close();
参考书籍《kafka权威指南》,《kafka入门与实践》