回顾
依赖
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.3.0</version>
</dependency>
生产者
简单看下kafka生产者的配置
- bootstrap.servers:kafka的地址
- ack:消息的确认机制,默认值是0
acks=0:如果设置为0,生产者不会等待kafka的响应。
acks=1:这个配置意味着kafka会把这条消息写到本地日志文件中,但是不会等待集群中其他机器的成功响应。
acks=all:这个配置意味着leader会等待所有的follower同步完成。这个确保消息不会丢失,除非kafka集群中所有机器挂掉。这是最强的可用性保证。 - retries:配置为大于0的值的话,客户端会在消息发送失败时重新发送
- batch.size:当多条消息需要发送到同一个分区时,生产者会尝试合并网络请求。这会提高client和生产者的效率
- key.serializer: 键序列化,默认org.apache.kafka.common.serialization.StringDeserializer
- value.deserializer:值序列化,默认org.apache.kafka.common.serialization.StringDeserializer
…
更详细的配置见官方文档
kafka中文文档
异步发送
public class Send {
public void sendMessage(String topicName) {
Properties props = new Properties();
props.put("bootstrap.servers", "127.0.0.1:9092");
props.put("acks", "all");
props.put("retries", 0);
props.put("batch.size", 16384);
props.put("linger.ms", 1);
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");
Producer<String, String> producer = new KafkaProducer<>(props);
for (int i = 0; i < 100; i++) {
producer.send(new ProducerRecord<String, String>(topicName, Integer.toString(i), "hello kafka " + Integer.toString(i)));
}
producer.close();
}
public static void main(String[] args) {
Send demo = new Send();
demo.sendMessage("my-topic");
}
}
producer.send(new ProducerRecord<String, String>(topic,key,value));
- topic:消息队列的名称,可以先行在kafka服务中进行创建。如果kafka中并未创建该topic,那么便会自动创建
- key:键值,也就是value对应的值,和Map类似
- value:要发送的数据,数据格式为String类型的
测试
- 启动zookeeper
- 启动kafka
- 运行代码
- 启动消费者,指定topic名称为“my-topic”
批处理 - DOS窗口输出:
hello kafka 0
hello kafka 1
...
hello kafka 99
带回调函数的异步发送
public void sendMessageWithCallback(String topicName) {
Properties props = new Properties();
props.put("bootstrap.servers", "127.0.0.1:9092");
props.put("acks", "all");
props.put("retries", 0);
props.put("batch.size", 16384);
props.put("linger.ms", 1);
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");
Producer<String, String> producer = new KafkaProducer<>(props);
for (int i = 0; i < 100; i++) {
producer.send(new ProducerRecord<String, String>(topicName, Integer.toString(i), "hello kafka " + Integer.toString(i)), (recordMetadata, e) -> {
if (e == null) {
System.out.println("发送成功");
} else {
e.printStackTrace();
}
});
}
producer.close();
}
同步发送
同步发送意味着必须等待一个发送消息的ACK后,才可以发送下一条消息
public void sendMessageSynchronize(String topicName) {
Properties props = new Properties();
props.put("bootstrap.servers", "127.0.0.1:9092");
props.put("acks", "all");
props.put("retries", 0);
props.put("batch.size", 16384);
props.put("linger.ms", 1);
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");
Producer<String, String> producer = new KafkaProducer<>(props);
for (int i = 0; i < 100; i++) {
try {
RecordMetadata recordMetadata = producer.send(new ProducerRecord<String, String>("my-topic", Integer.toString(i), "hello kafka " + Integer.toString(i))).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("meta");
}
producer.close();
}
消费者
简单看下kafka消费者的配置
- bootstrap.servers:kafka的地址
- group.id:组名,不同组名可以重复消费。例如你先使用了组名A消费了kafka的1000条数据,但是你还想再次进行消费这1000条数据,并且不想重新去产生,那么这里你只需要更改组名就可以重复消费了
- enable.auto.commit:是否自动提交,默认为true
- auto.commit.interval.ms:从poll(拉)的回话处理时长
- session.timeout.ms:超时时间
- max.poll.records:一次最大拉取的条
- auto.offset.reset:消费规则,默认earliest
earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
none:topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常 - key.serializer:键序列化,默认org.apache.kafka.common.serialization.StringDeserializer
- value.deserializer:值序列化,默认org.apache.kafka.common.serialization.StringDeserializer
…
更详细的配置见官方文档
kafka中文文档
自动提交offset(默认)
@SuppressWarnings("all")
public class Recive {
public void reciveMessage() {
Properties props = new Properties();
/**
* 配置可以用ProducerConfig和ConsumerConfig中的常量
* 里面有相应的DOC说明
*/
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "test");
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000");
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1000);
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// Properties props = new Properties();
// props.put("bootstrap.servers", "127.0.0.1:9092");
// props.put("group.id", "test");
// props.put("enable.auto.commit", "true");
// props.put("auto.commit.interval.ms", "1000");
// props.put("session.timeout.ms", "30000");
// props.put("auto.offset.reset", "earliest");
// props.put("key.deserializer", StringDeserializer.class.getName());
// props.put("value.deserializer", StringDeserializer.class.getName());
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("my-topic"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("topic = %s, offset = %d, key = %s, value = %s%n", record.topic(), record.offset(), record.key(), record.value());
}
}
}
}
public static void main(String[] args) {
Recive demo = new Recive();
demo.reciveMessage();
}
测试
- 运行消费者代码
- 运行生产者代码
控制台打印
topic = my-topic, offset = 800, key = 0, value = hello kafka 0
topic = my-topic, offset = 801, key = 1, value = hello kafka 1
...
topic = my-topic, offset = 899, key = 99, value = hello kafka 99
手动提交offset
offset下标自动提交其实在很多场景都不适用,因为自动提交是在kafka拉取到数据之后就直接提交,这样很容易丢失数据,尤其是在需要事务控制的时候。很多情况下我们需要从kafka成功拉取数据之后,对数据进行相应的处理之后再进行提交。如拉取数据之后进行写入mysql这种,所以这时我们就需要进行手动提交kafka的offset下标。
复习一下offset
offset指的是kafka的topic中的每个消费组消费的下标。简单的来说就是一条消息对应一个offset下标,每次消费数据的时候如果提交offset,那么下次消费就会从提交的offset加一那里开始消费。比如一个topic中有100条数据,我消费了50条并且提交了,那么此时的kafka服务端记录提交的offset就是49(offset从0开始),那么下次消费的时候offset就从50开始消费。
就好比看书,每页都会有一个页码,今天你看了50页,在50页放个书签,明天就从51页开始看。
测试
先测试下不提交
- 将
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
的true
值改为false
- 运行消费者代码
- 跑一次生产者代码
控制台打印
topic = my-topic, offset = 1000, key = 0, value = hello kafka 0
topic = my-topic, offset = 1001, key = 1, value = hello kafka 1
...
topic = my-topic, offset = 1099, key = 99, value = hello kafka 99
- 再跑一次生产者代码
控制台打印
topic = my-topic, offset = 1100, key = 0, value = hello kafka 0
topic = my-topic, offset = 1001, key = 1, value = hello kafka 1
...
topic = my-topic, offset = 1199, key = 99, value = hello kafka 99
- 重新跑一下消费者代码(注意offset)
控制台打印
topic = my-topic, offset = 1000, key = 0, value = hello kafka 0
topic = my-topic, offset = 1001, key = 1, value = hello kafka 1
...
topic = my-topic, offset = 1199, key = 99, value = hello kafka 99
可以看到已经重复消费了
手动提交
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("topic = %s, offset = %d, key = %s, value = %s%n", record.topic(), record.offset(), record.key(), record.value());
}
// 手动提交
consumer.commitAsync();
}
- 自行尝试着测试一下吧!