springboot中简单使用及RabbitMQ高级特性

SpringBoot中使用rabbitMQ

SpringBoot为了使用RabbitMQ,提供了一个工具类RabbitTemplate,使用此工具类可以发送消息。

在这里插入图片描述

1、父工程中引入相关的依赖

   <dependencies>
        <!--rabbitMQ的依赖: 启动类加载。读取配置文件:
              springboot自动装配原理: 引用starter启动依赖时,把对应的自动装配类加载进去,该自动装配类可以读取application配置文件中
              内容。 DispatherServlet
           -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2、相应的生产者和消费者的配置(此处为yml文件格式)

#SpringBoot开启的端口号,为了防止与其他的冲突设置的
server:
  port: 8888
  
  
#rabbit的配置
spring:
  rabbitmq:
    host: 192.168.31.168
    #port: 5672,java默认rabbit端口号为5672,,可修改

3、生产者(producer)代码

//模拟下单业务
@RestController
public class HelloController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("hello")
    public String hello(){ //业务层
        System.out.println("下单成功");
        //String exchange, String routingKey, Object message
        Map<String,Object> map=new HashMap<>();
        map.put("productId",1);
        map.put("num",10);
        map.put("price",12);
        rabbitTemplate.convertAndSend("ban_exchange","", JSON.toJSONString(map)); //序列化过程
        return "下单成功";
    }

4、消费者(consumer)监听消息(运行后实时监听消息的产生)

@Component
public class MyRabbitListener {

    //队列中存在消息则立即回调该方法,此注解值为需要监听的队列名
    @RabbitListener(queues = {"ban_queue_fanout01"})
    public void listener(String msg){//此处使用字符串接收,也可用Message类型接收消息
    
        Map map = JSON.parseObject(msg, Map.class);//接收的json字符串转化成map类型(此消息发送时就是使用的map发送的,因此可以)
        
        System.out.println(map);
    }  
}

Rabbit的高级特性

1、什么是RabbitMQ的高级特性?

rabbitMQ的高级特性是对rabbitMQ的深入理解及使用。
主要包含有:
	1、消息的可靠性传递
	2、Consumer ACK(即手动确认签收消息)
	3、消费端限流
	4、TTL(设置队列或消息的过期时间)
	5、死信队列
	6、延迟队列
	7、消息的幂等性

2、springboot中使用代码创建队列和交换机及其绑定。

创建配置文件类RabbitConfig,编写代码:

@Configuration
public class RabbitConfig {

    private final String exchange_name="myexchange";
    private final String queue_name="myqueue";
    //创建交换机对象
    @Bean
    public Exchange exchange(){
        Exchange exchange= ExchangeBuilder.fanoutExchange(exchange_name).durable(true).build();
        return exchange;
    }

    //创建队列
    @Bean(value = "queue")
    public Queue queue(){
        Queue queue= QueueBuilder.durable(queue_name).withArgument("x-message-ttl",20000).build();
        return queue;
    }

    //绑定交换机和队列
    @Bean
    public Binding binding(Queue queue,Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("").noargs();
    }
}

3、消息的可靠性传递

3.1、什么是消息的可靠性传递?

在使用rabbitMQ的时候,作为消息的发送方希望杜绝任何消息的丢失或者投递失败场景。RabbitMQ为我们提供了两种方式用来控制消息的投递可靠性模式。

1、消息的投递步骤:
	生产者-----》交换机-------》队列
	
	
2、为了确保消息的可靠性投递,提供了如下两种方式:
	confirm  确认模式
	return   退回模式

3.2、确认模式

(1)必须开启确认模式(手动确认)

spring:
  rabbitmq:
    host: 192.168.31.168
    #开启rabbitMQ的生产方确认模式
    publisher-confirm-type: correlated

(2)创建普通测试类,设置RabbitTemplate的确认回调函数(消费者端):

@Autowired
    private RabbitTemplate rabbitTemplate;
    /**
     * 保证发送方到交换机的可靠性。
     *    1.开启confirm模式,publisher-confirm-type: correlated
     *    2.设置rabbitTemplate的确认回调函数。如果消息到达交换机则返回true,如果消息没有到达交换机则返回一个false
     */
    @Test
    public void test2(){
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                if(b==false){//消息没有到达交换机  根据业务需求。
                    System.out.println("继续发现消息");
                    //取消订单
                }
            }
        });
        rabbitTemplate.convertAndSend("ban_exchange","","hello confirm");
    }

3.3、退回模式

(1)开启回退机制

server:
  port: 8001

spring:
  rabbitmq:
    host: 192.168.31.168
    #开启rabbitMQ的生产方确认模式
    publisher-confirm-type: correlated
    # 开启发布者退回模式
    publisher-returns: true

(2)设置RabbitTemplate回调的函数

/**
     *  退回模式:
     *     1. 开启退回模式。
     *     2. 设置RabbitTemplate的退回回调函数。
     */
    @Test
    public void test3(){
            rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
                @Override
                public void returnedMessage(ReturnedMessage returnedMessage) {
                    //只要交换机到队列失败时才会触发该方法。 可以继续发送也可以取消相应的业务功能。
                    System.out.println("消息从交换机到队列失败"+returnedMessage.getReplyText());
                }
            });
        rabbitTemplate.convertAndSend("ban_exchange_direct","error2","hello confirm2");
    }

3.4、如何保证消息的可靠性?

1、保证消息从产生者到交换机的可靠性;--》使用confim确认机制。
2、保证消息从交换机到队列的可靠性;--》使用return回退机制
3、消息在队列中的可靠性。---》设置消息和队列的持久化(存活时间)
4、保证消息从队列到消费者的可靠性。--》使用消费端的手动确认机制。

4. Consumer ACK(手动确认签收)

表示消费端收到消息后的确认方式。

其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息队列中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后。

调用channel.basicAck(),手动签收,如果出现异常。

则调用channel.basicNack()方法,让其自动重新发送消息。

(1)消费端(consumer)配置手动开启确认模式

spring:
  rabbitmq:
    host: 192.168.31.168
    listener:
      simple:
        #表示手动确认
        acknowledge-mode: manual
      # 表示自动确认模式
        # acknowledge-mode: none

(2)消费端接收消息后确认消息

 @RabbitListener(queues = "ban_queue_direct01")
    public void listener(Message message, Channel channel) throws Exception{
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        byte[] body = message.getBody();
        String msg=new String(body);

        System.out.println(msg);
        try {
//            int c = 10 / 0;//模拟异常
            System.out.println("处理业务逻辑");
            //消费端手动确认消息
            //long deliveryTag, 表示的标识。
            // boolean multiple:是否允许多确认
            channel.basicAck(deliveryTag,true); //从队列中删除该消息。
        }catch (Exception e){
            //(long deliveryTag, boolean multiple, boolean requeue: 是否让队列再次发送该消息。
            channel.basicNack(deliveryTag,true,true);
        }
    }

5. 消费端限流模式

(1)开启前提:

1. 必须为手动确认模式。

2. 必须配置限流的个数。
spring:
  rabbitmq:
    host: 192.168.213.188
    listener:
      simple:
      	# 表示自动确认模式
        # acknowledge-mode: none
        #表示手动确认
        acknowledge-mode: manual
        # 设置每次消费的个数。
        prefetch: 100

(2)消费端接收消息。

@Component
public class MyListener {

    @RabbitListener(queues = "ban_queue_direct01")
    public void listener(Message message, Channel channel) throws Exception{
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        byte[] body = message.getBody();
        String msg=new String(body);

        System.out.println(msg);

        try {
//            int c = 10 / 0;
            //System.out.println("处理业务逻辑");
            //消费端手动确认消息
            //long deliveryTag, 表示的标识。
            // boolean multiple:是否允许多确认
            channel.basicAck(deliveryTag,true); //从队列中删除该消息。
        }catch (Exception e){
            //(long deliveryTag, boolean multiple, boolean requeue: 是否让队列再次发送该消息。
            channel.basicNack(deliveryTag,true,true);
        }

    }
}

6.TTL设置队列或消息存活时间

1.设置队列过期;

2.设置消息的过期;该消息必须在队列的头部时才会被移除。

设置队列或消息的过期时间:

	//为队列设置过期时间  相当于该队列里面的消息都由过期时间
    @Test
    public void test01(){
        rabbitTemplate.convertAndSend("myexchange","","hello xiaoxi");
    }

    //设置消息的过期时间 如果由设置了队列的过期时间 也设置了消息的过期时间 谁的过期时间短 以谁为准。
    //该消息必须在头部才能从队列中移除。
    @Test
    public void test02(){

        for(int i=0;i<10;i++) {
            if(i==3){
                MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
                    @Override
                    public Message postProcessMessage(Message message) throws AmqpException {
                        message.getMessageProperties().setExpiration("20000");
                        return message;
                    }
                };
                rabbitTemplate.convertAndSend("myexchange", "", "hello xiaoxi"+i, messagePostProcessor);
            }else {

                rabbitTemplate.convertAndSend("myexchange", "", "hello xiaoxi"+i);
            }
        }
    }

7.死信队列

死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。

在这里插入图片描述

(1)消息成为死信的三种情况:

1. 队列消息长度到达限制;

2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;

3. 原队列存在消息过期设置,消息到达超时时间未被消费;


队列需要绑定死信交换机才会生效:

给队列设置参数(绑定死信交换机):
   x-dead-letter-exchange	  死信交换机 
   x-dead-letter-routing-key  死信交换机的路由key

(2)创建死信队列及普通队列并进行绑定:

@Configuration
public class RabbitConfig {

    //创建死信队列
    @Bean
    public Queue dead_queue(){
        return QueueBuilder.durable("dead_queue")
                .build();
    }

    //创建死信交换机
    @Bean
    public Exchange dead_exchange(){
        return ExchangeBuilder.directExchange("dead_exchange")
                .build();
    }

    //绑定死信交换机和死信队列,并设置死信路由key
    @Bean
    public Binding dead_binding(){
        return BindingBuilder.bind(dead_queue()).to(dead_exchange()).with("error").noargs();

    }

    //创建普通队列并绑定死信交换机
    @Bean
    public Queue queue(){
        return QueueBuilder.durable("queue")
            //设置消息的存活时间
                .withArgument("x-message-ttl",30000)
            //设置最大消息长度
                .withArgument("x-max-length",10)
                .withArgument("x-dead-letter-exchange","dead_exchange")
                .withArgument("x-dead-letter-routing-key","error")
                .build();
    }

  
    //创建普通消息交换机
    @Bean
    public Exchange exchange(){
        return ExchangeBuilder.directExchange("exchange")
                .durable(true)
                .build();
    }

    //绑定普通队列及普通交换机并设置路由key
    @Bean
    public Binding binding(){
        return BindingBuilder.bind(queue()).to(exchange()).with("error").noargs();

    }
}

(3)创建消息产生者:

@SpringBootTest(classes = SiXingApp.class)
public class SiXingTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    void kfjkfj(){
        for (int i = 0; i < 15; i++) {
            rabbitTemplate.convertAndSend("exchange","error","nfiwjfjw");
        }

    }
}

超过设置队列长度的消息会直接进入死信队列。

存在于普通队列中的消息超过存活时间未被消费后会进入死信队列。

8、延迟队列

8.1、为什么要使用延迟队列?一般的使用场景有哪些?

(1)为什么使用延迟队列:
  例如订单下单之后30分钟后,如果用户没有付钱,则系统自动取消订单。
  
(2)使用场景:
  这样类似的需求是我们经常会遇见的问题。最常用的方法是定期轮训数据库,设置状态。在数据量小的时候并没有什么大的问题,但是数据量一大轮训数据库的方式就会变得特别耗资源。当面对千万级、上亿级数据量时,本身写入的IO就比较高,导致长时间查询或者根本就查不出来。此时通过使用延迟队列来解决这种问题。

8.2、例如订单系统:

在这里插入图片描述

(1)给消息队列设置30分钟的存活时间但不绑定消费者(当30分钟过后,消息会进入死信队列)

(2)对死信队列中的消息进行判断是否支付。
	如果未支付则代表支付超时,直接取消订单,并回滚库存。
	如果支付了则代表已付款,则库存系统什么都不需要做,只需要商家确定订单并发货即可

模拟小案例:

(1)使用图形化界面设置队列存活时间

(2)创建消息的产生者

	@Test
    public void testDeadQueue() {
        rabbitTemplate.convertAndSend("exchange", "info", "下单成功。");
    }

(3)创建消费者:

@Component
public class MyListener {
    @RabbitListener(queues = "dead_queue")
    public void listener(Message message, Channel channel){
        System.out.println("超时没有处理订单,取消订单");
        try {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
        } catch (IOException e) {
            try {
                channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,true);
            } catch (IOException ioException) {
                
            }
        }
    }
}

9、消息的幂等性

9.1、什么是消息的幂等性?

无论执行多少次,得到的结果和第一次都是相同的。 

即保证消息不被重复消费

在这里插入图片描述

例如:
	(1)当我们下达一个订单后,消息发送到消费者需要将id=2的商品库存减10.
	(2)消费者接受消息,并将库存减少了,后像库存系统发送确认消息(表示库存已减少,即此消息已被消费)
	(3)因为网络原因或其他原因,确认的消息未及时回调到队列,此时队列认为消息未被消费,因此向消费者再次发送此条减库存的请求。造成库存多次减少(即同一条消息多次消费)。

解决方法:

//消费端
@Component
public class MyListener {

    @Autowired
    private RedisTemplate redisTemplate;
    @RabbitListener(queues = "queue")
    public void listener(Message msg, Channel channel) throws  Exception{
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        //根据消息的唯一标识获取缓存,如果有则代表此消息以及被处理,如果没有则需要处理该消息
        Object o = redisTemplate.opsForValue().get(deliveryTag);
        if(o==null){
            //业务代码
            try {
                System.out.println("完成业务功能");
                //设置消息唯一标识作为key的缓存
                redisTemplate.opsForValue().set(deliveryTag, "slz");
                channel.basicAck(msg.getMessageProperties().getDeliveryTag(), true);
            }catch (Exception e){
                channel.basicNack(msg.getMessageProperties().getDeliveryTag(),true,true);
            }
        }else{
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(),true);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值