RabbitMQ 安装部署(New)
elang环境与MQ版本一定要对应,否则无法启动,Rabbit版本与插件版本一定要对应,负责无法加载插件
# RabbitMQ 安装部署(New) > elang环境与MQ版本一定要对应,否则无法启动,Rabbit版本与插件版本一定要对应,负责无法加载插件 | | 版本信息 | ## 安装脚本步骤: 1. 卸载erlang ```shell 2. 卸载rabbitMq ```shell 3. 安装erlang ```shell ```shell 4. 安装rabbitMq ```shell 5. 部署插件 ```shell RabbitMQ的有些插件没有集成在初始的安装中,它们需要额外安装,这些文件的后缀为`.ez`,安装时需要将`.ez`文件拷贝到安装的插件目录。以下是不同系统中默认安装的插件目录路径: | | 插件目录 | ```shell 6. 授权用户访问 ```shell 7. 启动命令脚本 ```shell
## Spring搭建延时队列-注解方式 > 此处应用于Spring、SpringBoot等开源框架 1. 添加依赖 ```java 2. 定义工厂链接信息 ```java 3. 定义exchange&queue ```java 4. 进行绑定 ```java 5. 定义生产者 ```java 6. 定义消费者 ```java ```java 7. 辅助Class列表 - 消息头信息设置 ```java - 异常信息复制记录 ```java - Exception封装 ```java - 重试机制失败后重新路由,MessageRecorve ```java - 静态属性配置表 ```java
## Spring搭建延时队列-xml方式 ```xml <!-- 连接服务配置 --> <bean id="mqErrorHandler" class="com.zefun.wechat.utils.MQErrorHandler"/> <!-- 创建rabbitAdmin 代理类 --> ## 实现区别 上述延迟队列中,我们使用了两种方式,分别是插件方式与原生方式。 原生方式中缺点:较短过期时间后插入,必须等待先前插入的较长过期时间job被消费。 插件方式缺点:有时候在网页中,无法看到存放好的队列数据信息。另外,在插件中的最大过期时间49天。 | |
---|---|
安装脚本步骤:
-
卸载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天。