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、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.system.mq.error.MQRepublishMessageRecoverer;
import com.soul.home.lws.system.mq.callback.MessageConfirmCallback;
import com.soul.home.lws.system.mq.callback.MessageReturnCallback;
import org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean;
import org.springframework.amqp.rabbit.connection.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
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.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
/**
* @author gaoguofan
* @date 2020/6/15
*/
@Configuration
@EnableConfigurationProperties(AutoConfigRabbitMqProperties.class)
public class AutoConfigurationRabbitMqConfig {
@Autowired
MessageConfirmCallback confirmCallback;
@Autowired
MessageReturnCallback returnCallback;
@Autowired
AutoConfigRabbitMqProperties rabbitMqProperties;
@Bean
public ConnectionFactory connectionFactory() {
com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = new com.rabbitmq.client.ConnectionFactory();
rabbitConnectionFactory.useNio();
rabbitConnectionFactory.setAutomaticRecoveryEnabled(true);
rabbitConnectionFactory.setNetworkRecoveryInterval(10000);
// rabbitConnectionFactory.setNioParams(new NioParams().setNbIoThreads(4));
rabbitConnectionFactory.setHost(rabbitMqProperties.getHost());
rabbitConnectionFactory.setPort(rabbitMqProperties.getPort());
rabbitConnectionFactory.setUsername(rabbitMqProperties.getUserName());
rabbitConnectionFactory.setPassword(rabbitMqProperties.getPassword());
// Clients can be configured to allow fewer channels per connection.
rabbitConnectionFactory.setRequestedChannelMax(rabbitMqProperties.getRequestedChannelMax());
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitConnectionFactory);
// this params server for Ack Exchange & Ack Exchange -> Queue,it shoule be seted true
connectionFactory.setPublisherConfirms(true);
// connectionFactory.setCacheMode(CachingConnectionFactory.CacheMode.CHANNEL);
// connectionFactory.setChannelCacheSize(rabbitMqProperties.getChannelCacheSize());
connectionFactory.setConnectionCacheSize(rabbitMqProperties.getChannelCacheSize());
connectionFactory.setCacheMode(CachingConnectionFactory.CacheMode.CONNECTION);
// 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;
}
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(rabbitMqProperties.getMaxAttempts());
// retry Policy 指数退避策略,必须使用指数,内网连接很快
ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
exponentialBackOffPolicy.setInitialInterval(rabbitMqProperties.getInitialInterval());
exponentialBackOffPolicy.setMultiplier(rabbitMqProperties.getMultiplier());
exponentialBackOffPolicy.setMaxInterval(rabbitMqProperties.getMaxInterval());
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;
}
/**
* 业务回调不同,可配置scope进行单独注入
* @param connectionFactory
* @return
*/
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// Ack Exchange
rabbitTemplate.setConfirmCallback(confirmCallback);
// Ack Exchange -> queue
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(returnCallback);
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.listener;
import com.soul.home.lws.conf.properties.AutoConfigRabbitMqProperties;
import com.soul.home.lws.system.mq.error.MQErrorHandler;
import com.soul.home.lws.system.mq.config.MQMessageQueueNames;
import org.springframework.amqp.core.AcknowledgeMode;
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.MessageListenerContainer;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpoint;
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;
/**
* 监听队列容器
*/
@Configuration
public class MQMessageListenerContainerConfig {
@Autowired
ConnectionFactory connectionFactory;
@Autowired
MQErrorHandler mqErrorHandler;
@Autowired
StatelessRetryOperationsInterceptorFactoryBean retryOperationsInterceptorFactoryBean;
@Autowired
AutoConfigRabbitMqProperties rabbitMqProperties;
@Autowired
MemberChargeNoticeListener memberChargeNoticeListener;
/**
* 消息预取数据计算:
* channel.basicQos() = simpleMessageListenerContainer.setPrefetchCount(250)
* 预取值大小与性能成正比,与数据安全成反比;性能较好值=500,上线2500
* 根据业务特定需求:设置预取值=1、Ack确认是单条确认,或者预取值=250但每次都进行批量确认或者单条确认即可;
* 原因:未必能凑够合适大小进行统一批量Ack,导致宕机后消息重回Ready造成幂等性问题。
* 多个消费客户端在消息确认的时候,使用批量确认,channel.basicAck(deliveryTag, true); 不会造成A客户端确认B的Tags
* 原因:Ack确认是通过MQ服务端对ConsumerTag和MessageTag共同决定;
* 同样setConcurrentConsumers=5的状态下,channel也=5,所以其他线程不会Ack掉本线程msg
* @return
*/
@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(rabbitMqProperties.getConcurrentConsumers());
simpleMessageListenerContainer.setMaxConcurrentConsumers(rabbitMqProperties.getConcurrentConsumers());
simpleMessageListenerContainer.setAdviceChain(retryOperationsInterceptorFactoryBean.getObject());
simpleMessageListenerContainer.setErrorHandler(mqErrorHandler);
simpleMessageListenerContainer.setPrefetchCount(rabbitMqProperties.getBasicQos());
return simpleMessageListenerContainer;
}
/**
* server for @RabbitListener
* @EnableRabbit
* @RabbitListener(queues = MQMessageQueueNames.NULL)
* @param connectionFactory
* @return
*/
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
SimpleRabbitListenerContainerFactory simpleMessageListenerContainer =
new SimpleRabbitListenerContainerFactory();
//SimpleRabbitListenerContainerFactory发现消息中有content_type有text就会默认将其转换成string类型的
simpleMessageListenerContainer.setConnectionFactory(connectionFactory);
simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
simpleMessageListenerContainer.setConcurrentConsumers(rabbitMqProperties.getConcurrentConsumers());
simpleMessageListenerContainer.setMaxConcurrentConsumers(rabbitMqProperties.getConcurrentConsumers());
simpleMessageListenerContainer.setAdviceChain(retryOperationsInterceptorFactoryBean.getObject());
simpleMessageListenerContainer.setErrorHandler(mqErrorHandler);
simpleMessageListenerContainer.setPrefetchCount(rabbitMqProperties.getBasicQos());
return simpleMessageListenerContainer;
}
// /**
// * 队列调度器
// * @param body
// * @param headers
// */
// @RabbitListener(queues = MQMessageQueueNames.NULL)
// public void handleMessage(@Payload String body, @Headers Map headers){
//
// }
}
package com.soul.home.lws.system.mq.listener;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.soul.home.lws.system.mq.config.MQMessageQueueNames;
import com.soul.home.lws.system.utils.GsonUtils;
import org.apache.log4j.Logger;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.converter.MessageConversionException;
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 IOException {
String msg = new String(message.getBody());
logger.info("message tag: " + message.getMessageProperties().getDeliveryTag() + ", " + msg);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
throw new RuntimeException("hanler message " + msg + " failed.");
// try {
//
// } catch (MessageConversionException e) {
//// logger.error("convert MQ message error.", e);
// } finally {
// long deliveryTag = message.getMessageProperties().getDeliveryTag();
// if (deliveryTag != DELIVERIED_TAG) {
// 退回消息则无需进行Ack确认,会自动确认并执行退回交换机队列数据
// channel.basicNack(deliveryTag, true, false); // 批量退回
// channel.basicReject(deliveryTag, false); // 单条退回
//
// channel.basicAck(deliveryTag, true);
// message.getMessageProperties().setDeliveryTag(DELIVERIED_TAG);
//// logger.info("revice and ack msg: " + (obj == null ? message : new String((byte[]) obj)));
// }
// }
}
}
辅助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";
}
<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" />
<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" />
<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天。