项目名称 | 描述 | 地址 |
---|---|---|
base-data-mybatis | 整合mybatis-plus(实际上官方教程已经很多,只做了自定义插件) | 未完成 |
base-jpa | JPA基础使用 | JPA 数据模型定义 |
base-jpa-query | JPA多表关联使用 | JPA 数据模型关联操作 |
base-log | 日志配置 | SpringBoot日志配置 |
base-rabbit | rabbitMQ简单使用 | RabbitMQ基础使用 |
base-rabbit3 | rabbitMQ一些自定义配置 | 消息确认回调、消息转换以及消息异常处理 |
base-rabbit-delay | rabbitMQ延时队列 | 延时队列和消息重试 |
base-redis | redis简单使用 | RedisTemplate基础使用;Redis实现简单的发布订阅以及配置序列化方式 |
base-redis-lock | redis分布式锁 | Redis分布式锁的简单实现 |
base-redis-delay | 基于有赞的延时消息方案的简单实现 | 延时队列的简单实现 |
base-swagger | swagger使用 | wagger2使用 |
base-mongodb | mongodb简单使用 | MongoDB安装以及Spring Boot整合,MongoDB实体创建以及简单CRUD,MongoDB聚合操作,MongoDB分组去重以及MongoDB联表查询 |
依赖 | 版本 |
---|---|
springboot | 2.0.8.RELEASE |
mongodb | 4.0.14 |
因为涉及的代码较多,所以并不会贴出所有代码。本篇文章涉及的源码下载地址: https://gitee.com/daifyutils/springboot-samples
针对消息的处理主要有两种方式:
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的消息发送的简单操作就截止了。后面我会介绍消费端的内容。
个人水平有限,上面的内容可能存在没有描述清楚或者错误的地方,假如开发同学发现了,请及时告知,我会第一时间修改相关内容。假如我的这篇内容对你有任何帮助的话,麻烦给我点一个赞。你的点赞就是我前进的动力。