每天进步一点点 - spring-cloud-stream 3.X 整合kafka、rocketmq、rabbitmq

2,209 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情

1、序言

最近项目的业务场景是在别的模块需要进行记录日志,但是又要求比较高的TPS,之前的文章# 每天进步一点点 - SpringBoot的异步操作@Async里面想到的方案是利用线程池异步调取别的服务存取日志,但是当并发比较高的时候,线程池里面的线程用了之后,后面的日志信息就会被抛弃,并且在高并发抢线程的时候也影响性能,所以就改成里面消息队列的形式来记录日志并消费存取。

2、POM

都是引用的最新版本的包,并且都是引入的大包,包含生产者和消费者以及spring-cloud-stram的包。

  • kafka
<!--kafka-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-kafka</artifactId>
    <version>3.2.2</version>
</dependency>
  • rabbitmq
<!--rabbitmq-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    <version>3.2.2</version>
</dependency>
  • rocketmq
<!-- rocketmq -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
    <version>2021.1</version>
</dependency>

3、配置

spring:
  cloud:
    stream:
        kafka:
            binder:
                brokers: 192.168.11.117:9092
            bindings:
                # 此处一定要跟方法名匹配上
                apiServiceLog-in-0:
                    destination: api-service-log
                    content-type: application/json
                apiServiceLog-out-0:
                    destination: api-service-log
                    content-type: application/json
                    
  • 说明:
  1. spring.cloud.stream.kafka.binder配置里面有很多配置,对于当前项目来说,默认值即可满足;比如:默认提交偏移量,默认的分区和副本数量为1等配置;
  2. spring.cloud.stream.kafka.binders配置主要是为了配置消费者以及生成者信息的,其中:xxxx-out/in-0这个配置主要是为了配置声明当前是生产者还是消费者的,destination是说明topic名称的。

4、代码实现

spring-cloud-stream 3.X之后,官方就不建议使用@Binding(Source.class) 、@StreamListener(Sink.class),改成推荐使用函数式编程的方式实现;

用官方的话说就是你不管我是怎么给你的,你只管接收消费就行。

4.1 生产者

  • 配置生产者的代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.stereotype.Component;

// 别忘了声音让Spring管理
@Component
public class LogMessageProducer {

    // 这个地方必须要注入这个,因为都是基于这个来的。
    @Autowired
    private StreamBridge streamBridge;

    public void logProducer(String topicName, String logJsonInfo) {
        // 这个地方也可以使用实体类啥的,都一样。
        streamBridge.send(topicName, logJsonInfo);
    }
}

  • 引入,使用;
/**
 * 获取配置的topic名称,由于开启了自动创建topic,如果配置的topic没有,会给你自动生成一个。
 */
@Value("${mq.topic.log.api-service}")
private String TOPIC_NAME;

// 注入一下子
@Autowired
private LogMessageProducer logMessageProducer;

// .... 具体的代码

// 调用即可。
logMessageProducer.logProducer(TOPIC_NAME, JSON.toJSONString(apiServiceLog));

4.2 消费者

  • 说明

声明的这个bean的方法,需要跟配置文件里面配置的一样,就是xxxx-out-0xxxx的名字。

import cn.sdibc.mgt_service.api.domain.ApiServiceLog;
import cn.sdibc.tools_sdk.core.utils.StringUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.function.Consumer;

/**
 * 日志消息消费者
 */
@Slf4j
@Component
public class ApiLogMessageConsumer {

    @Bean
    public Consumer<String> apiServiceLog() {
        return message -> {
            // 校验一下有没有
            if (StringUtils.isBlank(message)) {
                return;
            }
            try {
                // 存在的话,转一下类型
                ApiServiceLog apiServiceLog = JSON.parseObject(message, ApiServiceLog.class);
                // 执行保存
                apiServiceLogMapper.insertApiServiceLog(apiServiceLog);
            } catch (Exception e) {
                log.error("消费操作日志失败:{}", e.getMessage());
            }

        };
    }
}