一起养成写作习惯!这是我参与「掘金日新计划 · 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
- 说明:
spring.cloud.stream.kafka.binder配置里面有很多配置,对于当前项目来说,默认值即可满足;比如:默认提交偏移量,默认的分区和副本数量为1等配置;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-0的xxxx的名字。
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());
}
};
}
}