springboot整合kafka,收发javaBean

    上午不想上班,把周六整的kafka贴出来。看网上搜不出来几篇好用的,供大家参考参考。<( ̄▽ ̄)> 哇哈哈…

    spring和kafka整合 并没有什么starter项目。 使用的是spring-kafka。如果是在spring-cloud里使用kafka的话,可以使用spring-cloud-stream。这里我用的是springboot+dubbo,而且项目都是单个的,就直接使用spring-kafka了。由于spirng-kafka最新版本需要jdk1.8,我的项目还是1.7的,所以我用老一点版本的。

<dependency>
		  <groupId>org.springframework.kafka</groupId>
		  <artifactId>spring-kafka</artifactId>
		  <version>1.3.0.RELEASE</version>
		</dependency>

概念。

    首先要配置至少一个producerFactory,这个工厂类指定我们要连哪个kafka,也就是设置producer相关的属性。kafkaTemplate。模版类,是用来向kafka发送消息的,它没有收消息的功能。在producer端,只需要这个template就完了,springboot的项目写了配置文件,就默认有个producerFactory, template默认就用这个工厂类。

    在消费者端,就复杂点。首先,我们需要至少一个ConsumerFactory工厂类,这个工厂类配置consumer相关的信息,比如你连哪个kafka,你的group_id是多少。然后,再要配置一个ListenerContainerFactory,这个工厂类配置线程池,由这个线程池来负责调用listener来消费消息。listener就是我们实际消费消息的业务类。

生产者端。

    KafkaTemplate,可以指定默认的topic,这样发的时候就不用再指定topic了。template的发送是异步的,会返回一个future。给future注册回调,就能处理成功还是失败。

ListenableFuture<SendResult<Integer, String>> future = template.send("foo");
future.addCallback(new ListenableFutureCallback<SendResult<Integer, String>>() {

    @Override
    public void onSuccess(SendResult<Integer, String> result) {
        ...
    }

    @Override
    public void onFailure(Throwable ex) {
        ...
    }

});

也可以直接给template注册一个全局的回调ProducerListener来处理。默认template就注册了LoggingProducerListener,这个listener成功的时候什么都不做,失败了打印下日志。Listener不用写,继承ProducerListenerAdapter就可以了。如果需要当前线程阻塞等待future完成,先执行template.flush,然后再future.get()。

消费者端。

    配置好consumerFactory之后,就定义好了怎么连到kafka。然后需要配置containerFactory来生成container。container有两种,一种是 KafkaMessageListenerContainer  也就是单线程的。另一种是 ConcurrentMessageListenerContainer 实际上是维护了多个单线程的container,通过设置concurrency来控制并发度。

    对于并发度。假设我们一个topic有6个partion,而我们有concurrency设置为3,也就是有3个KafkaMessageListenerContainer,那么每个container都会读两个partition。 如果我们的concurrency设置为7呢? 那么实际上有一个是没用的,所以实际上spring只会new 6个出来。

    consumer会一次从kafka拉取一定数量的ConsumerRecord,派发给container处理,这里和kafka api里的poll(long timeout)是一致的。如果consumer设置了enable.auto.commit为true,那么拉取过来的消息会自动commit。否则,需要设置container来决定ack的方式。

RECORD - 由listener方法处理完一行记录就提交
BATCH - 由listener方法处理完所有行记录就提交
TIME - 隔段时间提交
COUNT - 一定次数提交
COUNT_TIME - 一定时间或者一定次数提交
MANUAL - 当listener调用了acknowledge()方法,然后按照batch的方式提交
MANUAL_IMMEDIATE - 每次listener调用了ack方法就提交一次

batch当然性能更好,但是如果你的业务是可能报错或者说不稳定的,那么也好不到哪去。

扫描二维码关注公众号,回复: 869510 查看本文章

我们通过设置container的syncCommits 来决定是同步提交偏移,还是异步。

关于从kafka读数据的初始偏移量。如果我是一个新的消费者,可以设置consumer的auto-offset-reset来决定。取值可以是earliest或者是latest。也就是说从最老的消息开始,还是从最新的消息开始。 如果我是个老消费者,只是有段时间下线了,再来,会接着上次的读,和HighLevelApi的行为一致。当然也可以在构造consumer的时候,设置TopicPartitionInitialOffset属性来人为改变偏移量。

序列化

kafka只能发字符串或者字节流。不过spring提供了一个基于jackson的json方案,直接可以把任何javabean先变成json,在变成字节写出去。不过反过来的时候,构造JsonDeserializer 的时候,必须传入java类的class,所以这里不能使用yml或者properties文件来配置。

<!-- json解析器 -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-annotations</artifactId>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
		</dependency>

实例

生产者:

spring: 
  jackson:
    serialization:
      fail-on-empty-beans: false 
  kafka:
    producer:
      # 0不需要确认,1leader确认, all所有人确认收到
      acks: 1 
      # kafka地址,逗号分隔
      bootstrap-servers: 175.xxx.xxx.xx:9092
      # log里的客户端id
      client-id: ${spring.application.name}
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
      retries: 0 

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MessageServiceApplication.class)
public class TestKafka {

    @Autowired
    private KafkaTemplate<String, AppUser> kafka;

    @Test
    public void contextLoads() {
        AppUser ap = new AppUser();
        ap.setUserId(1l);
        ap.setNickName("胡淘");
        ap.setAvatar("http://localhost/123");
        
        ListenableFuture<SendResult<String, AppUser>> future = kafka.send("test", "1", ap);
        future.addCallback(new ListenableFutureCallback<SendResult<String, AppUser>>() {
            @Override
            public void onSuccess(SendResult<String, AppUser> result) {
                System.out.println(result.getProducerRecord().key()+"发送成功");
            }

            @Override
            public void onFailure(Throwable ex) {
                System.out.println(ex.getMessage());
            }
        });
    }
}

这里,我们的producer和template都是提供了配置文件,springboot自动就提供了bean。不需要额外配置。除非我们要连多个kafka。

消费者:

    由于deSerializer的配置需要提供一个java类,所以,不能通过springboot的配置文件,来使用默认的consumer。所以我们手动写歌配置类,使用代码自己配置。

spring: 
  application:
    name: xxx 
  kafka:
    consumer: 
      bootstrap-servers: 175.xx.xx.xx:9092
      group-id: ${spring.application.name}
      #key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      #value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer 
      max-poll-records: 20
    listener:
      concurrency: 1 
@Configuration
@EnableKafka
public class KafkaConfig {

    @Value("${spring.kafka.consumer.bootstrap-servers}")
    private String bootstrapServers;

    @Value("${spring.kafka.consumer.group-id}")
    private String groupId;

    @Value("${spring.kafka.consumer.max-poll-records}")
    private Integer maxPollRecords;

    @Value("${spring.kafka.listener.concurrency}")
    private Integer concurrency;

    @Bean(name = "userConsumerFactory")
    public ConsumerFactory<String, AppUser> userConsumerFactory() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(),
                new JsonDeserializer<>(AppUser.class));
    }

    @Bean(name = "kafkaListenerContainerFactory")
    public ConcurrentKafkaListenerContainerFactory<String, AppUser> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, AppUser> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(userConsumerFactory());
        factory.setConcurrency(concurrency);
        factory.getContainerProperties().setAckMode(AckMode.MANUAL_IMMEDIATE);
        return factory;
    }
}

enableKafka,是打开扫描,扫Listener。所以我们的Listener所在的包要给spring托管。

这里有个坑KafkaAnnotationDrivenConfiguration,会自动装配consumer和Container,但是他默认的哪个containerFactory竟然是指定了名字的,所以,我自己的类,不能随便起名字。如果我只有一个containerFactory,那么我必须叫上面这个名字,否则,springboot还会再初始化一个,但是由于我们自己提供了consumerFactory,导致他的consumerFactory没初始化,这里就会报错。

@Component
public class AppUserListener {

    @KafkaListener(id = "messageListener", topics = "test", containerFactory = "kafkaListenerContainerFactory")
    public void listen(AppUser user, Acknowledgment ack) {
        System.out.println("收到kafka消息");
        System.out.println(JsonUtils.toJson(user));
        ack.acknowledge();
    }
}

猜你喜欢

转载自my.oschina.net/u/1047640/blog/1563199
今日推荐