實戰:SpringBoot集成rabbitmq並實現延時隊列

集成rabbitmq

前言

消息隊列中間件是分佈式系統中重要的組件,主要解決應用耦合,異步消息,流量削鋒等問題

實現高性能,高可用,可伸縮和最終一致性架構。RabbitMQ是實現了高級消息隊列協議(AMQP)的開源消息,具有較高的系統吞吐量、可靠性、消息持久化、免費等優點,在軟件項目中具有非常廣泛的應用。

項目介紹

本項目以springboot集成rabbitmq,引導如何設計和優雅地集成rabbitmq相關的組件,並實現用死信隊列實現延遲消息隊列。

項目設計與實戰

配置

maven依賴

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> 
 </parent>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置文件

spring.rabbitmq.host=192.168.202.128
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

組件設計與實現

Exchange(交換機)

定義交換機名稱、類型、持久化、延時交換機名稱等屬性。

public interface IRabbitMqExchange {

    /**
     * Exchange(交換機) 的名稱
     * */
    String exchangeName();

    /**
     * exchange類型 DIRECT("direct"), FANOUT("fanout"), TOPIC("topic"), HEADERS("headers")
     * */
    default String type(){return "topic";}

    /**
     * 是否持久化
     */
    default boolean durable(){return true;}

    /**
     * 當所有隊列在完成使用此exchange時,是否刪除
     */
    default boolean autoDelete(){return false;}

    /**
     * 是否允許直接binding
     * 如果是true的話 則不允許直接binding到此 exchange
     */
    default boolean internal(){ return false;}

    /**
     * 其他的一些參數設置
     */
    default Map<String, Object> arguments(){ return null; }

    /**
     * 延時 Exchange
     * */
    default String delayExchangeName() {return "delay."+exchangeName();}

}

路由(Routing)

public interface IRabbitMqRouting {
    /**
     * rabbitmq路由key
     * */
    String routingKey();

}

隊列(Queue)

定義隊列名稱、持久化、延時隊列名稱等屬性

public interface IRabbitMqQueue {
    /**
     * Queue(隊列)名稱
     */
    String queueName();

    /**
     * 是否持久化
     * */
    default boolean durable() {return true;}

    /**
     * 排他性
     * */
    default boolean exclusive(){return false;}

    /**
     * 是否自動刪除
     * */
    default boolean autoDelete(){return false;}

    /**
     * 其他屬性設置
     * */
    default Map<String, Object> arguments() { return null; }

    /**
     * 默認的延時隊列名稱
     * */
    default String delayQueueName(){return "delay."+this.queueName();}

}

綁定關係(Binding)

定義了 交換機(Exchange)-路由(Routing)-消息隊列(Queue)的綁定關係,以及定義是否支持延時消息。

public interface IRabbitMqBinding {
    /**
     * 需要綁定的exchange(交換機)
     * */
    IRabbitMqExchange exchange();

    /**
     * 需要綁定的routing(路由)
     * */
    IRabbitMqRouting routing();

    /**
     * 需要綁定的queue(隊列)
     * */
    IRabbitMqQueue queue();

    /**
     * 消息隊列是否允許延時
     * */
    boolean allowDelay();
}

默認註冊器

實現了交換機、消息隊列、綁定關係的註冊。如果綁定關係中定義支持延遲消息,則額外註冊一個延時交換機和死信隊列,以實現延時消息推送的功能。

public class DefaultRabbitMqRegister implements IRabbitMqRegister, SmartLifecycle {

    ConnectionFactory connectionFactory;

    Channel channel;

    public DefaultRabbitMqRegister() {
    }

    public DefaultRabbitMqRegister(ConnectionFactory connectionFactory) {
        this.connectionFactory = connectionFactory;
    }

    @PostConstruct
    public void init() {
        channel = connectionFactory.createConnection().createChannel(false);
    }

    @Override
    public void registerExchange(IRabbitMqExchange... exchanges) throws IOException {
        for (IRabbitMqExchange exchange : exchanges) {
            channel.exchangeDeclare(exchange.exchangeName(), exchange.type(), exchange.durable(), exchange.autoDelete(), exchange.internal(), exchange.arguments());
        }
    }

    @Override
    public void registerQueue(IRabbitMqQueue... queues) throws IOException {
        for (IRabbitMqQueue queue : queues) {
            channel.queueDeclare(queue.queueName(), queue.durable(), queue.exclusive(), queue.autoDelete(), queue.arguments());
        }
    }

    @Override
    public void registerBinding(IRabbitMqBinding... bindings) throws IOException {
        for (IRabbitMqBinding binding : bindings) {
            channel.queueBind(binding.queue().queueName(), binding.exchange().exchangeName(), binding.routing().routingKey());
            if (binding.allowDelay()) {
                registerDelayBinding(binding);
            }
        }
    }

    /**
     * 創建一個內部的 死信隊列 用來實現 延時隊列
     */
    private void registerDelayBinding(IRabbitMqBinding binding) throws IOException {
        IRabbitMqExchange exchange = binding.exchange();
        // 註冊一個延時的消息交換機
        channel.exchangeDeclare(exchange.delayExchangeName(), exchange.type(), exchange.durable(), exchange.autoDelete(), exchange.internal(), exchange.arguments());
        // 註冊一個死信隊列  設置消息超時後,將消息轉發到原來的Router隊列
        IRabbitMqQueue queue = binding.queue();
        Map<String, Object> arguments = queue.arguments();
        if (arguments == null) {
            arguments = new HashMap<>(4);
        }
        arguments.put("x-dead-letter-exchange", binding.exchange().exchangeName());
        arguments.put("x-dead-letter-routing-key", binding.routing().routingKey());
        channel.queueDeclare(queue.delayQueueName(), queue.durable(), queue.exclusive(), queue.autoDelete(), arguments);
        // 將交換機和隊列綁定
        channel.queueBind(queue.delayQueueName(), exchange.delayExchangeName(), binding.routing().routingKey());
    }

    private List<MessageListenerContainer> listenerContainers = new LinkedList<>();

    @Override
    public void listenerQueue(IRabbitMqListener listener, IRabbitMqQueue... queues) {
        String[] queueNames = new String[queues.length];
        for (int idx = 0; idx < queues.length; idx++) {
            queueNames[idx] = queues[idx].queueName();
        }
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        // 配置手動確認
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        container.setQueueNames(queueNames);
        container.setMessageListener(listener);
        listenerContainers.add(container);
    }

    @Override
    public void start() {
        for (MessageListenerContainer container : listenerContainers) {
            container.start();
        }
    }

    @Override
    public void stop() {
    }

    @Override
    public boolean isRunning() {
        return false;
    }

    @Override
    public boolean isAutoStartup() {
        return true;
    }

    @Override
    public void stop(Runnable runnable) {
    }

    @Override
    public int getPhase() {
        return 9999;
    }
}

消息監聽器

public interface IRabbitMqListener {
    /**
     * 處理rabbitMq的消息
     * */
    boolean handleMessage(Object obj);

}

抽象實現類(具體的消費者繼承該抽象類,重寫handleMessage()方法,實現消費邏輯)

public abstract class AbstractMessageListener implements ChannelAwareMessageListener, IRabbitMqListener {

    private Logger logger = LoggerFactory.getLogger(AbstractMessageListener.class);

    private MessageConverter messageConverter = new Jackson2JsonMessageConverter();

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long tag = message.getMessageProperties().getDeliveryTag();
        try {
            Object obj = messageConverter.fromMessage(message);
            boolean handleResult = handleMessage(obj);
            if (handleResult) {
                channel.basicAck(tag, false);
            } else {
                logger.error("消息處理失敗 message: {}", message);
                channel.basicNack(tag, false, false);
            }
        } catch (Exception e) {
            channel.basicNack(tag, false, false);
            logger.error("消息處理異常 message: " + message + " " + e.getMessage(), e);
        }
    }

}

消息發送服務類

實現發送消息、發送延時消息等功能

public class RabbitMqServiceImpl implements IRabbitMqService, RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    private Logger logger = LoggerFactory.getLogger(RabbitMqServiceImpl.class);

    @Autowired
    protected RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init() {
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
    }

    @Override
    public void send(IRabbitMqExchange exchange, IRabbitMqRouting routing, Object msg) {
        CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend(exchange.exchangeName(), routing.routingKey(), msg, correlationId);
    }

    @Override
    public void send(IRabbitMqExchange exchange, IRabbitMqRouting routing, Object msg, long delay) {
        CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
        if (delay > 0) {
            MessagePostProcessor processor = (Message message) -> {
                message.getMessageProperties().setExpiration(delay + "");
                return message;
            };
            rabbitTemplate.convertAndSend(exchange.delayExchangeName(), routing.routingKey(), msg, processor, correlationId);
        } else {
            rabbitTemplate.convertAndSend(exchange.exchangeName(), routing.routingKey(), msg, correlationId);
        }
    }
    /**
     * 消息發送的回調
     *
     * @param correlationId 消息Id
     * @param ack           是否成功的標示
     * @param cause         錯誤原因
     */
    @Override
    public void confirm(CorrelationData correlationId, boolean ack, String cause) {
        if (ack) {
            logger.info("消息發送成功 correlationId: {} cause: {}", correlationId, cause);
        } else {
            logger.error("消息發送失敗 correlationId: {} cause: {}", correlationId, cause);
        }
    }

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        logger.info("returnedMessage message: {} replyCode: {} exchange: {} routingKey: {}", message, replyCode, exchange, routingKey);
    }

}

實戰

使用枚舉定義消息隊列配置

定義測試Exchange:mq.exchange.test

/**
 * RabbitMq Exchange(交換機)定義
 * */
public enum RabbitMqExchange implements IRabbitMqExchange {

    MQ_EXCHANGE_TEST("mq.exchange.test") ;

    private String exchangeName;

    @Override
    public String exchangeName() {
        return this.exchangeName;
    }

    RabbitMqExchange(String exchangeName){
        this.exchangeName = exchangeName;
    }
}

定義測試Queue:mq.queue.test

public enum RabbitMqQueue implements IRabbitMqQueue {
    MQ_QUEUE_TEST("mq.queue.test");

    private String queueName;

    @Override
    public String queueName() {
        return this.queueName;
    }

    RabbitMqQueue(String queueName){
        this.queueName = queueName;
    }

}

定義測試Routing:mq.routing.test

/**
 * RabbitMq routing(路由定義)
 * */
public enum RabbitMqRouting implements IRabbitMqRouting {
    MQ_ROUTING_TEST("mq.routing.test");

    private String routingKey;

    @Override
    public String routingKey() {
        return this.routingKey;
    }

    RabbitMqRouting(String routingKey){
        this.routingKey = routingKey;
    }
}

定義綁定關係:

/**
 * RabbitMq Exchange(交換機) Routing(路由) Queue(隊列) 的綁定關係
 * */
public enum RabbitMqBinding implements IRabbitMqBinding {

    MQ_BINDING_TEST(RabbitMqExchange.MQ_EXCHANGE_TEST,RabbitMqRouting.MQ_ROUTING_TEST,RabbitMqQueue.MQ_QUEUE_TEST,true);

    /**
     * exchange(交換機)
     */
    IRabbitMqExchange exchange;
    /**
     * routing(路由)
     */
    IRabbitMqRouting routing;
    /**
     * queue(隊列)
     */
    IRabbitMqQueue queue;
    /**
     * 是否允許延時
     */
    boolean allowDelay = false;

    RabbitMqBinding(IRabbitMqExchange exchange,IRabbitMqRouting routing,IRabbitMqQueue queue){
        this.exchange = exchange;
        this.routing = routing;
        this.queue = queue;
    }

    RabbitMqBinding(IRabbitMqExchange exchange,IRabbitMqRouting routing,IRabbitMqQueue queue,boolean allowDelay){
        this.exchange = exchange;
        this.routing = routing;
        this.queue = queue;
        this.allowDelay = allowDelay;
    }

    @Override
    public IRabbitMqExchange exchange() {
        return this.exchange;
    }

    @Override
    public IRabbitMqRouting routing() {
        return this.routing;
    }

    @Override
    public IRabbitMqQueue queue() {
        return this.queue;
    }

    @Override
    public boolean allowDelay() {
        return this.allowDelay;
    }
}

測試消費者類

public class TestConsumer extends AbstractMessageListener {

    Logger logger = LoggerFactory.getLogger(TestConsumer.class);
    @Override
    public boolean handleMessage(Object obj) {
        logger.info("rabbitmq消費者開始消費,消息內容:" +obj.toString());
        return true;
    }
}

啓動項目

image-20200705215834650

登錄rabbitmq控制檯,已經自動創建了 交換機和延遲交換機,消息隊列和死信隊列

測試發送消息

@Test
public void testSendMq(){
     logger.info("生產者發送消息到mq");
     rabbitMqService.send(RabbitMqExchange.MQ_EXCHANGE_TEST, RabbitMqRouting.MQ_ROUTING_TEST,"測試發送消息");
 }

image-20200705221516347

image-20200705221535366

測試發送延時消息(60秒)

 @Test
public void testSendDelayMq(){
    logger.info("生產者發送延遲消息到mq");
     rabbitMqService.send(RabbitMqExchange.MQ_EXCHANGE_TEST, RabbitMqRouting.MQ_ROUTING_TEST,"測試發送延時消息60s",60*1000);
 }

代碼獲取

這個項目包含很多輪子實例,跪求star

https://github.com/pengziliu/GitHub-code-practice

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章