死信

0. 目标

  1. spring AMQP
  2. 死信队列
  3. 延时队列

1. Spring AMQP

1.1 简介

Sprin有很多不同的项目,其中就有对AMQP的支持

http://spring.io/projects/spring-amqp

Spring-amqp是对AMQP协议的抽象实现,而spring-rabbit 是对协议的具体实现,也是目前的唯一实现。底层使用的就是RabbitMQ。

1.2 依赖和配置

添加AMQP的启动器:

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

application.yml中添加RabbitMQ地址:

spring:
  rabbitmq:
    host: 192.168.70.136
    username: guest
    password: guest
    virtual-host: /

1.3 配置对象

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitmqConfig {
    @Bean
    public Queue queue(){
        return new Queue("test-queue",false,false,false);
    }
}

1.4 消费者

在SpringAmqp中,对消息的消费者进行了封装和抽象,一个普通的JavaBean中的普通方法,只要通过简单的注解,就可以成为一个消费者。

@Service
public class XxxService {

    // @RabbitListener:方法上的注解,声明这个方法是一个消费者方法
    // 消费者方法关联的队列
    @RabbitListener(queues = "test-queue")
    public void test1(String message){
        System.out.println("receive :"+message);
    }
}

类似listen这样的方法在一个类中可以写多个,就代表多个消费者。

1.5 AmqpTemplate

Spring最擅长的事情就是封装,把他人的框架进行封装和整合。

Spring为AMQP提供了统一的消息处理模板:AmqpTemplate,非常方便的发送消息。常用的发送方法

// 指定交换机、RoutingKey和消息体
convertAndSend(String exchange,String routingKey,Object message)
// 指定消息
convertAndSend(Object message)
// 指定RoutingKey和消息,会向默认的交换机发送消息
convertAndSend(String routingKey,Object message)    

1.6 生产者

@RestController
public class RabbitmqTestController {
    @Autowired
   private AmqpTemplate amqpTemplate;

    @GetMapping("send1")
    public String sendMessage(String name){

        amqpTemplate.convertAndSend("test-queue","xxx:"+name);

        return "send ok";
    }

}

1.7 示例的解释

首先,我们配置了一个名为“test-queue”的Queue,但是并没有配置Exchange,也没有配置“test-queue”的Binding,这其实使用了RabbitMQ的默认行为,即所有Queue都以其自身的名称为routingKey绑定到了一个默认的Exchange上,该默认Exchange的名称为""

由于Spring Boot自动配置了AmqpAdmin,该AmqpAdmin将自动向RabbitMQ创建名为“test-queue”的Queue。

在发送消息的时候,我们直接使用了AmqpTemplate,这个AmqpTemplate是Spring Boot自动为我们配置好的,AmqpTemplate所依赖的CachingConnectionFactory也由Spring Boot自动配置。

发送消息时指定了routingKey为“test-queue”,但是没有指定Exchange,此时消息将会发送到RabbitMQ默认的Exchange,又由于名为“test-queue”的Queue向默认Exchange绑定的routingKey正是“test-queue”,因此消息将由默认Exchange转发到名为“test-queue” 的Queue。至此发送方任务结束。

在消息接收方,由于Spring Boot默认为我们配置了SimpleRabbitListenerContainerFactory,因此只需要配置@RabbitListener和@RabbitHandler接收消息即可。

Spring Boot采用了很多默认配置,通过统一的RabbitProperties同时完成消费方和生产方的配置。

1.8 其他配置对象的创建

//队列 起名:TestDirectQueue
@Bean
public Queue queue() {
    //true 是否持久 
    return new Queue("test-queue",true);  
}

//Direct交换机 起名:directExchange
@Bean
DirectExchange directExchange() {
    return new DirectExchange("directExchange");
}

//绑定  
// 将队列和交换机绑定, 并设置用于匹配键:item.insert
@Bean
Binding bindingDirect() {
    return BindingBuilder.bind(queue()).to(directExchange()).with("item.insert");
}

对于发送方一般来说,有交换机的配置就可以了。接收方,有交换机和队列的配置

2 死信队列

死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer将消息投递到broker或者直接到queue里了,consumer从queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信,然后就有了死信队列。

  • 死信队列:DLX,dead-letter-exchange

  • 利用DLX,当消息在一个队列中变成死信 (dead message) 之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX

2.1 消息变成死信有以下几种情况

  • 消息被拒绝(basic.reject / basic.nack),并且requeue = false
  • 消息TTL过期
  • 队列达到最大长度

2.2 死信的处理

  • 丢弃,如果不是很重要,可以选择丢弃
  • 记录死信入库,然后做后续的业务分析或处理
  • 通过死信队列,由负责监听死信的应用程序进行处理

2.3 死信处理过程

  • DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。
  • 当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列。
  • 可以监听这个队列中的消息做相应的处理。

更常用的做法是第三种,即通过死信队列,将产生的死信通过程序的配置路由到指定的死信队列,然后应用监听死信队列,对接收到的死信做后续的处理。

2.4 死信队列设置

  • 首先需要设置死信队列的exchange和queue,然后进行绑定:
/**
     * 定义死信队列相关信息
     */
public final static String deadQueueName = "dead_queue";
public final static String deadRoutingKey = "#";
public final static String deadExchangeName = "dead_exchange";


@Bean
public TopicExchange deadExchange() {
    return new TopicExchange(deadExchangeName, true, false, null);
}


@Bean
public Queue deadQueue() {
    return new Queue(deadQueueName, true, false, false);
}

@Bean
public Binding orderPublishDlqBinding() {
    return BindingBuilder.bind(deadQueue() ).to(deadExchange()).with(deadRoutingKey);
}
  • 然后需要有一个监听,去监听这个队列进行处理
  @RabbitListener(queues = "dead_queue")
    public void test1(Message message) throws UnsupportedEncodingException {
        // 获取消息Id
        String messageId = message.getMessageProperties().getMessageId();
        String msg = new String(message.getBody(), "UTF-8");
        System.out.println("私信队列处理==>邮件消费者获取生产者消息msg:" + msg + ",消息id" + messageId);
        System.out.println("receive :" + message);
    }
  • 然后我们进行正常声明交换机、队列、绑定,只不过我们需要在队列加上一个参数即可:arguments.put(" x-dead-letter-exchange","dlx.exchange");,这样消息在过期、requeue、 队列在达到最大长度时,消息就可以直接路由到死信队列!
/**
 * 死信队列 交换机标识符
 */
public static final String DEAD_LETTER_QUEUE_KEY = "x-dead-letter-exchange";
@Bean
public Queue emailQueue() {
    // 将普通队列绑定到死信队列交换机上
    Map<String, Object> args = new HashMap<>(2);
    // x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
    args.put(DEAD_LETTER_QUEUE_KEY, deadExchangeName);
    // x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
    // 如果未配置x-dead-letter-routing-key则会按照原队列的key进行转发
    // args.put("x-dead-letter-routing-key", "x-dead-letter-routing-key");

    Queue queue = new Queue("email_queue", true, false, false, args);
    return queue;
}

@GetMapping("send2")
public void send(String email) throws JsonProcessingException {
    Map map = new HashMap<>();
    map.put("email", email);
    map.put("timestamp", 0);
    String jsonString = new ObjectMapper().writeValueAsString(map);
    System.out.println("jsonString:" + jsonString);
    // 设置消息唯一id 保证每次重试消息id唯一
    Message message = MessageBuilder.withBody(jsonString.getBytes())
        .setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8")
        .setMessageId(UUID.randomUUID() + "").build(); //消息id设置在请求头里面 用UUID做全局ID
    amqpTemplate.convertAndSend("email_queue", message);
}

正常的消息处理

@RabbitListener(queues = RabbitmqConfig2.EMAIL_QUEUE_NAME )
    public void handlerMessage3(Message message, MessageHeaders headers, Channel channel) throws Exception {
        
        byte[] body = message.getBody();
        String jsonString = new String(body,"utf-8");
        System.out.println("接收到的消息:" + jsonString);
        
        // 拒收消息后会进入死信队列
        channel.basicReject(headers.get("amqp_deliveryTag",Long.class),false);

    }

2.5 队列长度的指定

  • 对队列中消息的条数进行限制 x-max-length
  • 对队列中消息的总量进行限制 x-max-length-bytes

对消息总条数进行限制(总条数包括未被消费的消息+被消费但未被确认的消息):

3. 延时消息

可以通过设置消息的expiration字段或者x-message-ttl属性来设置时间(单位是毫秒),两者是一样的效果。只是expiration字段是字符串参数,所以要写个int类型的字符串:

 Message message = MessageBuilder.withBody(jsonString.getBytes())
     .setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8")
     // 设置延时时间
     .setExpiration(3+"")
     .setMessageId(UUID.randomUUID() + "").build(); //消息id设置在请求头里面 用UUID做全局ID
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RabbitMQ的死信Dead Letter)是指当消息无法被消费或被拒绝时,将消息发送到一个特定的队列中的机制。这个特定的队列被称为死信队列。引用中提到了Spring RabbitMQ死信机制的原理和实例,可以提供一些参考学习的价值。引用中也给出了一些应用场景,比如保证订单业务的消息数据不丢失,或者在商城中,用户下单成功但在指定时间内未支付时自动失效。而死信的来源主要有三种情况:消息的TTL过期,队列达到最大长度,消息被拒绝并且不再重新入队。可以根据这些来源来设计和应用死信机制。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [RabbitMQ中的死信死信队列详解](https://blog.csdn.net/qq_41865652/article/details/123311813)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Spring RabbitMQ死信机制原理实例详解](https://download.csdn.net/download/weixin_38500948/12742891)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [RabbitMQ 死信队列详解](https://blog.csdn.net/dingd1234/article/details/125024880)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值