前言
一中我们基于rabbitmq的rabbitTemplate.receiveAndConvert来消费消息,这里我们基于channel.basicGet手动ack,站在消费者角度考虑消息重试和消费者性能两个场景
场景
消费者重试
实际的网络环境没有开发环境网络稳定,是不可靠的;在这种条件下,要保证我们业务流程,需要在网络异常导致消费者消费消息失败环节进行重试处理;
消费者性能
手动ack的时候,由于业务的因素,需要异步ack,这里就涉及到系统的性能问题
探究原理
这里我们使用Spring Framework提供的 Retry,它提供了一种简单的方式来实现重试机制。我们使用的就是RetryTemplate模板类,像RedisTemplate和RabbitTemplate一样,我们可以在配置中自定义配置注入需要的Templaet类;
在异步ack上,用的是一个核心线程数和最大线程数都为3、保活时间为0的固定线程池;通过一个定时消费和定时生产任务来测试一下性能。
动手实践
消费者重试
配置RetryTemplate
设置的最大重试次数为3,然后重试间隔为3s;
@Bean
public RetryTemplate retryTemplate() {
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(3);
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(3000);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(simpleRetryPolicy);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
return retryTemplate;
}
注入使用,执行需要重试的方法
@Resource
private RetryTemplate retryTemplate;
@Override
public String consume(String queueName) {
return retryTemplate.execute(context -> doConsume(queueName));
}
可以看到,在方法抛出异常后,会自动进行重试(默认是对所有的异常都会抛出?)
消费者性能
起两个定时任务,都是100ms执行一次;
@Scheduled(fixedRate = 100)
public void consumeMQMessageBatch() {
log.info("耗时: {}", (System.currentTimeMillis() - beginTime) / 1000);
// 可以增加分布式锁
Map<String, Object> queryPamas = new HashMap<>();
queryPamas.put("queueName", "XXXXXX");
log.info("开始消费消息");
String result = HttpUtil.get("XXXXXX", queryPamas);
// 调用ack接口
if (Objects.isNull(result) || result.isEmpty()) {
return;
}
mailService.sendMail(result);
Map<String, Object> ackBody = new HashMap<>();
ackBody.put("messageId", result);
HttpUtil.post("XXXXXX", JSONObject.toJSONString(ackBody));
log.info("ack: {}", result);
}
@Scheduled(fixedRate = 100)
public void product() {
Map<String, String> productBody = new HashMap<>();
message ++;
productBody.put("msg", String.valueOf(message));
productBody.put("routingKey", "XXXXXX");
String post = HttpUtil.post("XXXXXX", JSONObject.toJSONString(productBody));
}
定义异步线程池一个:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
在消费的过程中,有一个异步ack过程,用到了redis
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
while ("0".equals(redisTemplate.opsForValue().get(finalMessageId))) {
Thread.sleep(200);
}
finalChannel.basicAck(deliveryTag, false);
log.info("消费完成消息: {}", message);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (finalChannel!= null) {
finalChannel.close();
}
if (finalConnection != null) {
finalConnection.close();
}
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
}
}
}
});
发送消息时,自定义messag id:
try {
UUID uuid = UUID.randomUUID();
rabbitTemplate.convertAndSend(RabbitConfig.TOPIC_EXCHANGE_SECONDARY_AUTH_NAME, routingKey, msg, message -> {
message.getMessageProperties().setMessageId(uuid.toString());
return message;
});
return msg;
} catch (Exception e) {
return "error";
}
问题
1.RetryTemplate默认是对所有的异常都会重试执行吗?扩展一下就是spring这些template类有什么特点?怎么封装好的?
2.当前的测试还是单线程性能测试,后面的多线程测试的性能呢?
3.异步ack会受限于服务器的配置,所以最佳的线程配置是多少?(核心数、最大线程数、拒绝策略等等)?