当下互联网公司使用最多的消息中间件,无疑是 kafka和rocketmq,
虽然rocketmq单机10W+TPS比不上kafka单机的100W+
但rocketmq 由于不依赖外部kit,加上简洁的api和灵活的设计,相对丰富的特性 包括 顺序消息,事务消息,重新投递等。成为越来越多互联网公司的首选。
不过kafka作为老牌的消息中间件,其单机高达百万的TPS在处理海量日志等大数据量场景下还是很值得青睐的,

1. kafka简介

kafka 是apache基金会下的一款高可用,高吞吐量,低延迟的消息中间件,
kafka 对zookeeper有强依赖,用于选举机制的实现和部分数据存贮。
高版本的kafka 将zk整合到了一起。

kafka 一个 topic下 有多个partition,一个消费组(group)可以订阅多个topic,一个消费组可以有多个消费者,
多个消费组可以消费同一个topic,但对于同一个topic的每个partition来说,只能被同一个消费组的单个消费者消费.即 topic的partition的数量是消费速率的瓶颈所在。

每个消费组对于topic的每个partition都维护了一个消费偏移量offset ,靠整个来维护消息消费的进度,在消息消费完后可以选择自动提交或者手动提交。

2.下载和启动kafka

从官网下载kafka
http://kafka.apache.org/quickstart

1.下载完,解压

tar -zxvf kafka_2.13-2.8.0.tgz
 cd kafka_2.13-2.8.0

2.启动zk

bin/zookeeper-server-start.sh config/zookeeper.properties

3.启动kafka

配置kafka server文件

如何查看kafka中间件日志功能 kafka是中间件吗_java


搭建kafka集群,只需要保证在集群中每个broker的broker.id 唯一即可,大家都连入同一个zk地址,即完成了集群的搭建,advertised.listeners:表示外部的生产者和消费者连接到kafka的地址,需要配置对应的集群的ip+端口号,多个地址以逗号分割,否则其他机器无法连接到kafka

如何查看kafka中间件日志功能 kafka是中间件吗_kafka_02

bin/kafka-server-start.sh config/server.properties

4.创建测试的topic
9092是kafka启动后默认的端口

bin/kafka-topics.sh --create --topic my-topic --bootstrap-server localhost:9092

kafka的bin目录下有很多kafka相关的shell脚本,我们可以使用–help查看帮助

如何查看kafka中间件日志功能 kafka是中间件吗_kafka_03

5.使用java发送和消费kafka消息

引入maven依赖

<dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>2.8.0</version>
        </dependency>

发送消息

package com.ly.kafka;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class KafkaProducerTest {
    public  void   send(){
        Properties props = new Properties();
        props.put("bootstrap.servers", "192.168.234.8:9092");
        //消息发送确认类型
        props.put("acks", "all");
        props.put("retries", 0);
        props.put("linger.ms", 1);
        //指定key的序列化方式,key相同的message 会分到同一个partition中
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        //指定value的序列化方式
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        Producer<String, String> producer = new KafkaProducer<>(props);
        for (int i = 0; i < 109; i++)
            producer.send(new ProducerRecord<String, String>("my-topic", Integer.toString(i), Integer.toString(i)));

        producer.close();
    }
}

消费消息

package com.ly.kafka;

import com.ly.service.ImportTest2Service;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;

@Service
public class KafkaConsumerTest {

    private Logger logger = LoggerFactory.getLogger(ImportTest2Service.class);

    public void consumer() {
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", "192.168.234.8:9092");
        //消费组id
        props.setProperty("group.id", "test");
        //自动确认还是手动确认,默认是自动
        props.setProperty("enable.auto.commit", "true");
        props.setProperty("auto.commit.interval.ms", "1000");
        props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("offset-test", "my-topic"));
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records)
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
        }
    }

    public void manuallyConsumer() {
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", "192.168.234.8:9092");
        props.setProperty("group.id", "test");
        props.setProperty("enable.auto.commit", "false");
        //props.setProperty("auto.commit.interval.ms", "1000");
        props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.setProperty("max.poll.records", "1");
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("offset-test", "my-topic"));
        AtomicInteger atomicInteger = new AtomicInteger();
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records) {
                if (records.isEmpty()) {
                    break;
                }

                logger.warn("offset-test = {}, key = {}, value = {}", record.offset(), record.key(), record.value());

                if (atomicInteger.get() % 10 == 0) {
                	//手动异步确认消息commitAsync, consumer.commitSync()表同步确认,会大大降低吞吐量。未被commit的消息,如果后续新产生的消息有被commit,那么前面为被commint的消息也将被视为已消费,因为消费组于单个partition只维护了一个偏移量。
                    consumer.commitAsync();
                }
                atomicInteger.incrementAndGet();
            }
        }
    }
}

上面只是简单的介绍了kafka的使用,很多配置和特性的详情,见官方文档
http://kafka.apache.org/documentation/#semantics kafka原生的api用起来很不方便,一般会考虑使用spring-kafka。