RabbitMQ 安裝部署(New)
elang環境與MQ版本一定要對應,否則無法啓動,Rabbit版本與插件版本一定要對應,負責無法加載插件
版本信息 | |
---|---|
centos | 7.3.0 |
erlang | Version: 23.0.2,Release: 2.el7 |
RabbitMQ | 3.8.0-1 |
rabbitmq_delayed_message_exchange | 3.8 |
安裝腳本步驟:
-
卸載erlang
service rabbitmq-server stop --停止服務 yum list | grep erlang --查詢當前環境 yum -y remove erlang-* --卸載 yum remove erlang.x86_64 --移除
-
卸載rabbitMq
yum list | grep rabbitmq --當前是否安裝 yum -y remove rabbitmq-server.noarch --移除安裝文件
-
安裝erlang
yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm rpm -Uvh erlang-solutions-1.0-1.noarch.rpm yum -y install epel-release sudo yum install erlang
[root@nginx-1 mq]# yum info erlang --校驗版本信息 Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile Installed Packages Name : erlang Arch : x86_64 Version : 23.0.2 Release : 2.el7 Size : 0.0 Repo : installed From repo : erlang-solutions Summary : General-purpose programming language and runtime environment URL : http://www.erlang.org License : ERPL Description : Erlang is a general-purpose programming language and runtime : environment. Erlang has built-in support for concurrency, distribution : and fault tolerance. Erlang is used in several large telecommunication : systems from Ericsson.
-
安裝rabbitMq
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.0/rabbitmq-server-3.8.0-1.el7.noarch.rpm yum -y install socat --安裝socat依賴 rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc --導入什麼簽名 rpm -ivh rabbitmq-server-3.8.0-1.el7.noarch.rpm
-
部署插件
rabbitmq-delayed-message-exchange --插件名稱 https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/tag/v3.8.0 --下載地址,選擇3.8
RabbitMQ的有些插件沒有集成在初始的安裝中,它們需要額外安裝,這些文件的後綴爲
.ez
,安裝時需要將.ez
文件拷貝到安裝的插件目錄。以下是不同系統中默認安裝的插件目錄路徑:插件目錄 Linux /usr/lib/rabbitmq/lib/rabbitmq_server-${version}/plugins Windows C:\Program Files\RabbitMQ\rabbitmq_server-version\plugins(安裝rabbitmq的目錄) Homebrew /usr/local/Cellar/rabbitmq/version/plugins rabbitmq-plugins enable rabbitmq_delayed_message_exchange --啓用插件 rabbitmq-plugins disable rabbitmq_delayed_message_exchange --棄用插件
-
授權用戶訪問
rabbitmq-plugins enable rabbitmq_management --啓用網頁插件 添加用戶:rabbitmqctl add_user admin admin 添加權限:rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*" 修改用戶角色rabbitmqctl set_user_tags admin administrator 然後就可以遠程訪問了,然後可直接配置用戶權限等信息。
-
啓動命令腳本
service rabbitmq-server restart --重啓 service rabbitmq-server stop --停止
Spring搭建延時隊列-註解方式
此處應用於Spring、SpringBoot等開源框架
-
添加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
-
定義工廠鏈接信息
package com.soul.home.lws.system.conf; import com.soul.home.lws.conf.properties.AutoConfigRabbitMqProperties; import com.soul.home.lws.conf.properties.AutoConfigTomcatProperties; import com.soul.home.lws.system.mq.MQRepublishMessageRecoverer; import org.springframework.amqp.AmqpException; import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean; import org.springframework.amqp.rabbit.connection.*; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.amqp.support.converter.SimpleMessageConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.RetryContext; import org.springframework.retry.backoff.BackOffContext; import org.springframework.retry.backoff.BackOffInterruptedException; import org.springframework.retry.backoff.BackOffPolicy; import org.springframework.retry.backoff.ExponentialBackOffPolicy; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.retry.support.RetryTemplate; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * @author gaoguofan * @date 2020/6/15 */ @Configuration @EnableConfigurationProperties(AutoConfigRabbitMqProperties.class) public class AutoConfigurationRabbitMqConfig { @Autowired AutoConfigRabbitMqProperties autoConfigRabbitMqProperties; @Bean public ConnectionFactory connectionFactory() { com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = new com.rabbitmq.client.ConnectionFactory(); rabbitConnectionFactory.setHost(autoConfigRabbitMqProperties.getHost()); rabbitConnectionFactory.setPort(autoConfigRabbitMqProperties.getPort()); rabbitConnectionFactory.setUsername(autoConfigRabbitMqProperties.getUserName()); rabbitConnectionFactory.setPassword(autoConfigRabbitMqProperties.getPassword()); rabbitConnectionFactory.setRequestedChannelMax(autoConfigRabbitMqProperties.getRequestedChannelMax()); ConnectionFactory connectionFactory = new AbstractConnectionFactory(rabbitConnectionFactory) { @Override public Connection createConnection() throws AmqpException { try { return new SimpleConnection(rabbitConnectionFactory.newConnection(), 100000); } catch (Exception e) { e.printStackTrace(); } return null; } }; return connectionFactory; } // 消費者消費消息,首先進入重試邏輯中,如果有異常則根據重試機制重試 // 若超過重試機制,則執行下面StatelessRetryOperationsInterceptorFactoryBean中的messageRecoverer類方法 // 所以,手動Ack的情況下,此處纔是策略中的重新發送回mq消息隊列中的正確做法 // 而在ErrorHandler中,發生異常只能作爲記錄信息存在,不能依靠其業務進行重新發送,原因如下: // 默認都會有重試,假定重試三次,三次都出異常,那麼ErrorHandler中的業務程序將會運行三次 --- 雪崩 @Bean public RetryTemplate retryTemplate() { RetryTemplate retryTemplate = new RetryTemplate(); SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(); simpleRetryPolicy.setMaxAttempts(1); // retry Policy 指數退避策略,必須使用指數,內網連接很快 ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy(); exponentialBackOffPolicy.setInitialInterval(500); exponentialBackOffPolicy.setMultiplier(10.0d); exponentialBackOffPolicy.setMaxInterval(10000); retryTemplate.setBackOffPolicy(exponentialBackOffPolicy); retryTemplate.setRetryPolicy(simpleRetryPolicy); return retryTemplate; } @Bean public StatelessRetryOperationsInterceptorFactoryBean statelessRetryOperationsInterceptorFactoryBean( MQRepublishMessageRecoverer messageRecoverer) { StatelessRetryOperationsInterceptorFactoryBean interceptorFactoryBean = new StatelessRetryOperationsInterceptorFactoryBean(); interceptorFactoryBean.setMessageRecoverer(messageRecoverer); interceptorFactoryBean.setRetryOperations(retryTemplate()); return interceptorFactoryBean; } @Bean() public RabbitTemplate rabbitTemplate() { RabbitTemplate rabbitTemplate = new RabbitTemplate(); rabbitTemplate.setConnectionFactory(connectionFactory()); return rabbitTemplate; } @Bean public SimpleMessageConverter simpleMessageConverter() { return new SimpleMessageConverter(); } }
-
定義exchange&queue
package com.soul.home.lws.system.mq; import org.springframework.amqp.core.CustomExchange; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.QueueBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /** * durable屬性和auto-delete屬性可以同時生效; * durable屬性和exclusive屬性會有性質上的衝突,兩者同時設置時,僅exclusive屬性生效; * auto_delete屬性和exclusive屬性可以同時生效; * 除此之外, * Queue的“Exlusive owner”對應的是connection而不是channel; * Consumer存在於某個channel上的; * @author gaoguofan * @date 2020/6/15 */ @Configuration public class MQMessageQueuesConfig { private Boolean exclusive = false; private Boolean autoDelete = false; private Boolean durable = true; @Bean(MQMessageQueueNames.DELAY_EXCHANGE_NAME) public DirectExchange directExchange() { DirectExchange directExchange = new DirectExchange(MQMessageQueueNames.DELAY_EXCHANGE_NAME, true, false); return directExchange; } @Bean(MQMessageQueueNames.DEAD_LETTER_EXCHANGE) public DirectExchange deadLetterExchange() { DirectExchange directExchange = new DirectExchange(MQMessageQueueNames.DEAD_LETTER_EXCHANGE, true, false); return directExchange; } @Bean(MQMessageQueueNames.DELAYED_PLUGS_EXCHANGE) public CustomExchange customExchange() { Map<String, Object> args = new HashMap<>(); args.put("x-delayed-type", "direct"); return new CustomExchange(MQMessageQueueNames.DELAYED_PLUGS_EXCHANGE, "x-delayed-message", true, false, args); } /** * 聲明延時隊列C 不設置TTL * 並綁定到對應的死信交換機 * @return */ @Bean("delayQueueC") public Queue delayQueueC(){ Map<String, Object> args = new HashMap<>(3); // x-dead-letter-exchange 這裏聲明當前隊列綁定的死信交換機 args.put("x-dead-letter-exchange", MQMessageQueueNames.DEAD_LETTER_EXCHANGE); // x-dead-letter-routing-key 這裏聲明當前隊列的死信路由key args.put("x-dead-letter-routing-key", MQMessageQueueNames.DEAD_LETTER_QUEUEC_ROUTING_KEY); return QueueBuilder.durable(MQMessageQueueNames.DELAY_QUEUEC_NAME).withArguments(args).build(); } /** * 聲明死信隊列C 用於接收延時任意時長處理的消息 * @return */ @Bean("deadLetterQueueC") public Queue deadLetterQueueC(){ return new Queue(MQMessageQueueNames.DEAD_LETTER_QUEUEC_NAME); } @Bean(MQMessageQueueNames.DELAYED_PLUGS_QUEUEC_NAME) public Queue autoDelayMQPlugs() { return new Queue(MQMessageQueueNames.DELAYED_PLUGS_QUEUEC_NAME); } }
-
進行綁定
package com.soul.home.lws.system.mq; import org.springframework.amqp.core.*; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author gaoguofan * @date 2020/6/15 */ @Configuration public class MQMessageQueueKeyBindConfg { /** * 聲明延時列C綁定關係 * @param queue * @param exchange * @return */ @Bean public Binding delayBindingC(@Qualifier("delayQueueC") Queue queue, @Qualifier(MQMessageQueueNames.DELAY_EXCHANGE_NAME) DirectExchange exchange){ return BindingBuilder.bind(queue).to(exchange).with(MQMessageQueueNames.DELAY_QUEUEC_ROUTING_KEY); } /** * 聲明死信隊列C綁定關係 * @param queue * @param exchange * @return */ @Bean public Binding deadLetterBindingC(@Qualifier("deadLetterQueueC") Queue queue, @Qualifier(MQMessageQueueNames.DEAD_LETTER_EXCHANGE) DirectExchange exchange){ return BindingBuilder.bind(queue).to(exchange).with(MQMessageQueueNames.DEAD_LETTER_QUEUEC_ROUTING_KEY); } /** * 插件延時隊列綁定關係 * @param queue * @param customExchange * @return */ @Bean public Binding bindingNotify(@Qualifier(MQMessageQueueNames.DELAYED_PLUGS_QUEUEC_NAME) Queue queue, @Qualifier(MQMessageQueueNames.DELAYED_PLUGS_EXCHANGE) CustomExchange customExchange) { return BindingBuilder.bind(queue).to(customExchange).with(MQMessageQueueNames.DELAYED_PLUGS_ROUTING_KEY).noargs(); } }
-
定義生產者
package com.soul.home.lws.system.controler; import com.rabbitmq.client.AMQP; import com.soul.home.lws.route.conf.Api; import com.soul.home.lws.sql.dto.BaseDto; import com.soul.home.lws.system.mq.ExpirationMessagePostProcessor; import com.soul.home.lws.system.mq.MQMessageQueueNames; import com.soul.home.lws.system.mq.MQMessageQueuesConfig; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; /** * @author gaoguofan * @date 2020/6/15 */ @RestController public class RabbitMessageController { @Autowired RabbitTemplate rabbitTemplate; @GetMapping(value = "ts") public BaseDto sendMsg(Integer ttl) { rabbitTemplate.convertAndSend(MQMessageQueueNames.DELAY_EXCHANGE_NAME, MQMessageQueueNames.DELAY_QUEUEC_ROUTING_KEY, "HELLO" + ttl, new ExpirationMessagePostProcessor(ttl, null)); return new BaseDto(0, null); } @GetMapping(value = "ts2") public BaseDto sendMsg2(Integer ttl) { Map<String, Object> headers = new HashMap<>(); headers.put("x-delay", ttl); AMQP.BasicProperties.Builder props = new AMQP.BasicProperties.Builder().headers(headers); rabbitTemplate.convertAndSend(MQMessageQueueNames.DELAYED_PLUGS_EXCHANGE, MQMessageQueueNames.DELAYED_PLUGS_ROUTING_KEY, "HELLO" + ttl, new ExpirationMessagePostProcessor(ttl, headers)); return new BaseDto(0, null); } }
-
定義消費者
package com.soul.home.lws.system.mq; import com.soul.home.lws.system.mq.listener.MemberChargeNoticeListener; import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; import org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.handler.annotation.Headers; import org.springframework.messaging.handler.annotation.Payload; import java.util.Map; /** * @author gaoguofan * @date 2020/6/15 */ @Configuration public class MQMessageListenerContainerConfig { @Autowired ConnectionFactory connectionFactory; @Autowired MQErrorHandler mqErrorHandler; @Autowired StatelessRetryOperationsInterceptorFactoryBean retryOperationsInterceptorFactoryBean; @Autowired MemberChargeNoticeListener memberChargeNoticeListener; @Bean public SimpleMessageListenerContainer simpleMessageListenerContainer() { SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(); simpleMessageListenerContainer.setConnectionFactory(connectionFactory); simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL); // simpleMessageListenerContainer.setQueueNames(MQMessageQueueNames.DEAD_LETTER_QUEUEC_NAME); simpleMessageListenerContainer.setQueueNames(MQMessageQueueNames.DEAD_LETTER_QUEUEC_NAME, MQMessageQueueNames.DELAYED_PLUGS_QUEUEC_NAME); simpleMessageListenerContainer.setMessageListener(memberChargeNoticeListener); simpleMessageListenerContainer.setConcurrentConsumers(5); simpleMessageListenerContainer.setMaxConcurrentConsumers(5); simpleMessageListenerContainer.setAdviceChain(retryOperationsInterceptorFactoryBean.getObject()); simpleMessageListenerContainer.setErrorHandler(mqErrorHandler); return simpleMessageListenerContainer; } // /** // * 使用註解 @RabbitListener 自定義消費者情況部署 // * @EnableRabbit // * @RabbitListener(queues = MQMessageQueueNames.NULL) // * @param connectionFactory // * @return // */ // @Bean // public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){ // //SimpleRabbitListenerContainerFactory發現消息中有content_type有text就會默認將其轉換成string類型的 // SimpleRabbitListenerContainerFactory simpleMessageListenerContainer = new SimpleRabbitListenerContainerFactory(); // simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL); // simpleMessageListenerContainer.setConcurrentConsumers(5); // simpleMessageListenerContainer.setMaxConcurrentConsumers(5); // simpleMessageListenerContainer.setAdviceChain(retryOperationsInterceptorFactoryBean.getObject()); // simpleMessageListenerContainer.setErrorHandler(mqErrorHandler); // simpleMessageListenerContainer.setConnectionFactory(connectionFactory); // return simpleMessageListenerContainer; // } // /** // * 隊列調度器 // * @param body // * @param headers // */ // @RabbitListener(queues = MQMessageQueueNames.NULL) // public void handleMessage(@Payload String body, @Headers Map<String,Object> headers){ // // } }
package com.soul.home.lws.system.mq.listener; import java.util.Map; import org.apache.log4j.Logger; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener; import org.springframework.amqp.support.converter.MessageConversionException; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.amqp.support.converter.SimpleMessageConverter; import org.springframework.beans.factory.annotation.Autowired; import com.rabbitmq.client.Channel; import org.springframework.stereotype.Component; /** * */ @Component public class MemberChargeNoticeListener implements ChannelAwareMessageListener { private long DELIVERIED_TAG = -1l; private static final Logger logger = Logger.getLogger(MemberChargeNoticeListener.class); @Autowired private SimpleMessageConverter msgConverter; @Override public void onMessage(Message message, Channel channel) throws Exception { Object obj = null; try { obj = msgConverter.fromMessage(message); logger.info(obj.toString()); } catch (MessageConversionException e) { // logger.error("convert MQ message error.", e); } finally { long deliveryTag = message.getMessageProperties().getDeliveryTag(); if (deliveryTag != DELIVERIED_TAG) { channel.basicAck(deliveryTag, false); message.getMessageProperties().setDeliveryTag(DELIVERIED_TAG); // logger.info("revice and ack msg: " + (obj == null ? message : new String((byte[]) obj))); } } if (obj == null) { return; } boolean flag = false; if (obj instanceof Map<?, ?>) { Map<?, ?> cpaMsg = (Map<?, ?>) obj; // logger.info("cpaMsg : " + cpaMsg); String url = cpaMsg.get("url").toString(); String storeId = cpaMsg.get("storeId").toString(); String openId = cpaMsg.get("openId").toString(); String storeName = cpaMsg.get("storeName").toString(); String memberLevel = cpaMsg.get("memberLevel").toString(); String chargeAmount = cpaMsg.get("chargeAmount").toString(); String balanceAmount = cpaMsg.get("balanceAmount").toString(); String chargeTime = cpaMsg.get("chargeTime").toString(); } else { // logger.warn("not a map msg, ingore it."); } if (!flag) { // logger.error("hanler message " + obj + " failed, throw a exception, and it will be retried."); throw new RuntimeException("hanler message " + obj + " failed."); } } }
-
輔助Class列表
-
消息頭信息設置
package com.soul.home.lws.system.mq; import org.springframework.amqp.AmqpException; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessagePostProcessor; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.support.Correlation; import java.util.Map; /** * @author gaoguofan * @date 2020/6/15 */ public class ExpirationMessagePostProcessor implements MessagePostProcessor { private final Integer ttl; // 毫秒 private Map<String, Object> headers; public ExpirationMessagePostProcessor(Integer ttl, Map<String, Object> headers) { this.ttl = ttl; this.headers = headers; } @Override public Message postProcessMessage(Message message) throws AmqpException { MessageProperties messageProperties = message.getMessageProperties(); if (headers != null) { for (String key : headers.keySet()) { messageProperties.getHeaders().put(key, headers.get(key)); } } message.getMessageProperties().setExpiration(ttl.toString()); return message; } }
-
異常信息複製記錄
package com.soul.home.lws.system.mq; import java.lang.reflect.Field; import org.apache.commons.lang.reflect.FieldUtils; import org.apache.log4j.Logger; import org.springframework.amqp.core.Message; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.amqp.support.converter.SimpleMessageConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ErrorHandler; @Component public class MQErrorHandler implements ErrorHandler { private static final Logger logger = Logger.getLogger(MQErrorHandler.class); // @Autowired // private RedisService redisService; @Autowired private SimpleMessageConverter msgConverter; @Override public void handleError(Throwable cause) { Field mqMsgField = FieldUtils.getField(MQListenerExecutionFailedException.class, "mqMsg", true); if (mqMsgField != null) { try { Message mqMsg = (Message) mqMsgField.get(cause); Object msgObj = msgConverter.fromMessage(mqMsg); logger.info(Thread.currentThread().getName() + "-" + "handle MQ msg: " + msgObj + " failed, record it to mq.", cause); // redisService.zadd(App.MsgErr.MQ_MSG_ERR_RECORD_KEY, new Double(new Date().getTime()), msgObj.toString()); } catch (Exception e) { e.printStackTrace(); } } else { logger.error("An error occurred.", cause); } } }
-
Exception封裝
package com.soul.home.lws.system.mq; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException; /** * */ public class MQListenerExecutionFailedException extends ListenerExecutionFailedException { private static final long serialVersionUID = 1L; private Message mqMsg; public MQListenerExecutionFailedException(String msg, Throwable cause, Message mqMsg) { super(msg, cause, mqMsg); } public MQListenerExecutionFailedException(String msg, Message mqMsg, Throwable cause) { this(msg, cause, mqMsg); this.mqMsg = mqMsg; } public Message getMqMsg() { return mqMsg; } }
-
重試機制失敗後重新路由,MessageRecorve
package com.soul.home.lws.system.mq; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Map; //import org.apache.log4j.Logger; //import org.springframework.amqp.rabbit.core.RabbitTemplate; //import org.springframework.amqp.support.converter.MessageConverter; //import org.springframework.beans.factory.annotation.Autowired; import org.apache.log4j.Logger; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.retry.MessageRecoverer; import org.springframework.amqp.core.Message; import org.springframework.amqp.support.converter.SimpleMessageConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component public class MQRepublishMessageRecoverer implements MessageRecoverer { private static final Logger logger = Logger.getLogger(MQRepublishMessageRecoverer.class); // @Autowired @Qualifier("rabbitTemplate") private RabbitTemplate rabbitTemplate; // @Autowired private SimpleMessageConverter msgConverter; @Override public void recover(Message message, Throwable cause) { Map<String, Object> headers = message.getMessageProperties().getHeaders(); headers.put("x-exception-stacktrace", getStackTraceAsString(cause)); headers.put("x-exception-message", cause.getCause() != null ? cause.getCause().getMessage() : cause.getMessage()); headers.put("x-original-exchange", message.getMessageProperties().getReceivedExchange()); headers.put("x-original-routingKey", message.getMessageProperties().getReceivedRoutingKey()); // this.rabbitTemplate.send(message.getMessageProperties().getReceivedExchange(), message.getMessageProperties().getReceivedRoutingKey(), message); // logger.error("handler msg (" + msgConverter.fromMessage(message) + ") err, republish to mq.", cause); } private String getStackTraceAsString(Throwable cause) { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter, true); cause.printStackTrace(printWriter); return stringWriter.getBuffer().toString(); } }
-
靜態屬性配置表
package com.soul.home.lws.system.mq; /** * @author gaoguofan * @date 2020/6/15 */ public interface MQMessageQueueNames { // NULL-DEFUALT 延時隊列 public static final String NULL = "NULL"; // 延時隊列交換機 public static final String DELAY_EXCHANGE_NAME = "delay.queue.business.exchange"; // 死信隊列交換機 public static final String DEAD_LETTER_EXCHANGE = "delay.queue.deadletter.exchange"; // rabbitmq_delayed_message_exchange 插件交換機 public static final String DELAYED_PLUGS_EXCHANGE = "delay.queue.rabbit.plugs.delayed.exchange"; // 延時隊列隊列名稱 public static final String DELAY_QUEUEC_NAME = "delay.queue.business.queue"; // 延遲隊列路由Key public static final String DELAY_QUEUEC_ROUTING_KEY = "delay.queue.demo.business.queue.routingkey"; // rabbitmq_delayed_message_exchange 隊列名稱 public static final String DELAYED_PLUGS_QUEUEC_NAME = "delay.queue.rabbit.plugs.delayed.queue"; // rabbitmq_delayed_message_exchange 隊列路由Key public static final String DELAYED_PLUGS_ROUTING_KEY = "delay.queue.rabbit.plugs.delayed.routingkey"; // 死信隊列隊列名稱 public static final String DEAD_LETTER_QUEUEC_NAME = "delay.queue.deadletter.queue"; // 死信隊列路由Key public static final String DEAD_LETTER_QUEUEC_ROUTING_KEY = "delay.queue.deadletter.delay_anytime.routingkey"; }
-
Spring搭建延時隊列-xml方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- 連接服務配置 -->
<rabbit:connection-factory id="connectionFactory"
host="${rabbitmq.host}" port="${rabbitmq.port}" username="${rabbitmq.username}"
password="${rabbitmq.password}" channel-cache-size="${rabbitmq.channel.cache.size}" />
<bean id="ackManual"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField"
value="org.springframework.amqp.core.AcknowledgeMode.MANUAL" />
</bean>
<bean id="mqErrorHandler" class="com.zefun.wechat.utils.MQErrorHandler"/>
<bean id="msgConverter" class="org.springframework.amqp.support.converter.SimpleMessageConverter" />
<!-- 創建rabbitAdmin 代理類 -->
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>
<rabbit:admin connection-factory="connectionFactory" />
<bean id="retryOperationsInterceptorFactoryBean"
class="org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean">
<property name="messageRecoverer">
<bean class="com.zefun.wechat.utils.MQRepublishMessageRecoverer"/>
</property>
<property name="retryOperations">
<bean class="org.springframework.retry.support.RetryTemplate">
<property name="backOffPolicy">
<bean
class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="initialInterval" value="500" />
<property name="multiplier" value="10.0" />
<property name="maxInterval" value="10000" />
</bean>
</property>
</bean>
</property>
</bean>
<rabbit:queue id="queue_member_charge_notice" name="${rabbitmq.wechat.template.notice.member.charge}" durable="true"
auto-delete="false" exclusive="false" />
<!--路由設置 將隊列綁定,屬於direct類型 -->
<rabbit:binding queue="queue_member_charge_notice" key="${rabbitmq.wechat.template.notice.member.charge}" />
</rabbit:direct-exchange>
<!-- 處理會員充值通知隊列 -->
<bean class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="acknowledgeMode" ref="ackManual" />
<property name="queueNames" value="${rabbitmq.wechat.template.notice.member.charge}" />
<property name="messageListener">
<bean class="com.zefun.wechat.listener.MemberChargeNoticeListener" />
</property>
<property name="concurrentConsumers" value="${rabbitmq.concurrentConsumers}" />
<property name="adviceChain" ref="retryOperationsInterceptorFactoryBean" />
<property name="errorHandler" ref="mqErrorHandler" />
</bean>
</beans>
實現區別
上述延遲隊列中,我們使用了兩種方式,分別是插件方式與原生方式。
原生方式中缺點:較短過期時間後插入,必須等待先前插入的較長過期時間job被消費。
插件方式缺點:有時候在網頁中,無法看到存放好的隊列數據信息。另外,在插件中的最大過期時間49天。