首先,我们需要配置好kafka的依赖及客户端必要参数(有关服务器的配置,我会在另一篇博客里介绍)。

 1.加入kafka依赖

//kafka
compile ('org.springframework.kafka:spring-kafka')

2.配置kafka的相关参数

/*kafka配置*/
@Configuration
@EnableKafka
public class KafkaConf {

    @Value("${kafka.bootstrap-servers}")
    private String bootstrapServers;//服务器的地址,例如,192.168.31.136:9092

    /*消息生产者*/
    @Bean
    public Map<String, Object> producerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        props.put(ProducerConfig.RETRIES_CONFIG, 0);
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
        props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, LongSerializer.class);//这个是kafka自带的Long型数据序列化类
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, DataSerializer.class);//自定义的序列化类
        return props;
    }

    
    @Bean
    public ProducerFactory<Long, Message> producerFactory() {
        return new DefaultKafkaProducerFactory<>(producerConfigs());
    }

    @Bean
    public KafkaTemplate<Long, Message> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }

    /*消息的消费者,通常,生产者和消费者不在同一个项目中*/
    @Bean
    public Map<String, Object> consumerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, LongDeserializer.class);//kafka自带的Long型数据反序列化类
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class);//kafka自带的byte数组反序列化类
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "test");//默认客户组
        return props;
    }

    @Bean
    public ConsumerFactory<Long, byte[]> consumerFactory() {
        return new DefaultKafkaConsumerFactory<>(consumerConfigs());
    }

    /*监听器配置*/
    @Bean
    public ConcurrentKafkaListenerContainerFactory<Long, byte[]> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<Long, byte[]> factory =
                new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        return factory;
    }
}

给kafka发送的数据必须是经过序列化的,自定义的序列化类需要实现kafka的Serializer接口,反序列化需要实现Deserializer接口,例如上面的DataSerializer类

public class DataSerializer<T extends Message> implements Serializer<T> {

    @Override
    public void configure(Map<String, ?> configs, boolean isKey) {
        //do nothing
    }

    @Override
    public byte[] serialize(final String topic, final T data) {
        return data.toByteArray();
    }

    @Override
    public void close() {
        //do nothing
    }
}

configure()和close()方法都可以使用默认的配置处理,这里只需要把serialize方法处理好就行

3.生成特定的bean

以上是项目基本配置,但是,光有以上配置是不够的,通常,我们希望能直接交给生产者一个我们自定义的javabean,由它来处理具体的toByte过程,这时,就需要使用特定的bean。

例如,我们有一个Test类,包含两个参数:id和text

public class Test {
    private long id;
    private String text;
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
}

但是,这个类kafka是无法直接使用的,即便是使用了之前的自定义序列化方法。于是,我们引入一个插件protobuf-gradle-plugin,这个插件可以将.proto文件转化为kafka能够使用的类,这个类的调用方法又和我们熟悉的javabean相似,这正是我们所需要的。

配置脚本依赖包

classpath('com.google.protobuf:protobuf-gradle-plugin:0.8.1')

这个配置加在buildscript的dependencies中,这是给IDE使用的插件

之后,在build.gradle中加入

//protobuf code generation
sourceSets {
    main {
        proto {
            srcDir 'src/main/resources/proto'//.proto文件所在目录
            include 'test.proto'//我们定义的自定义类
        }
    }
}

protobuf {
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {}
            }
        }
    }
    generatedFilesBaseDir = "$projectDir/src"//项目主目录
}

之后我们就能在脚本任务中找到generateProto任务了(通常在Tasks的other中),执行这个任务,就能用我们配置好的test.proto生成kafka所需的类了。

test.proto配置如下

syntax = "proto3";

option java_package = "com.test.study.aboutkafka.model";//类输出位置
option java_outer_classname = "Test";//输出的类的名字
message test {
    int64 id = 1;//1表示第一个属性,int64对应java的Long
    string text = 2;//第二个属性
}

输出的类为这种格式

public final class Test {
  private Test() {}
  public static void registerAllExtensions(
      com.google.protobuf.ExtensionRegistryLite registry) {
  }

  public static void registerAllExtensions(
      com.google.protobuf.ExtensionRegistry registry) {
    registerAllExtensions(
        (com.google.protobuf.ExtensionRegistryLite) registry);
  }
  public interface testOrBuilder extends
      // @@protoc_insertion_point(interface_extends:test)
      com.google.protobuf.MessageOrBuilder {

很长,就不全贴了,两个属性在这里

private long id_ ;
/**
 * <code>int64 id = 1;</code>
 */
public long getId() {
  return id_;
}
/**
 * <code>int64 id = 1;</code>
 */
public Builder setId(long value) {
  
  id_ = value;
  onChanged();
  return this;
}
 
 
private java.lang.Object text_ = "";
/**
 * <code>string text = 2;</code>
 */
public java.lang.String getText() {
  java.lang.Object ref = text_;
  if (!(ref instanceof java.lang.String)) {
    com.google.protobuf.ByteString bs =
        (com.google.protobuf.ByteString) ref;
    java.lang.String s = bs.toStringUtf8();
    text_ = s;
    return s;
  } else {
    return (java.lang.String) ref;
  }
}
 
 
/**
     * <code>string text = 2;</code>
     */
    public Builder setText(
        java.lang.String value) {
      if (value == null) {
  throw new NullPointerException();
}

      text_ = value;
      onChanged();
      return this;
    }

对于string类型参数text,这里有个非空校验,如果不想要可以删掉。

4.使用kafka

先是生产者

@Component
public class TestProducer {
    @Autowired
    public TestProducer(KafkaTemplate<Long, Message> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }
    public void testsend() {
        Test.test.Builder builder = Test.test.newBuilder();
        builder.setId(1);
        builder.setText("hey man");
        Test.test msg = builder.build();

        kafkaTemplate.send("test1" ,msg );
        System.out.println("send");
    }
    private final KafkaTemplate<Long, Message> kafkaTemplate;
}

首先注入我们配置好的kafkaTemplate实例,接着构造bean,完成后发送

public ListenableFuture<SendResult<K, V>> send(String topic, V data) {
   ProducerRecord<K, V> producerRecord = new ProducerRecord<>(topic, data);
   return doSend(producerRecord);
}

send有两个参数,第一个是topic主题,只有订阅这个主题的消费者才能消费这条消息,第二个是data,消息主体。

接下来是消费者

@Component
public class TestConsumer {
    @KafkaListener( topics = "test1" , group = "chicken")
   public void listen1(byte[] buf) throws InvalidProtocolBufferException {
        Test.test msg = Test.test.parseFrom(buf);
        System.out.println("msg is = " + msg);
    }
}

消费者需要配置自己的消费主题topics,和自己所在用户组,组如果不配,则会使用我们默认的test组。一个主题下的一条消息,在同一个用户组中,只会被消费一次。

比如,生产者往test1发送一条“hey man”,现在有10个消费者,其中6个所在组是chicken,另外4个所在组是dog,那么chicken和dog组中分别只有一个用户能监听到并消费这条消息。

以上就是kafka的简单用例。