SpringBoot整合RabbitMq

RabbitMq

<!--rabbitmq的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

properties文件定义配置

配置文件的参考

server.port=8089
#======================rabbitmq配置================
spring.rabbitmq.host=192.168.1.105
spring.rabbitmq.port=5672
spring.rabbitmq.username=mqRabbit
spring.rabbitmq.password=123456
spring.rabbitmq.virtual-host=/mqRabbit
#消费者重试
spring.rabbitmq.listener.simple.retry.enabled=true
#最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
#重试间隔时间
spring.rabbitmq.listener.simple.retry.initial-interval=3000
#手动ack
spring.rabbitmq.listener.simple.acknowledge-mode=manual
#生产者消息确认
#spring.rabbitmq.publisher-confirms=true
#spring.rabbitmq.template.mandatory=true
#============================数据库的配置============================
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/redis_learning?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#=============================MyBaties========================
# mybatis 下划线转驼峰配置,两者都可以
#mybatis.configuration.mapUnderscoreToCamelCase=true
mybatis.configuration.map-underscore-to-camel-case=true
#增加打印sql语句句,⼀一般⽤用于本地开发测试
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#配置xml文件
mybatis.mapper-locations=classpath:mapper/*.xml

RabbitMq的java配置

定义不同的Queue,Exchange,bindingExchangeSms 就是可以实现多个不同消息队列搭配

package com.roy.config;

import org.springframework.amqp.core.*;

import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Authror royLuo
 * @Date 2020/4/28 22:51
 **/
@Configuration
public class RabbitConfig {

    //sms队列
    public static final String SMSQUEUE = "sms_queue";
    public static final String SMSEXCHANGE = "sms_exchange";

    /**
     * Queue队列
     **/
    @Bean
    public Queue smsQueue() {
        /**
         * 1.durable: 队列是否持久化;队列是否可持久化
         *
         * 2.exclusive: 是否独享、排外的;
         *
         * 3.autoDelete: 是否自动删除;
         * 队列没有任何订阅的消费者时是否自动删除,默认为false。可以通过RabbitMQ Management,查看某个队列的消费者数量。如果为true,当consumers = 0时队列就会自动删除。
         * **/
        return new Queue(SMSQUEUE, true, false, true);
    }

    /**
     * Exchange
     * 交换机
     **/
    @Bean
    public Exchange smsExchange() {
                                      //ExchangeTypes选择交换机的类型。现在选择的是广播模式
        return new ExchangeBuilder(SMSEXCHANGE, ExchangeTypes.FANOUT).durable(true).build();
    }

    @Bean
    public Binding bindingExchangeSms(@Qualifier("smsQueue") Queue smsQueue, @Qualifier("smsExchange") Exchange smsExchange) {
          //这里使用的是广播模式,所以是没有routingKey。但是也要定义为空字符串
        return BindingBuilder.bind(smsQueue).to(smsExchange).with("").noargs();
    }


    /***
     * 代码中使用@RabbitListener注解指定消费方法,默认情况是单线程监听队列,可以观察当队列有多个任务时消费
     * 端每次只消费一个消息,单线程处理消息容易引起消息处理缓慢,消息堆积,不能最大利用硬件资源。
     * 可以配置mq的容器工厂参数,增加并发处理数量即可实现多线程处理监听队列,实现多线程处理消息。
     * **/
    //消费者并发数量
    public static final int DEFAULT_CONCURRENT = 10;

    @Bean("customContainerFactory")
    public SimpleRabbitListenerContainerFactory
    containerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory
            connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConcurrentConsumers(DEFAULT_CONCURRENT);
        factory.setMaxConcurrentConsumers(DEFAULT_CONCURRENT);
        configurer.configure(factory, connectionFactory);
        return factory;
    }
}

消息的发送

package com.roy.service;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.UUID;

/**
 * @Authror royLuo
 * @Date 2020/4/28 23:01
 **/
@Service
public class SendMsgService {

    //sms交换机
    public static final String SMSEXCHANGE = "sms_exchange";

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public String sendMsg(String msg){
        //方式一:直接把msg发送
//        rabbitTemplate.convertAndSend(SMSEXCHANGE,"",msg);
        //方式二:构建消息
        Message message = MessageBuilder
                .withBody(msg.getBytes())
                .setContentType(MessageProperties.CONTENT_TYPE_JSON)
                .setContentEncoding("utf-8")
                .setMessageId(UUID.randomUUID() + "").build();
        
        rabbitTemplate.convertAndSend(SMSEXCHANGE,"",message);

        return "success";
    }
}

消息的消费方

package com.roy.service;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * 消费者端使用@RabbitListener注解实现监听消息
 * 1. @RabbitListener 实现原理
 * 底层使用AOP进行拦截,如果程序没有抛出异常,自动提交事务
 * 如果AOP使用异常通知拦截获取异常信息的话,自动实现补偿机制,该消息会缓存到rabbitmq服务器进行存放,
 * 一直重试到不抛出异常为准
 * <p>
 * 2.修改重试机制策略 一般默认情况下 间隔5秒重试一次
 * <p>
 * 3.重试机制
 * 3.1如何合适选择重试机制
 * 3.1.1.消费者获取消息,调用第三方接口,但接口暂时无法访问,需要重试
 * 流程:1.1http请求第三方接口,通过判断响应状态。在消费方抛出异常重试
 * <p>
 * 3.1.2.消费者获取到消息后,抛出数据转换异常,不需要重试
 * 流程: 2.捕获异常,记录日志信息。人工接口补偿
 * 对于2,我们应该采用日志记录+定时任务job健康检查+人工进行补偿
 *
 * @Authror royLuo
 * @Date 2020/4/28 23:26
 **/
@Service
public class CustomerMsgService {

    public static final String SMSQUEUE = "sms_queue";

    /**
     * 并发监听发来的消息
     * containerFactory="customContainerFactory"
     **/
//    @RabbitListener(queues = SMSQUEUE, containerFactory = "customContainerFactory")

    /**
     * 默认单线程
     * 这里使用字段串msg的形式获取消息
     * *
     **/
    @RabbitListener(queues = SMSQUEUE)
    public void listerNing1(String msg, @Headers Map<String, Object> heards, Channel channel) {
        Thread currentThread = Thread.currentThread();
        String name = currentThread.getName();
        System.out.println("name:============>>" + name + msg);
        /**
         * 当消费者这边报异常将不停重新执行代码
         int i = 1 / 0;
         * **/
        //实现手动ack
        try {
            //手动ack,这里使用手动ack的同时,配置文件也是修改的
            Long deliverTag = (Long) heards.get(AmqpHeaders.DELIVERY_TAG);
            //手动签收
            channel.basicAck(deliverTag, false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 这里使用Message的形式获取消息
     **/
    @RabbitListener(queues = SMSQUEUE)
    public void listerNing2(Message msg, @Headers Map<String, Object> heards, Channel channel) {
        try {
            //获取消息内容
            String mssage = new String(msg.getBody(), StandardCharsets.UTF_8);
            //获取消息的id
            String messageId = msg.getMessageProperties().getMessageId();
            System.out.println("msg:============>>" + mssage);
            System.out.println("messageId:============>>" + messageId);
            /**
             * 当消费者这边报异常将不停重新执行代码
             int i = 1 / 0;
             * **/
            //手动ack,这里使用手动ack的同时,配置文件也是修改的
            Long deliverTag = (Long) heards.get(AmqpHeaders.DELIVERY_TAG);
            //手动签收
            channel.basicAck(deliverTag, false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

死信队列

什么是死信呢?什么样的消息会变成死信呢?
消息被拒绝(basic.reject或basic.nack)并且requeue=false.
消息TTL过期
队列达到最大长度(队列满了,无法再添加数据到mq中)

伪代码

1.定义一个死信队列
2.定义一个死信交换机
3.绑定死信队列和死信交换机,定义路由键。

当你需要给短信队列添加死信队列的做法:
1.在定义短信队列的时侯需要这样定义:
public Queue fanOutEamilQueue(){
     //邮件队列绑定死信队列
      Map<String,Object>args = new HashMap<>(2);
      args.put(死信队列的标识符,死信交换机);
     args.put(死信路由键的标识符,死信路由键);    
     Queue queue = new Queue(短信队列名,true,false,false,args);
     return queue;

 }

2.在消费者的代码方法的参数列表Message message, @Headers Map<String, Object> headers, Channel channel里使用
丢弃该消息
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);

3.保证绑定死信队列的队列原本是不存在的

RabbitMQ解决分布式事务原理

实现的思路:

RabbitMQ解决分布式事务原理: 采用最终一致性原理。
需要保证以下三要素

1、确认生产者一定要将数据投递到MQ服务器中(采用MQ消息确认机制)
配置文件
#生产者消息确认
#spring.rabbitmq.publisher-confirms=true
#spring.rabbitmq.template.mandatory=true
生产者实现接口
   implements RabbitTemplate.ConfirmCallback
  
//生产代码发送消息代理添加代码
        //构建回调返回的数据
		CorrelationData correlationData = new CorrelationData(orderId);
		// 发送消息
		this.rabbitTemplate.setMandatory(true);
		this.rabbitTemplate.setConfirmCallback(this);


// 生产消息确认机制
	@Override
	public void confirm(CorrelationData correlationData, boolean ack, String cause) {
		String orderId = correlationData.getId();
		System.out.println("消息id:" + correlationData.getId());
		if (ack) {
			System.out.println("消息发送确认成功");
		} else {
			send(orderId);
			System.out.println("消息发送确认失败:" + cause);
		}

	}


2、MQ消费者消息能够正确消费消息,采用手动ACK模式(注意重试幂等性问题)
// 手动签收
channel.basicAck(deliveryTag, false);

3、如何保证第一个事务先执行,采用补偿机制,在创建一个补单消费者进行监听,如果订单没有创建成功,进行补单。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值