【国产化】金蝶MQ验证

文章目录

概述

本次验证金蝶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会重试发送
相关推荐
这周也會开心3 小时前
SpringBootTest和SpringBootApplication
springboot
她说..17 小时前
基于Redis实现的分布式唯一编号生成工具类
java·数据库·redis·分布式·springboot
最后11120 小时前
lamp-cloud 5.7.0 发布,新增缓存清理 + 修复优化全覆盖
java·后端·spring·缓存·springboot·springcloud
北京智和信通1 天前
智和信通全栈式运维平台,广泛适配信创国产化环境
国产化·信创运维·智能运维平台
bcgbsh4 天前
身份认证状态的存储与传递( Spring Boot篇 )
springboot·登录
xiezhr4 天前
Java开发中最那些常见的坑,你踩过几个?
java·spring·springboot·后端开发
暂时先用这个名字5 天前
信创时代下,PHP/MySQL应用的平滑迁移与运维管理升级(AI整理)
运维·mysql·php·信创·国产化·国产·迁移
原来是好奇心6 天前
Spring Boot缓存实战:@Cacheable注解详解与性能优化
java·spring·mybatis·springboot
千寻技术帮7 天前
50013_基于微信小程序的校园志愿者系统
mysql·微信小程序·springboot·文档·ppt