动手实现RabbitMq消息队列拉模式(二)

75 阅读2分钟

前言

一中我们基于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));
}

可以看到,在方法抛出异常后,会自动进行重试(默认是对所有的异常都会抛出?)

image.png

消费者性能

起两个定时任务,都是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";
}

image.png

问题

1.RetryTemplate默认是对所有的异常都会重试执行吗?扩展一下就是spring这些template类有什么特点?怎么封装好的?

2.当前的测试还是单线程性能测试,后面的多线程测试的性能呢?

3.异步ack会受限于服务器的配置,所以最佳的线程配置是多少?(核心数、最大线程数、拒绝策略等等)?