zookeeper |
单节点 |
kafka |
localhost:9092 localhost:9093 localhost:9094 |
1.下载zookeeper安装包
wget https://dlcdn.apache.org/zookeeper/zookeeper-3.8.0/apache-zookeeper-3.8.0-bin.tar.gz
2.解压
tar -xzvf apache-zookeeper-3.8.0-bin.tar.gz
3.修改配置文件
mv zoo_simple.cfg zoo.cfg
vi zoo.cfg
dataDir=/Users/dongzhang/develop/apps/apache-zookeeper-3.8.0/datas
4.启动zookeeper
./zkServer.sh start
5.下载kafka安装包
wget https://archive.apache.org/dist/kafka/3.0.0/kafka_2.12-3.0.0.tgz
6.解压kafka安装包
tar -xzvf kafka_2.12-3.0.0.tgz
7.整理目录
cp -R kafka_2.12-3.0.0/ kafka_node1/
cp -R kafka_2.12-3.0.0/ kafka_node2/
cp -R kafka_2.12-3.0.0/ kafka_node3/
mv kafka_2.12-3.0.0/ kafka_client
8.修改环境变量
vi ~/.bash_profile
export KAFKA_HOME=/Users/dongzhang/develop/apps/kafka_cluster_3.0.0/kafka_client
export PATH=$PATH:$KAFKA_HOME/bin
source ~/.bash_profile
9.修改server.properties
node1-3:
broker.id=0~2
listeners=PLAINTEXT://:9092、9093、9094
log.dirs=/Users/dongzhang/develop/apps/kafka_cluster_3.0.0/kafka_node[1-3]/datas
10.启动kafka节点
kafka_node1/bin/kafka-server-start.sh -daemon kafka_node1/config/server.properties
kafka_node2/bin/kafka-server-start.sh -daemon kafka_node2/config/server.properties
kafka_node3/bin/kafka-server-start.sh -daemon kafka_node3/config/server.properties
11.测试
kafka-topics.sh --bootstrap-server localhost:9092 --list
#创建topic
kafka-topics.sh --bootstrap-server localhost:9092 --create --partitions 2 --replication-factor 3 --topic test
#启动生产者
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test
#启动消费者
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topi
c test
#查看topic详情
kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic test
Topic: test TopicId: L_qX7_mtRHSP3jsuj079uQ PartitionCount: 2 ReplicationFactor: 3 Configs: segment.bytes=1073741824
Topic: test Partition: 0 Leader: 1 Replicas: 1,0,2 Isr: 1,0,2
Topic: test Partition: 1 Leader: 0 Replicas: 0,2,1 Isr: 0,2,1
在发送数据的过程中涉及到两个线程,Producer主线程和Sender线程,在主线程中会为每一个分区创建一个双端队列RecordAccumulator,主线程将消息放到RecordAccumulator中,然后Sender线程不断的从RecordAccumulator中拉取消息,发送到Broker。
对topic进行分区有以下两个好处
默认分区策略 |
如果在发送消息的时候指定了分区号,就向指定的分区发送消息 |
如果没有指定分区号,但是指定的key,那么就按照key.hashCode() % 分区数选择分区 |
|
如果即没有指定分区号,也没有指定key,那么会随机选择一个分区发送消息,直到发送下一批次的数据时再重新选择 |
|
自定义分区策略 |
实现Partitioner接口,重写partition方法 |
bootstrap.servers |
生产者连接集群所需的broker地址清单 |
key.serializer 和 value.serializer |
指定发送消息的 key 和 value 的序列化类型。一定要写全类名 |
buffer.memory |
RecordAccumulator 缓冲区总大小,默认 32m |
batch.size |
缓冲区一批数据最大值,默认 16k。适当增加该值,可以提高吞吐量,但是如果该值设置太大,会导致数据传输延迟增加 |
linger.ms |
如果数据迟迟未达到 batch.size,sender 等待 linger.time之后就会发送数据。单位 ms,默认值是 0ms,表示没有延迟。生产环境建议该值大小为 5-100ms 之间 |
acks |
0:生产者发送过来的数据,不需要等数据落盘应答;1:生产者发送过来的数据,Leader 收到数据后应答;-1(all):生产者发送过来的数据,Leader+和 isr 队列里面的所有节点收齐数据后应答。默认值是-1,-1 和all 是等价的 |
max.in.flight.requests.per.connection |
允许最多没有返回 ack 的次数,默认为 5,开启幂等性要保证该值是 1-5 的数字 |
retries |
当消息发送出现错误的时候,系统会重发消息。retries 表示重试次数。默认是 int 最大值,2147483647。 如果设置了重试,还想保证消息的有序性,需要设置MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION=1否则在重试此失败消息的时候,其他的消息可能发送成功了 |
retry.backoff.ms |
两次重试之间的时间间隔,默认是 100ms |
enable.idempotence |
是否开启幂等性,默认 true,开启幂等性 |
compression.type |
生产者发送的所有数据的压缩方式。默认是 none,也就是不压缩。支持压缩类型:none、gzip、snappy、lz4 和 zstd |
主要有三个重要数据:brokers、partition、controller leader。
AR是所有的Partition副本id列表,OR是那些由于宕机或数据同步延迟过大被leader提出的partition的id列表,ISR是能够正常从partition leader同步数据的follower id,即AR=OR+ISR。
以在ISR列表中的节点是存活的为前提,按照AR列表中排在前面的优先。例如ISR列表为[0,1,2],AR列表为[1,0,2],那么久按照AR的顺序从ISR列表中选举新的partition作为Leader。
由于分区leader和follower之间存在数据延迟,如果leader节点出现了问题,并且正好有一部分数据还没有被follower完整同步,那么当controller选出新的leader时,剩余的各个follower可能就会出现不一致的情况,为了保证这种极端情况下数据的一致性,新的leader就会以HW为基准,把某些节点中多余的数据给删除掉,虽然会造成数据丢失,但能够保证数据的一致性。
从kafka 服务端的角度看会出现数据丢失,但在生产端如果我们把acks设置为-1,那么当出现上述情况时,就会到时producer发送消息失败,这样就会做后续的重发补偿等处理,保证数据不丢,但如果acks=0或1,就会出现数据丢失的情况。
正常情况下,Kafka本身就会把分区Leader均匀的分布到各个机器上,来保证每台机器的读写吞吐量基本均匀,但如果某些broker宕机,会导致某些partition leader过于集中到某些机器上,这会导致少数几台broker读写压力过大,其他宕机的broker重启之后都是作为partition follower存在的,读写压力比较低,这就造成了集群的负载不均衡。
Kafka提供了参数来Partition Leader自平衡机制
auto.leader.rebalance.enable |
默认是 true。 自动 Leader Partition 平衡。生产环 境中,leader 重选举的代价比较大,可能会带来 性能影响,建议设置为 false 关闭。 |
leader.imbalance.per.broker.percentage |
默认是 10%。每个 broker 允许的不平衡的 leader 的比率。如果每个 broker 超过了这个值,控制器 会触发 leader 的平衡。 |
leader.imbalance.check.interval.seconds |
默认值 300 秒。检查 leader 负载是否平衡的间隔 时间。 |
Kafka中的日志数据默认保持7天,可以通过调整以下参数来调整
log.retention.hours |
最低优先级小时,默认 7 天 |
log.retention.minutes |
分钟 |
log.retention.ms |
最高优先级毫秒 |
log.retention.check.interval.ms |
检查周期,默认 5 分钟 |
log.cleanup.policy = delete/compact |
delete,以 segment 中所有记录中的最大时间戳作为该文件时间戳,compact,对于相同key的不同value值,只保留最后一个版本 |
log.retention.bytes |
基于大小删除,与delete配合使用,默认关闭 |
replica.lag.time.max.ms |
ISR 中,如果 Follower 长时间未向 Leader 发送通信请求或同步数据,则该 Follower 将被踢出 ISR。该时间阈值,默认 30s |
auto.leader.rebalance.enable |
默认是 true。 自动 Leader Partition 平衡 |
leader.imbalance.per.broker.percentage |
默认是 10%。每个 broker 允许的不平衡的 leader的比率。如果每个 broker 超过了这个值,控制器会触发 leader 的平衡。 |
leader.imbalance.check.interval.seconds |
默认值 300 秒。检查 leader 负载是否平衡的间隔时间 |
log.segment.bytes |
Kafka 中 log 日志是分成一块块存储的,此配置是指 log 日志划分 成块的大小,默认值 1G |
log.index.interval.bytes |
默认 4kb,kafka 里面每当写入了 4kb 大小的日志(.log),然后就往 index 文件里面记录一个索引 |
log.retention.hours |
Kafka 中数据保存的时间,默认 7 天 |
log.retention.minutes |
Kafka 中数据保存的时间,分钟级别,默认关闭 |
log.retention.ms |
Kafka 中数据保存的时间,毫秒级别,默认关闭 |
log.retention.check.interval.ms |
检查数据是否保存超时的间隔,默认是 5 分钟 |
log.retention.bytes |
默认等于-1,表示无穷大。超过设置的所有日志总大小,删除最早的 segment |
log.cleanup.policy |
默认是 delete,表示所有数据启用删除策略;如果设置值为 compact,表示所有数据启用压缩策略 |
num.io.threads |
默认是 8。负责写磁盘的线程数。这个参数值要占总核数的 50% |
num.replica.fetchers |
副本拉取线程数,这个参数占总核数的 50%的 1/3 |
num.network.threads |
默认是 3。数据传输线程数,这个参数占总核数的50%的 2/3 |
log.flush.interval.messages |
强制页缓存刷写到磁盘的条数,默认是 long 的最大值,9223372036854775807。一般不建议修改,交给系统自己管理 |
log.flush.interval.ms |
每隔多久,刷数据到磁盘,默认是 null。一般不建议修改,交给系统自己管理 |
pull |
consumer采用从broker中主动拉取数据,kafka采用的就是这种方式 |
push |
服务端向consumer主动推送,kafka没有采用这种方式,因为这种方式下推送消息的速率很难适应所有的消费者 |
消费者组由多个consumer组成,形成一个消费者组的条件是所有消费者的group id相同。
消费者组在初始化的时候需要Coordinator这个组件协助,在每一个Broker上都有Coordinator这个组件,消费者组在初始化的时候会根据gourpId.hashCode() % 50来选择某一个Coordinator,这里的50实际上是__consumer_offset这个topic的分区数量,比如groupId的hashCode=1,1%50=1,那么__consumer_offset这个topic的1号分区在哪一个broker上就选择这个broker上的Coordinator组件进行协调,具体初始化流程如下:
当有多个消费者去消费多个分区的时候,在Coordinator的协助下,会将不同的partition分配给不同的消费者去处理,Kafka提供了以下几种分配策略:
可以通过参数partition.assignment.strategy来修改,而当某个消费者发生故障被Coordinator剔除或有新的消费者加入时,就会触发再平衡,也就是在这些消费者之间充分分配分区。
Range策略首先会对topic内的分区进行编号并排序,并对消费者按字母顺序排序,比如有7个分区和3个消费者,那么分区编号就是0~6,消费者编号就是C0~C2,然后通过 分区数/消费者数 来决定每个消费者应该消费几个分区,如果除不尽,那么前面几个消费者会多分到1个分区。这种分配方式的弊端在于,如果只针对一个topic而言某个消费者多消费一个分区的数据影响还不是很大,但如果有多个topic,那么针对每个topic 排在前面的消费者都会多消费分区,这样就会导致Consumer的负载不均衡。
RoundRobin是轮询策略,是把所有parition和所有consumer都列出来,然后按照hashCode进行排序,最后通过轮询算法给各个consumer分配partition。
RoundRobin是轮询策略,是把所有parition和所有consumer都列出来,然后按照hashCode进行排序,最后通过轮询算法给各个consumer分配partition。
Sticky可以理解为分配的结果带有一定的粘性,即在执行一次新的分配之前,会考虑上一次的分配结果,在同一消费者组内如果消费者出现问题,会尽量保持原有的分区分配不发生变化。
//修改分区分配策略
//org.apache.kafka.clients.consumer.RangeAssignor
//org.apache.kafka.clients.consumer.RoundRobinAssignor
//org.apache.kafka.clients.consumer.StickyAssignor
//org.apache.kafka.clients.consumer.CooperativeStickyAssignor
List strateies = new ArrayList<>();
strateies.add("org.apache.kafka.clients.consumer.RoundRobinAssignor");
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, strateies);
在kafka 0.9之前,消费端消费数据的offset维护在zookeeper上,但由于存在性能问题,从0.9版本以后就将offset存放在了kafka的一个指定的topic中(__consumer_offset),某个consumer如果想确认或提交自己的offset就去消费这个topic,内部消息的key=groupId + topic + 分区号,value=offset,卡夫卡内部每隔一段时间就是对这个topic执行compact,也就是每个值保留最新的条数据,即最新提交的offset值。
默认情况下,消费者自动维护offset的提交,但可以通过以下两个参数对提交offset的行为进行调整:
enable.auto.commit |
默认值为 true,消费者会自动周期性地向服务器提交偏移量。 |
auto.commit.interval.ms |
如果设置了 enable.auto.commit 的值为 true, 则该值定义了消费者偏移量向 Kafka 提交的频率,默认 5s |
如果enable.auto.commit=false,就需要手动提交事务,kafka提供了手动同步提交(commitSync)和手动异步提交(commitAsync)两种方式,两者的相同点是都会将本地数据的最大偏移量提交到__consumer_offset,不同点是同步提交会阻塞当前线程一直到提交成功为止,并且会自动进行失败重试,而异步提交方式没有失败重试机制,并且也不会阻塞当前线程的执行,所以可能会导致提交offset失败。
同时,Kafka提供了三种offset的消费模式:earlist | least | none
幂等性就是指不论Producer向Broker发送多少条重复属于,Consumer只会收到一条,保证不会重复消费,所以精准一次=幂等性 + 至少一次,Broker对数据进行重复判断的标准是相同时,就只会持久化一条数据,其中PID是Producer每次重启时都会重新分配,Partition为分区号,SeqNumber是单调递增的,所以,幂等性只能保证单分区单会话内不重复。
bootstrap.server |
向 Kafka 集群建立初始连接用到的 host/port 列表 |
key.deserializer value.deserializer |
指定接收消息的 key 和 value 的反序列化类型,一定要写全类名 |
group.id |
标记消费者所属的消费者组 |
enable.auto.commit |
默认值为 true,消费者会自动周期性地向服务器提交偏移量 |
auto.commit.interval.ms |
如果设置了 enable.auto.commit 的值为 true, 则该值定义了消费者偏移量向 Kafka 提交的频率,默认 5s |
auto.offset.reset |
当 Kafka 中没有初始偏移量或当前偏移量在服务器中不存在 (如,数据被删除了),该如何处理? earliest:自动重置偏 移量到最早的偏移量。 latest:默认,自动重置偏移量为最 新的偏移量。 none:如果消费组原来的(previous)偏移量 不存在,则向消费者抛异常。 anything:向消费者抛异常 |
offsets.topic.num.partitions |
__consumer_offsets 的分区数,默认是 50 个分区。 |
heartbeat.interval.ms |
Kafka 消费者和 coordinator 之间的心跳时间,默认 3s。该条目的值必须小于 session.timeout.ms ,也不应该高于session.timeout.ms 的 1/3 |
session.timeout.ms |
Kafka 消费者和 coordinator 之间连接超时时间,默认 45s。超过该值,该消费者被移除,消费者组执行再平衡 |
max.poll.interval.ms |
消费者处理消息的最大时长,默认是 5 分钟。超过该值,该消费者被移除,消费者组执行再平衡 |
fetch.min.bytes |
默认 1 个字节。消费者获取服务器端一批消息最小的字节数 |
fetch.max.wait.ms |
默认 500ms。如果没有从服务器端获取到一批数据的最小字节数。该时间到,仍然会返回数据 |
fetch.max.bytes |
默认 Default: 52428800(50 m)。消费者获取服务器端一批消息最大的字节数。如果服务器端一批次的数据大于该值(50m)仍然可以拉取回来这批数据,因此,这不是一个绝对最大值。一批次的大小受 message.max.bytes (broker config)or max.message.bytes (topic config)影响 |
max.poll.records |
一次 poll 拉取数据返回消息的最大条数,默认是 500 条 |
batch.size |
每一个批次的大小,默认为16kb,这个参数不适合调的过大,会增加消息延迟时间和增加网络压力 |
linger.ms |
当消息不够batch.size时的等待时间,默认不等待直接发送,这个参数可以根据数据产生的速率调整到100ms作用,保证一个批次内有足够的消息 |
compression.type |
消息压缩类型,如果文本消息体比较大,开启压缩效果会很明显 |
buffer.memory |
缓冲区的大小,也就是RecordAccumulater的大小,默认时32MB,可以调整到64MB。 |
{
"version":1,
"partitions":[
{"topic":"three","partition":0,"replicas":[0,1]},
{"topic":"three","partition":1,"replicas":[0,1]},
{"topic":"three","partition":2,"replicas":[1,0]},
{"topic":"three","partition":3,"replicas":[1,0]}
]
}
bin/kafka-reassign-partitions.sh -- bootstrap-server localhost:9092 --reassignment-json-file increase-replication-factor.json --execute
bin/kafka-reassign-partitions.sh -- bootstrap-server localhost:9092 --reassignment-json-file increase-replication-factor.json --verify
bin/kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic three
{
"version":1,
"partitions":[
{"topic":"four","partition":0,"replicas":[0,1,2]},
{"topic":"four","partition":1,"replicas":[0,1,2]},
{"topic":"four","partition":2,"replicas":[0,1,2]}
]
}
bin/kafka-reassign-partitions.sh -- bootstrap-server localhost:9092 --reassignment-json-file increase-replication-factor.json --execute
如果想完成Consumer端的精准一次性消费,那么需要Kafka消费端将消费过程和提交offset 过程做原子绑定。此时我们需要将Kafka的offset保存到支持事务的自定义介质,比如Mysql的事务。
但要根据下游的处理能力来定,如果超过了下游的处理能力,那么即使增加Kafka单批次的拉取量,也无法快速处理掉。
/**
* 同步发送
* 主线程会等待Sender成功将消息发送到partition 并收到ack后才继续发送下一个
* Callback会在Sender收到partition的ack后 回调
* */
public class SyncProducer {
public void asyncWithoutCallback() {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
KafkaProducer producer = new KafkaProducer(properties);
for (int i = 0;i < 5;i ++) {
producer.send(new ProducerRecord<>("test", "test-" + i));
}
producer.close();
}
public void asyncWithCallback() throws Exception {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
KafkaProducer producer = new KafkaProducer(properties);
for (int i = 0;i < 5000;i ++) {
producer.send(new ProducerRecord<>("queue", "test-" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(recordMetadata.topic() + " ");
stringBuilder.append(recordMetadata.partition() + " ");
stringBuilder.append(recordMetadata.offset() + " ");
stringBuilder.append(recordMetadata.timestamp() + " ");
System.out.println(stringBuilder.toString());
}
}).get();
}
producer.close();
}
public static void main(String[] args) throws Exception {
SyncProducer producer = new SyncProducer();
//producer.asyncWithoutCallback();
producer.asyncWithCallback();
}
}
/**
* 异步发送
* 在异步发送模式下,主线程将消息放入到RecordAccumulator中就返回了,不会等待
* Sender把数据成功发送到Partition中
*
* 而Callback会在Sender收到partition的ack后 回调
* */
public class AsyncProducer {
public void asyncWithoutCallback() {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
KafkaProducer producer = new KafkaProducer(properties);
for (int i = 0;i < 5000;i ++) {
producer.send(new ProducerRecord<>("test", "test-" + i));
}
producer.close();
}
public void asyncWithCallback() {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
KafkaProducer producer = new KafkaProducer(properties);
for (int i = 0;i < 5000;i ++) {
producer.send(new ProducerRecord<>("test", "test-" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(recordMetadata.topic() + " ");
stringBuilder.append(recordMetadata.partition() + " ");
stringBuilder.append(recordMetadata.offset() + " ");
stringBuilder.append(recordMetadata.timestamp() + " ");
System.out.println(stringBuilder.toString());
}
});
}
producer.close();
}
public static void main(String[] args) {
AsyncProducer producer = new AsyncProducer();
//producer.asyncWithoutCallback();
producer.asyncWithCallback();
}
}
/**
* 将消息发送到指定的分区.
* */
public class SpecificPartitionProducer {
public void sendToSpecificPartition() {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
KafkaProducer producer = new KafkaProducer(properties);
for (int i = 0;i < 5000;i ++) {
//将消息发送到0号分区
producer.send(new ProducerRecord<>("test", 0, "", "test-" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(recordMetadata.topic() + " ");
stringBuilder.append(recordMetadata.partition() + " ");
stringBuilder.append(recordMetadata.offset() + " ");
stringBuilder.append(recordMetadata.timestamp() + " ");
System.out.println(stringBuilder.toString());
}
});
}
producer.close();
}
public static void main(String[] args) {
SpecificPartitionProducer producer = new SpecificPartitionProducer();
producer.sendToSpecificPartition();
}
}
/**
* 将消息发送到指定的分区.
* */
public class SpecificKeyProducer {
public void sendToSpecificPartition() {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
KafkaProducer producer = new KafkaProducer(properties);
for (int i = 0;i < 5;i ++) {
//将消息发送到0号分区
//分别测试 a - 0 / b - 0 / c - 0 / f - 1
producer.send(new ProducerRecord<>("test", "f", "test-" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(recordMetadata.topic() + " ");
stringBuilder.append(recordMetadata.partition() + " ");
stringBuilder.append(recordMetadata.offset() + " ");
stringBuilder.append(recordMetadata.timestamp() + " ");
System.out.println(stringBuilder.toString());
}
});
}
producer.close();
}
public static void main(String[] args) {
SpecificKeyProducer producer = new SpecificKeyProducer();
producer.sendToSpecificPartition();
}
}
/**
* 自定义分区规则
* */
public class CustomPartitioner implements Partitioner {
/**
*
* */
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
String msgValue = value.toString();
int partition;
if (msgValue.contains("kafka")) {
partition = 1;
} else {
partition = 0;
}
return partition;
}
@Override
public void close() {
}
@Override
public void configure(Map map) {
}
}
/**
* 将消息发送到指定的分区.
* */
public class SpecificCustomPartitionerProducer {
public void sendToSpecificPartition() {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
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, CustomPartitioner.class.getName());
KafkaProducer producer = new KafkaProducer(properties);
for (int i = 0;i < 500;i ++) {
//将消息发送到0号分区
//分别测试 a - 0 / b - 0 / c - 0 / f - 1
String value = "test-";
if (i % 2 == 0) {
value = value + "kafka";
}else {
value = value + "kafka";
}
//producer.send(new ProducerRecord<>("queue", 0, "", value), new Callback() {
producer.send(new ProducerRecord<>("queue", value), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(recordMetadata.topic() + " ");
stringBuilder.append(recordMetadata.partition() + " ");
stringBuilder.append(recordMetadata.offset() + " ");
stringBuilder.append(recordMetadata.timestamp() + " ");
System.out.println(stringBuilder.toString());
}
});
}
producer.close();
}
public static void main(String[] args) {
SpecificCustomPartitionerProducer producer = new SpecificCustomPartitionerProducer();
producer.sendToSpecificPartition();
}
}
/**
* 优化生产端的吞吐量.
* */
public class OptimizeProducer {
public void send() throws Exception {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig. VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//默认时16k,够用了
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
//等待时间,默认时0
properties.put(ProducerConfig.LINGER_MS_CONFIG, 50);
//缓冲区大小,默认时32MB,调整到64MB
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 67108864);
//设置压缩类型
properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
KafkaProducer producer = new KafkaProducer(properties);
for (int i = 0;i < 100;i ++) {
String bigMsg = i + "日常生活: 手机号码查询 2021年放假安排 邮编查询 长途电话区号 货币汇率查询 家常菜谱大全 人民币存款利率表 下载地址转换 北京时间 大学查询 汽车车标大全 快递查询 国家地区查询 升降旗时间 常用电话号码 电费计算器 日期差计算 网速测试 数字大写转换 今日油价 个税计算器 国际天气预报 亲属关系计算 台湾邮编查询 (共31个) 占卜求签: 指纹运势查询 生男生女预测 预测吉凶 称骨算命 黄大仙灵签 六十四卦金钱课 观音灵签 诸葛神算 妈祖天后灵签 关帝灵签 吕祖灵签 车公灵签 王公祖仔灵签 月老灵签 文王神卦 灵棋经 二十八星宿算命 佛祖灵签 月老姻缘签 (共20个) 民俗文化: 老黄历 万年历 周公解梦大全 十二生肖 歇后语大全 百家姓 民间谚语 二十四节气表 历史朝代表 解密生日 名人名言名句大全 古兰经 基督教圣经 三字经 地母经 (共16个) 交通出行: 列车时刻表 全国各地车牌号查询 车辆违章查询 北京时间校准 机场三字码查询 实时交通路况 地铁线路图 车牌限行查询 火车票代售点 中国电子地图 交通标志 (共11个) 学习应用: 汉语字典 汉语词典 成语大全 在线翻译 计算器 在线输入法 圆周率 繁体字转换器 汉字拼音查询 摩尔斯电码 存储单位换算器 时间换算器 英文名 长度换算器 温度换算器 重量换算器 体积换算器 功率换算器 面积换算器 压力换算器 热量换算器 五笔字根表 区位码查询 笔画数查询 汉字部首查询 郑码编码查询 仓颉编码查询 中文电码查询 四角号码查询 英文缩写大全 组词大全 近义词 (共32个) 休闲娱乐: 号码吉凶预测 脑筋急转弯 中华谜语大全 竖排古文 火星文转换 QQ价值评估 QQ在线状态查询 外星体重 外星年龄 在线拆字 笑话大全 绕口令大全 (共15个) 站长工具: IP地址查询 密码强度检测 时间戳转换 ASCII码对照表 HTML/JS互转 BASE64加密解密 MD5加密解密 进程查询 在线编码解码 谷歌PR查询 搜狗SR查询 网站速度测试 二维码生成器 颜色代码表 HTML特殊符号 CSS在线解压缩 JS在线解压缩 HTML代码调试器 密码生成器 (共20个) 身体健康: 安全期计算器 预产期计算器 体质指数计算器 食物营养价值 粥谱大全 生星座宝宝 身高预测 血型与性格 ";
producer.send(new ProducerRecord("test", bigMsg), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(recordMetadata.topic() + " ");
stringBuilder.append(recordMetadata.partition() + " ");
stringBuilder.append(recordMetadata.offset() + " ");
stringBuilder.append(recordMetadata.timestamp() + " ");
System.out.println(stringBuilder.toString());
}
});
}
//如果发送的消息比较少,就需要sleep,否则,因为sender会等待50ms,但那时producer线程就
//退出了
TimeUnit.SECONDS.sleep(3);
}
public static void main(String[] args) throws Exception {
OptimizeProducer producer = new OptimizeProducer();
producer.send();
}
}
/**
* 保证数据可靠性的Producer
* acks=-1
* partition num >= 3
* */
public class DataReliabilityProducer {
public void send() throws Exception {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//高可用设置
properties.put(ProducerConfig.ACKS_CONFIG, "-1");
properties.put(ProducerConfig.RETRIES_CONFIG, 3);
KafkaProducer producer = new KafkaProducer(properties);
for (int i = 0;i < 100;i ++) {
String bigMsg = "message-";
producer.send(new ProducerRecord("test", bigMsg + i), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(recordMetadata.topic() + " ");
stringBuilder.append(recordMetadata.partition() + " ");
stringBuilder.append(recordMetadata.offset() + " ");
stringBuilder.append(recordMetadata.timestamp() + " ");
System.out.println(stringBuilder.toString());
}
});
}
TimeUnit.SECONDS.sleep(3);
}
public static void main(String[] args) throws Exception {
DataReliabilityProducer producer = new DataReliabilityProducer();
producer.send();
}
}
/**
* 精准发送一次
* */
public class OnlyOnceProducer {
public void send() {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig. VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.ACKS_CONFIG, "-1");
properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "transaction_id_0");
KafkaProducer producer = new KafkaProducer(properties);
//初始化事务
producer.initTransactions();
//开启事务
producer.beginTransaction();
try {
for (int i = 0; i < 100; i++) {
producer.send(new ProducerRecord<>("test", "test--" + i));
}
//int i = 10 / 0;
//提交事务
producer.commitTransaction();
}catch (Exception ex) {
System.out.println("发送异常,终止事务");
//终止事务
producer.abortTransaction();
} finally {
producer.close();
}
}
public static void main(String[] args) {
OnlyOnceProducer producer = new OnlyOnceProducer();
producer.send();
}
}
/**
* 独立消费者.
* */
public class SingleConsumer {
public void pull() {
//配置属性
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "single_consumer_group");
KafkaConsumer consumer = new KafkaConsumer(properties);
List topics = Arrays.asList("queue");
consumer.subscribe(topics);
//拉取数据
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord record : records) {
System.out.println(record);
}
}
}
public static void main(String[] args) {
SingleConsumer consumer = new SingleConsumer();
consumer.pull();
}
}
public class Consumer1 {
public void pull() {
//配置属性
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "single_consumer_group");
//修改分区分配策略
//org.apache.kafka.clients.consumer.RangeAssignor
//org.apache.kafka.clients.consumer.RoundRobinAssignor
//org.apache.kafka.clients.consumer.StickyAssignor
//org.apache.kafka.clients.consumer.CooperativeStickyAssignor
List strateies = new ArrayList<>();
strateies.add("org.apache.kafka.clients.consumer.RoundRobinAssignor");
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, strateies);
KafkaConsumer consumer = new KafkaConsumer(properties);
List topics = Arrays.asList("queue");
consumer.subscribe(topics);
//拉取数据
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord record : records) {
System.out.println(record);
}
}
}
public static void main(String[] args) {
Consumer1 consumer = new Consumer1();
consumer.pull();
}
}
public class Consumer2 {
public void pull() {
//配置属性
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "single_consumer_group");
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, "org.apache.kafka.clients.consumer.RoundRobinAssignor");
KafkaConsumer consumer = new KafkaConsumer(properties);
List topics = Arrays.asList("queue");
consumer.subscribe(topics);
//拉取数据
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord record : records) {
System.out.println(record);
}
}
}
public static void main(String[] args) {
Consumer2 consumer = new Consumer2();
consumer.pull();
}
}
public class Consumer3 {
public void pull() {
//配置属性
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "single_consumer_group");
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, "org.apache.kafka.clients.consumer.RoundRobinAssignor");
KafkaConsumer consumer = new KafkaConsumer(properties);
List topics = Arrays.asList("queue");
consumer.subscribe(topics);
//拉取数据
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord record : records) {
System.out.println(record);
}
}
}
public static void main(String[] args) {
Consumer3 consumer = new Consumer3();
consumer.pull();
}
}
/**
* 手动提交offset.
* @author dongzhang
* */
public class ManualCommitConsumer {
public void run() {
//配置属性
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "manual_commit_group");
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000);
KafkaConsumer consumer = new KafkaConsumer(properties);
List topics = Arrays.asList("queue");
consumer.subscribe(topics);
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord record : records) {
System.out.println(record);
}
//同步提交
consumer.commitAsync();
//异步提交
//consumer.commitAsync();
}
}
public static void main(String[] args) {
ManualCommitConsumer consumer = new ManualCommitConsumer();
consumer.run();
}
}
/**
* 从任意指定的偏移量进行消费.
* @author dongzhang
* */
public class ManualOffsetConsumer {
public void run() {
//配置属性
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "single_consumer_group");
KafkaConsumer consumer = new KafkaConsumer(properties);
List topics = Arrays.asList("queue");
consumer.subscribe(topics);
//拿到当前的分区配置
Set set = new HashSet<>();
while (set.isEmpty()) {
consumer.poll(Duration.ofSeconds(1));
set = consumer.assignment();
}
//遍历所有的分区,并指定
for (TopicPartition topicPartition : set) {
consumer.seek(topicPartition, 1000);
}
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord consumerRecord : records) {
System.out.println(consumerRecord);
}
}
}
public static void main(String[] args) {
ManualOffsetConsumer consumer = new ManualOffsetConsumer();
consumer.run();
}
}
/**
* 从指定的时间开始下消费
* */
public class ManualTimeConsumer {
public void run() {
//配置属性
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "single_consumer_group");
KafkaConsumer consumer = new KafkaConsumer(properties);
List topics = Arrays.asList("queue");
consumer.subscribe(topics);
//拿到当前的分区配置
Set set = new HashSet<>();
while (set.isEmpty()) {
consumer.poll(Duration.ofSeconds(1));
set = consumer.assignment();
}
HashMap timestampToSearch = new HashMap<>();
// 封装集合存储,每个分区对应一天前的数据
for (TopicPartition topicPartition : set) {
timestampToSearch.put(topicPartition,
System.currentTimeMillis() - 1 * 24 * 3600 * 1000);
}
// 获取从 1 天前开始消费的每个分区的 offset
Map offsets = consumer.offsetsForTimes(timestampToSearch);
// 遍历每个分区,对每个分区设置消费时间。
for (TopicPartition topicPartition : set) {
OffsetAndTimestamp offsetAndTimestamp = offsets.get(topicPartition);
// 根据时间指定开始消费的位置
if (offsetAndTimestamp != null){
consumer.seek(topicPartition, offsetAndTimestamp.offset());
}
}
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord consumerRecord : records) {
System.out.println(consumerRecord);
}
}
}
}
/**
* 独立消费者.
* 消费数据时,指定某个分区
* */
public class SingleAndSpecificPartitionConsumer {
public void pull() {
//配置属性
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,localhost:9093,localhost:9094");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "single_consumer_group");
KafkaConsumer consumer = new KafkaConsumer(properties);
/*List topics = Arrays.asList("queue");
consumer.subscribe(topics);*/
//指定从queue topic的第0个分区消费数据
List topicPartitions = new ArrayList<>();
topicPartitions.add(new TopicPartition("queue", 0));
consumer.assign(topicPartitions);
//拉取数据
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord record : records) {
System.out.println(record);
}
}
}
public static void main(String[] args) {
SingleAndSpecificPartitionConsumer consumer = new SingleAndSpecificPartitionConsumer();
consumer.pull();
}
}