Spring Boot 整合——kafka消息转换、使用异步获取消息以及使用事务消息

Spring Boot 整合之前的内容

项目名称描述地址
base-data-mybatis整合mybatis-plus(实际上官方教程已经很多,只做了自定义插件)未完成
base-jpaJPA基础使用JPA 数据模型定义
base-jpa-queryJPA多表关联使用JPA 数据模型关联操作
base-log日志配置SpringBoot日志配置
base-rabbitrabbitMQ简单使用RabbitMQ基础使用
base-rabbit3rabbitMQ一些自定义配置消息确认回调、消息转换以及消息异常处理
base-rabbit-delayrabbitMQ延时队列延时队列和消息重试
base-redisredis简单使用RedisTemplate基础使用;Redis实现简单的发布订阅以及配置序列化方式
base-redis-lockredis分布式锁Redis分布式锁的简单实现
base-redis-delay基于有赞的延时消息方案的简单实现延时队列的简单实现
base-swaggerswagger使用wagger2使用
base-mongodbmongodb简单使用MongoDB安装以及Spring Boot整合,MongoDB实体创建以及简单CRUD,MongoDB聚合操作,MongoDB分组去重以及MongoDB联表查询

关于版本

依赖版本
springboot2.0.8.RELEASE
mongodb4.0.14

项目地址

因为涉及的代码较多,所以并不会贴出所有代码。本篇文章涉及的源码下载地址: https://gitee.com/daifyutils/springboot-samples

消息处理

针对消息的处理主要有两种方式:

  1. 对消息序列化的设置
  2. 实现RecordMessageConverter接口对消息进行处理

消息序列化

消息序列化主要是在初始化factory的时候对key和value进行操作的序列化设置。

    @Bean
    @ConditionalOnMissingBean(ConsumerFactory.class)
    public ConsumerFactory<?, ?> kafkaConsumerFactory() {
        DefaultKafkaConsumerFactory<String, Object> consumerFactory = new DefaultKafkaConsumerFactory<>(
                this.properties.buildConsumerProperties());
        // 对数据进行反序列化
        consumerFactory.setKeyDeserializer(new StringDeserializer());
        // 需要注意在对值进行解密的时候,kafka需要指定安全包名,或者使用*表示所有
        JsonDeserializer jsonDeserializer = new JsonDeserializer();
        jsonDeserializer.addTrustedPackages("*");
        consumerFactory.setValueDeserializer(jsonDeserializer);

        return consumerFactory;
    }

    @Bean
    @ConditionalOnMissingBean(ProducerFactory.class)
    public ProducerFactory<?, ?> kafkaProducerFactory() {
        DefaultKafkaProducerFactory<String, Object> factory = new DefaultKafkaProducerFactory<>(
            this.properties.buildProducerProperties());
        String transactionIdPrefix = this.properties.getProducer()
            .getTransactionIdPrefix();
        // 对数据进行序列化
        factory.setKeySerializer(new StringSerializer());
        factory.setValueSerializer(new JsonSerializer());

        if (transactionIdPrefix != null) {
            factory.setTransactionIdPrefix(transactionIdPrefix);
        }
        return factory;
    }

当然这种操作只是对数据的序列化操作,需要对数据内容进行修改需要实现RecordMessageConverter接口

  • 首先需要创建一个实现RecordMessageConverter接口的类
/**
 * 自定义消息转换器
 * @author daify
 */
@Log4j2
public class CustomRecordMessageConverter implements RecordMessageConverter {

    /**
     * 负责处理消费端传递的内容
     * @param consumerRecord
     * @param acknowledgment
     * @param consumer
     * @param type
     * @return
     */
    @Override
    public Message<?> toMessage(ConsumerRecord<?, ?> consumerRecord,
                                Acknowledgment acknowledgment,
                                Consumer<?, ?> consumer,
                                Type type) {

        return null;
    }

    /**
     * 负责将生产者的消息进行处理
     * @param message
     * @param s
     * @return
     */
    @Override
    public ProducerRecord<?, ?> fromMessage(Message<?> message, String s) {
        log.info("执行了………………fromMessage");
        log.info("acknowledgment 内容:{}", JSON.toJSONString(message));

        log.info("consumer 内容:{}", s);
        String valueStr = JSON.toJSONString(message.getPayload());
        // 此处在对消息处理的时候可以尝试修改消息目标
        ProducerRecord record = new ProducerRecord(KafkaConfig.TOPIC2,valueStr);

        return record;
    }
}
  • 在初始化KafkaTemplate的时候进行消息转换器设置
    @Bean
    @ConditionalOnMissingBean(KafkaTemplate.class)
    public KafkaTemplate<?, ?> kafkaTemplate(
        ProducerFactory<Object, Object> kafkaProducerFactory,
        ProducerListener<Object, Object> kafkaProducerListener) {

        KafkaTemplate<Object, Object> kafkaTemplate = new KafkaTemplate<>(
            kafkaProducerFactory);
        // 设置消息转换
        kafkaTemplate.setMessageConverter(new CustomRecordMessageConverter());
        kafkaTemplate.setProducerListener(kafkaProducerListener);
        kafkaTemplate.setDefaultTopic(this.properties.getTemplate().getDefaultTopic());
        return kafkaTemplate;
    }

  • 测试

在这里测试向kafka-topic1中发送消息,但是进过消息处理的时候目标会被修改到了kafka-topic2这个时候控制台输出的结果

## 经过了消息处理器
d.s.k.c.c.CustomRecordMessageConverter   : 执行了………………fromMessage
d.s.k.c.c.CustomRecordMessageConverter   : acknowledgment 内容:{"headers":{"id":"ff32f269-3e7d-984e-0f4f-936167b91903","timestamp":1588412460297},"payload":{"id":1,"type":0},"topic":"kafka-topic1"}
d.s.k.c.c.CustomRecordMessageConverter   : consumer 内容:null
o.a.k.clients.producer.ProducerConfig    : ProducerConfig values: 

## kafka-topic2消费端监听的结果
o.a.kafka.common.utils.AppInfoParser     : Kafka version : 1.0.2
o.a.kafka.common.utils.AppInfoParser     : Kafka commitId : 2a121f7b1d402825
d.s.k.c.consumer.KafkaConsumerListener   : kafka-topic2接收结果:"{\"id\":1,\"type\":0}"

异步发送消息

发送消息的时候,我们可能需要获取消息结果,但是等待时间过长会导致线程阻塞起来,所以我们可以通过异步方式获取消息发送的结果,这样可以大大提高了生产者的吞吐量。

获取消息结果的代码

同步

通过同步方式获取消息发送结果可以通过下面的代码

    @Async
    public String sendAsync(MyMessage myMessage, String topic) {
        CustomMessage message = new CustomMessage();
        message.setPayload(myMessage,topic);
        try {
            SendResult<String, Object> stringObjectSendResult = kafkaTemplate.send(message).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return JSON.toJSONString(message);
    }

异步

而如果希望获取结果使用异步方式可以使用下面的代码。

    @Async
    public String sendAsync(MyMessage myMessage, String topic) {
        CustomMessage message = new CustomMessage();
        message.setPayload(myMessage,topic);
        ListenableFuture<SendResult<String, Object>> send = kafkaTemplate.send(message);
        send.addCallback(new CustomListenableFutureCallback(message));
        return JSON.toJSONString(message);
    }

send.addCallback(new CustomListenableFutureCallback(message)); 是通过回调的方式获得最终结果,传入的方法需要实现ListenableFutureCallback接口。下面是一个实现此接口的方法

/**
 * 获取消费的异步结果
 * @author daify
 */
@Log4j2
public class CustomListenableFutureCallback implements ListenableFutureCallback<SendResult<String, Object>> {

    public CustomListenableFutureCallback(CustomMessage customMessage) {
        this.customMessage = customMessage;
    }

    private CustomMessage customMessage;

    /**
     * 失败的时候
     * @param throwable
     */
    @Override
    public void onFailure(Throwable throwable) {
        log.error("执行了onFailure");
        log.error("topic:{},message:{}" ,
            customMessage.getPayload(),
            JSON.toJSONString(customMessage.getPayload()));
    }

    /**
     * 成功的办法
     * @param stringMyMessageSendResult
     */
    @Override
    public void onSuccess(SendResult<String, Object> stringMyMessageSendResult) {
        log.info("执行了onSuccess");
        log.info("topic:{},message:{}" ,
            customMessage.getPayload(),
            JSON.toJSONString(customMessage.getPayload()));
    }
}

现在通过异步方式发送消息后控制台会打印下面内容

d.s.k.c.c.CustomListenableFutureCallback : 执行了onSuccess
d.s.k.c.c.CustomListenableFutureCallback : topic:MyMessage(id=2, name=sendAsync, type=2),message:{"id":2,"name":"sendAsync","type":2}
d.s.k.c.consumer.KafkaConsumerListener   : kafka-topic2接收结果:"{\"id\":2,\"name\":\"sendAsync\",\"type\":2}"

使用事务消息

首先要使用事务消息需要对配置进行修改。主要是针对生产者的修改

  application:
    name: base.kafka
  kafka:
    bootstrap-servers: kafka服务地址1:端口,kafka服务地址2:端口,kafka服务地址3:端口
    producer:
      # 写入失败时,重试次数。当leader节点失效,一个repli节点会替代成为leader节点,此时可能出现写入失败,
      # 当retris为0时,produce不会重复。retirs重发,此时repli节点完全成为leader节点,不会产生消息丢失。
      retries: 3
      #procedure要求leader在考虑完成请求之前收到的确认数,用于控制发送记录在服务端的持久化,其值可以为如下:
      #acks = 0 如果设置为零,则生产者将不会等待来自服务器的任何确认,该记录将立即添加到套接字缓冲区并视为已发送。在这种情况下,无法保证服务器已收到记录,并且重试配置将不会生效(因为客户端通常不会知道任何故障),为每条记录返回的偏移量始终设置为-1。
      #acks = 1 这意味着leader会将记录写入其本地日志,但无需等待所有副本服务器的完全确认即可做出回应,在这种情况下,如果leader在确认记录后立即失败,但在将数据复制到所有的副本服务器之前,则记录将会丢失。
      #acks = all 这意味着leader将等待完整的同步副本集以确认记录,这保证了只要至少一个同步副本服务器仍然存活,记录就不会丢失,这是最强有力的保证,这相当于acks = -1的设置。
      #可以设置的值为:all, -1, 0, 1
      acks: all
      transaction-id-prefix: transaction'
### 下面消费端内容不变
......

我们在使用kafka的时候,希望在某些业务出现问题的时候,终止对kafka传递的消息,所以KafkaTemplate提供了一个executeInTransaction方法让我们实现在事务的消息发送。

executeInTransaction的使用相当简单,只需要使用kafkaTemplate.executeInTransaction便实现了事务的消息

    /**
     * 事务性的消息发送
     * @param myMessage
     * @return
     */
    public String sendInTransaction(MyMessage myMessage){

        for (int i = 0; i < 5; i++) {
            int index = i;
            myMessage.setId(i);
            kafkaTemplate.executeInTransaction(new KafkaOperations.OperationsCallback<String, Object, Object>() {
                @Override
                public Object doInOperations(KafkaOperations<String, Object> operations) {
                    try {
                        SendResult<String, Object> result = null;
                        if (index == 3) {
                            throw new RuntimeException();
                        }
                        try {
                            result = kafkaTemplate.send("transaction-test", "测试数据:" + index).get();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (ExecutionException e) {
                            e.printStackTrace();
                        }
                        log.info("kafka 事务消息: {}" , "测试数据:" + index);
                        return JSON.toJSONString(myMessage);
                    } catch (Exception e) {
                        e.printStackTrace();
                        return "发送失败";
                    }
                }
            });
        }
        return JSON.toJSONString(myMessage);
    }

尝试调用上面的方法向"transaction-test"的topic输入数据最后控制台中会发现出现错误的第四条数据因为抛出异常,其他发送的消息并未出现在消费端的日志中

d.s.k.t.producer.TransactionKafkaSender  : kafka 事务消息: 测试数据:0
d.s.k.t.consumer.KafkaConsumerListener   : transaction-test接收结果:"测试数据:0"
d.s.k.t.producer.TransactionKafkaSender  : kafka 事务消息: 测试数据:1
d.s.k.t.consumer.KafkaConsumerListener   : transaction-test接收结果:"测试数据:1"
d.s.k.t.producer.TransactionKafkaSender  : kafka 事务消息: 测试数据:2
d.s.k.t.consumer.KafkaConsumerListener   : transaction-test接收结果:"测试数据:2"
java.lang.RuntimeException
	at dai.samples.kafka.transaction.producer.TransactionKafkaSender$1.doInOperations(TransactionKafkaSende..... 异常数据
d.s.k.t.producer.TransactionKafkaSender  : kafka 事务消息: 测试数据:4
d.s.k.t.consumer.KafkaConsumerListener   : transaction-test接收结果:"测试数据:4"

到目前,springboot关于kafka的消息发送的简单操作就截止了。后面我会介绍消费端的内容。


个人水平有限,上面的内容可能存在没有描述清楚或者错误的地方,假如开发同学发现了,请及时告知,我会第一时间修改相关内容。假如我的这篇内容对你有任何帮助的话,麻烦给我点一个赞。你的点赞就是我前进的动力。

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
可以使用Spring Kafka提供的@KafkaListener注解来监听Kafka消息。以下是示例代码: 首先,需要在Spring Boot应用程序中添加以下依赖: ```xml <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>2.5.5</version> </dependency> ``` 接下来,在配置类中配置Kafka相关属性: ```java @Configuration @EnableKafka public class KafkaConfiguration { @Value("${spring.kafka.bootstrap-servers}") private String bootstrapServers; @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, StringDeserializer.class); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group-id"); return props; } @Bean public ConsumerFactory<String, String> consumerFactory() { return new DefaultKafkaConsumerFactory<>(consumerConfigs()); } @Bean public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(consumerFactory()); return factory; } } ``` 在上面的代码中,我们使用@Value注解获取Spring Boot应用程序中配置的Kafka服务器地址,并配置了Kafka消费者的相关属性。 接下来,我们可以创建一个Kafka消息监听器: ```java @Component public class KafkaMessageConsumer { @KafkaListener(topics = "${spring.kafka.consumer.topic}") public void receiveMessage(String message) { System.out.println("Received message: " + message); } } ``` 在上面的代码中,我们使用@KafkaListener注解来指定要监听的Kafka主题,然后定义了一个receiveMessage方法来处理接收到的消息。 最后,我们需要在应用程序的启动类上添加@EnableKafka注解来启用Kafka监听器: ```java @SpringBootApplication @EnableKafka public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } ``` 这就是使用Spring Kafka来监听Kafka消息的示例代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大·风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值