文章目录
概述
本次验证金蝶MQ使用,验证结果将同步该文档
验证点:
1.金蝶MQ是否适配JDK8和JDK17
2.金蝶MQ是否适配现有MQ场景(目前内部主要是采用了rabbitMQ的主题模式)
3.是否支持队列模式、订阅模式、广播模式、延时队列、事物消息、以及广播模式下消息消费是否支持幂等
4.死信队列/延时队列验证
5.业务服务接入验证是否通过
验证说明
以下验证代码在rabbitmq下都可以正常运行,切换金蝶mq验证
验证结论
- 优势:目前预研的几款国产化mq(宝兰德mq/金蝶mq/东方通)中代码适配度最高的一套方案,不需要修改rabbitmq的代码,只需要修改服务地址
- 缺陷:
(1)不支持rabbitmq的插件,比如通过插件实现的rabbitmq延时队列(有替代方案:可以通过死信队列实现)
(2)不支持rabbitmq的事务消息,比如确认模式和回退模式的回调 - 未确认点:mq吞吐和并发量
连接配置
java
#金蝶mq连接配置
spring.rabbitmq.host = 192.168.xx.xx
spring.rabbitmq.port = 5672
spring.rabbitmq.username = xxxx
spring.rabbitmq.password = xxxx
spring.rabbitmq.virtual-host = xxx
#死信队列配置
# 允许消息消费失败的重试
spring.rabbitmq.listener.simple.retry.enabled = true
# 消息最多消费次数3次
spring.rabbitmq.listener.simple.retry.max-attempts = 3
# 消息多次消费的间隔1秒
spring.rabbitmq.listener.simple.retry.initial-interval = 1000
# 设置为false,会丢弃消息或者重新发布到死信队列
spring.rabbitmq.listener.simple.default-requeue-rejected = false
# 开启回退模式
spring.rabbitmq.publisher-returns=true
#开启ack手动签收,message.getMessageProperties().getDeliveryTag()递增才能生效
spring.rabbitmq.listener.simple.acknowledge-mode=manual
是否适配JDK8和JDK17-可用
- 验证结论:可用
适配现有MQ场景验证-可用
- 验证结论:可用
- 声明绑定交互机、队列、路由
java
public final static String BASIC_REGULATOR_INSERT_QUEUE = "checkInsertQueue";
public final static String BASIC_REGULATOR_INSERT_TOPIC_EXCHANGE = "checkTopicExchange";
public final static String BASIC_REGULATOR_INSERT_KEY = "checkOne";
@Bean(name = BASIC_REGULATOR_INSERT_QUEUE)
public Queue getgpaBasicPomlCheckUserInsertQueue1() {
return new Queue(BASIC_REGULATOR_INSERT_QUEUE);
}
@Bean(name = BASIC_REGULATOR_INSERT_TOPIC_EXCHANGE)
TopicExchange getGpaPomlTopicExchange1() {
return new TopicExchange(BASIC_REGULATOR_INSERT_TOPIC_EXCHANGE);
}
@Bean
Binding bindingPomlUserInsertToTopicExchange1(
@Qualifier(BASIC_REGULATOR_INSERT_QUEUE) Queue queue,
@Qualifier(BASIC_REGULATOR_INSERT_TOPIC_EXCHANGE) TopicExchange topicExchange) {
return BindingBuilder.bind(queue)
.to(topicExchange)
.with(BASIC_REGULATOR_INSERT_KEY);
}
- 消息发送
java
@ApiOperation("exchangeTopicCheck")
@GetMapping("/exchangeTopicCheck")
public Result<Boolean> exchangeTopicCheck() {
String msg = "Exchange.Topic msg";
System.out.println("Exchange.Topic验证发送:" + msg);
amqpTemplate.convertAndSend(ExchangeTopicMqConfig.BASIC_REGULATOR_INSERT_TOPIC_EXCHANGE,
ExchangeTopicMqConfig.BASIC_REGULATOR_INSERT_KEY,
msg);
return Result.ok(true);
}
- 消息消费
java
@RabbitListener(queues = ExchangeTopicMqConfig.BASIC_REGULATOR_INSERT_QUEUE)
public void rabbitRegulatorInsertConsumer(String string) {
log.info("接收到Exchange.Topic消息:" + string);
}
- 运行效果

直接通过队列收发消息验证-不可用
- 验证结论:不可用,金蝶mq不能直接通过队列收发消息
- 声明队列
java
@Bean
public Queue simpleQueue() {
return new Queue("simple.queue");
}
- 消息发送
java
@ApiOperation("sendByQueueCheck")
@GetMapping("/sendByQueueCheck")
public Result<Boolean> sendByQueueCheck() {
String msg = "sendByQueueCheck";
System.out.println("队列模式验证发送:" + msg);
amqpTemplate.convertAndSend("simple.queue", msg);
return Result.ok(true);
}
- 消息消费
java
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueueMessage(String msg) {
System.out.println("spring 消费者接收到消息:【" + msg + "】");
}
- 运行效果

广播模式fanout交换机验证-可用
- 验证结论:可用
- 声明绑定交互机、队列、路由
java
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("itcast.fanout");
}
@Bean
public Queue fanoutQueue1() {
return new Queue("fanout.queue1");
}
@Bean
public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
@Bean
public Queue fanoutQueue2() {
return new Queue("fanout.queue2");
}
@Bean
public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
- 消息发送
java
@ApiOperation("sendByFanoutCheck")
@GetMapping("/sendByFanoutCheck")
public Result<Boolean> sendByFanoutCheck() {
String msg = "sendByFanoutCheck";
System.out.println("广播模式fanout验证发送:" + msg);
amqpTemplate.convertAndSend("itcast.fanout", null, msg);
return Result.ok(true);
}
- 消息消费
java
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
System.out.println("消费者1接收到Fanout消息:【" + msg + "】");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
System.out.println("消费者2接收到Fanout消息:【" + msg + "】");
}
- 运行效果

路由模式direct交换机验证-可用
- 验证结论:可用
- 消息发送
java
@ApiOperation("sendByDirectCheck")
@GetMapping("/sendByDirectCheck")
public Result<Boolean> sendByDirectCheck() {
String msg = "sendByFanoutCheck";
System.out.println("路由模式direct验证发送:" + msg);
amqpTemplate.convertAndSend("itcast.direct", "red", msg);
return Result.ok(true);
}
- 声明绑定交互机、队列、路由和消息消费
java
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
key = {"red", "blue"}
))
public void listenDirectQueue1(String msg) {
System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg) {
System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】");
}
- 运行效果

主题模式topic交换机验证-可用
- 验证结论:可用
- 消息发送
java
@ApiOperation("sendByTopicCheck")
@GetMapping("/sendByTopicCheck")
public Result<Boolean> sendByTopicCheck() {
String msg = "sendByTopicCheck";
System.out.println("主题模式topic验证发送:" + msg);
amqpTemplate.convertAndSend("itcast.topic", "china.news", msg);
return Result.ok(true);
}
- 声明绑定交互机、队列、路由和消息消费
java
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
key = "china.#"
))
public void listenTopicQueue1(String msg) {
System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
key = "#.news"
))
public void listenTopicQueue2(String msg) {
System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
}
- 运行效果

死信队列验证-可用
- 验证结论:可用
- 配置
java
#死信队列配置
# 允许消息消费失败的重试
spring.rabbitmq.listener.simple.retry.enabled=true
# 消息最多消费次数3次
spring.rabbitmq.listener.simple.retry.max-attempts=3
# 消息多次消费的间隔1秒
spring.rabbitmq.listener.simple.retry.initial-interval=1000
# 设置为false,会丢弃消息或者重新发布到死信队列
spring.rabbitmq.listener.simple.default-requeue-rejected=false
- 声明绑定交互机、队列、路由
java
//--------------重定向交换机-begin--------------
public static final String REDIRECT_EXCHANGE = "TREDIRECT_EXCHANGE";//重定向交换机
public static final String REDIRECT_ROUTING_KEY = "TTREDIRECT_KEY_R"; //重定向队列routing key
public static final String REDIRECT_QUEUE = "TREDIRECT_QUEUE"; //重定向消息存储queue
/**
* 定义重定向交换机
*/
@Bean("redirectExchange")
public Exchange redirectExchange() {
return ExchangeBuilder.directExchange(REDIRECT_EXCHANGE).durable(true).build();
}
/**
* 定义重定向消息存储queue
*/
@Bean("redirectQueue")
public Queue redirectQueue() {
return QueueBuilder.durable(REDIRECT_QUEUE).build();
}
/**
* 将重定向队列通过重定向routingKey(TKEY_R)绑定到消息交换机上
*
* @return the binding
*/
@Bean
public Binding redirectBinding() {
return new Binding(REDIRECT_QUEUE, Binding.DestinationType.QUEUE, REDIRECT_EXCHANGE
, REDIRECT_ROUTING_KEY, null);
}
//--------------重定向交换机-end--------------
//--------------死信队列交换机-begin--------------
public static final String MESSAGE_EXCHANGE = "TDL_EXCHANGE";//消息接收的交换机
public static final String MESSAGE_ROUTING_KEY = "TDL_KEY";//消息routing key
public static final String MESSAGE_QUEUE = "TDL_QUEUE"; //消息存储queue
/**
* 定义消息接收交换机
* 交换机类型没有关系 不一定为directExchange 不影响该类型交换机的特性.
*/
@Bean("messageExchange")
public Exchange messageExchange() {
return ExchangeBuilder.directExchange(MESSAGE_EXCHANGE).durable(true).build();
}
/**
* 定义消息存储queue,设置死信交换机和死信路由key,本例子当异常时将消息
* 发送到x-dead-letter-exchange,然后x-dead-letter-exchange根据x-dead-letter-routing-key将消息路由到REDIRECT_QUEUE
*/
@Bean("messageQueue")
public Queue messageQueue() {
Map<String, Object> args = new HashMap<>(2);
// x-dead-letter-exchange 声明 死信队列Exchange
args.put("x-dead-letter-exchange", REDIRECT_EXCHANGE);
// x-dead-letter-routing-key 声明 队列抛出异常并重试无效后进入重定向队列的routingKey(TKEY_R)
args.put("x-dead-letter-routing-key", REDIRECT_ROUTING_KEY);
return QueueBuilder.durable(MESSAGE_QUEUE).withArguments(args).build();
}
/**
* 消息队列绑定到消息交换器上.,第二个参数需要指定类型为队列来区分,因为交换机可以绑定交换机
*
* @return the binding
*/
@Bean
public Binding messageBinding() {
return new Binding(MESSAGE_QUEUE, Binding.DestinationType.QUEUE, MESSAGE_EXCHANGE
, MESSAGE_ROUTING_KEY, null);
}
//--------------死信队列交换机-end--------------
- 消息发送
java
@ApiOperation("dLXCheck")
@GetMapping("/dLXCheck")
public Result<Boolean> dLXCheck(@RequestParam(value = "number") Integer number) {
System.out.println("死信队列验证发送 : " + number);
// 发送消息并制定routing key
amqpTemplate.convertAndSend(
DeadLetterMqConfig.MESSAGE_EXCHANGE,
DeadLetterMqConfig.MESSAGE_ROUTING_KEY,
number);
return Result.ok(true);
}
- 消息消费
java
@RabbitListener(queues = DeadLetterMqConfig.MESSAGE_QUEUE)
public void meetingRoomCancelPushQueue1(Integer number) {
log.info("Listener.死信队列消息接受:" + number + "/0 ");
int i = number / 0;
}
@RabbitListener(queues = DeadLetterMqConfig.REDIRECT_QUEUE)
public void meetingRoomCancelPushQueue2(Integer number) {
log.info("Listener.死信队列重定向消息接受:" + number);
}
- 运行效果


延时队列实现验证(基于死信队列实现)-可用
- 验证结论:可用
- 声明绑定交互机、队列、路由
java
public static final String X_CHANGE = "X";
private static final String QUEUE_A = "QA";
private static final String QUEUE_B = "QB";
private static final String Y_DEAD_LETTER_EXCHANGE = "Y";
private static final String DEAD_LETTER_QUEUE = "QD";
@Bean
public DirectExchange xExchange() {
return new DirectExchange(X_CHANGE);
}
@Bean
public DirectExchange yExchange() {
return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
}
/**
* 声明队列A 的消息ttl为10s,并绑定到对应的死信交换机
*/
@Bean("queueA")
public Queue queueA() {
HashMap<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
args.put("x-dead-letter-routing-key", "YD");
//延时10秒
args.put("x-message-ttl", 10000);
return QueueBuilder.durable(QUEUE_A).withArguments(args).build();
}
/**
* 声明队列 A 绑定 X 交换机
*/
@Bean
public Binding queueaBindingX(@Qualifier("queueA") Queue queueA,
@Qualifier("xExchange") DirectExchange xExchange) {
return BindingBuilder.bind(queueA).to(xExchange).with("XA");
}
/**
* 声明队列 B ttl 为 40s 并绑定到对应的死信交换机
*/
@Bean("queueB")
public Queue queueB() {
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
//声明当前队列的死信路由 key
args.put("x-dead-letter-routing-key", "YD");
//声明队列的 TTL,延时40秒
args.put("x-message-ttl", 40000);
return QueueBuilder.durable(QUEUE_B).withArguments(args).build();
}
/**
* 声明队列 B 绑定 X 交换机
*/
@Bean
public Binding queuebBindingX(@Qualifier("queueB") Queue queue1B,
@Qualifier("xExchange") DirectExchange xExchange) {
return BindingBuilder.bind(queue1B).to(xExchange).with("XB");
}
/**
* 声明死信队列 QD
*/
@Bean("queueD")
public Queue queueD() {
return new Queue(DEAD_LETTER_QUEUE);
}
/**
* 声明死信队列 QD 绑定关系
*/
@Bean
public Binding deadLetterBindingQAD(@Qualifier("queueD") Queue queueD,
@Qualifier("yExchange") DirectExchange yExchange) {
return BindingBuilder.bind(queueD).to(yExchange).with("YD");
}
- 消息发送
java
@ApiOperation("tTLCheck")
@GetMapping("/tTLCheck")
public Result<Boolean> tTLCheck() {
String msg = "tTLCheck msg,time" + new Date();
System.out.println("延时队列验证发送 : " + msg);
// 发送消息并制定routing key
amqpTemplate.convertAndSend(TtlQueueConfig.X_CHANGE, "XA", msg);
amqpTemplate.convertAndSend(TtlQueueConfig.X_CHANGE, "XB", msg);
return Result.ok(true);
}
- 消息消费
java
@RabbitListener(queues = "QD")
public void receiveD(String string) throws IOException {
log.info("当前时间:{},收到死信队列信息{}", new Date(), string);
}
- 运行效果

消息确认机制:确认模式-不可用
- 验证结论:不可用
- 配置
java
spring.rabbitmq.publisher-confirm-type=correlated
- 监听器
- 消息发送
java
@ApiOperation("correlatedCheck")
@GetMapping("/correlatedCheck")
public Result<Boolean> correlatedCheck() {
String msg = "correlatedCheck";
System.out.println("消息可靠性投递-确认模式验证发送:" + msg);
amqpTemplate.convertAndSend("itcast.top", "china.news", msg);
return Result.ok(true);
}
- 声明绑定交互机、队列、路由
未定义交互机、队列、路由,rabbitmq发送交换机失败会触发ConfirmCallback回调事件 - 运行效果
未触发ConfirmCallback回调事件
##消息确认机制:回退模式-不可用 - 验证结论:不可用
- 配置
java
# 开启回退模式
spring.rabbitmq.publisher-returns=true
-
监听器

-
消息发送
java
@ApiOperation("publisherReturnsCheck")
@GetMapping("/publisherReturnsCheck")
public Result<Boolean> publisherReturnsCheck() {
String msg = "publisherReturnsCheck";
System.out.println("消息可靠性投递-回退模式验证发送:" + msg);
amqpTemplate.convertAndSend("itcast.topic", null, msg);
return Result.ok(true);
}
- 声明绑定交互机、队列、路由
声明itcast.topic的TOPIC类型交换机未绑定对应队列,rabbitmq消息未从Exchange路由到Queue会触发ReturnCallback回调事件 - 运行效果
未触发ReturnCallback回调事件
##消息确认机制:ack验证-可用 - 验证结论:可用
- 配置
java
#开启ack手动签收,message.getMessageProperties().getDeliveryTag()递增才能生效
spring.rabbitmq.listener.simple.acknowledge-mode=manual
- 消息发送
java
@ApiOperation("ackCheck")
@GetMapping("/ackCheck")
public Result<Boolean> ackCheck() {
String msg = "ackCheck";
System.out.println("ack验证发送:" + msg);
amqpTemplate.convertAndSend("itcast.ack", "ack", msg);
return Result.ok(true);
}
- 声明绑定交互机、队列、路由和消息消费
java
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queuef"),
exchange = @Exchange(name = "itcast.ack", type = ExchangeTypes.DIRECT),
key = {"ack"}
))
public void listenAckQueue(Message message, Channel channel) throws InterruptedException, IOException {
// 消息投递序号,消息每次投递该值都会+1,spring.rabbitmq.listener.simple.acknowledge-mode=manual 才能生效
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
if (deliveryTag < 5) {
//模拟处理消息出现bug
int i = 1 / 0;
}
System.out.println("成功接受到消息:" + message);
// 签收消息
/**
* 参数1:消息投递序号
* 参数2:是否一次可以签收多条消息
*/
channel.basicAck(deliveryTag, true);
} catch (Exception e) {
System.out.println("消息消费失败!");
Thread.sleep(2000);
// 拒签消息
/**
* 参数1:消息投递序号
* 参数2:是否一次可以拒签多条消息
* 参数3:拒签后消息是否重回队列
*/
channel.basicNack(deliveryTag, true, true);
}
}
- 运行效果
未ack响应mq会重试发送
