【无标题】

本文详细介绍了如何在SpringAMQP中使用RabbitMQ进行消息的发送和接收,包括配置RabbitMQ服务、消费者监听队列、消息转换、消费者确认机制、延迟消息和死信交换机的使用。还讨论了生产者重连、确认机制、消息持久化和幂等性处理等问题。
摘要由CSDN通过智能技术生成

消息队列mq

SpringAMQP收发消息

1.引入依赖 spring-boot-starter-ampq
2.配置rabbitmq服务端信息
3.利用RibbitTemplate 发送消息
4.利用@RibbitListener注解申明要监听的队列,监听消息

发送消息

@Autowired
private RabbitTemplate rabbitTemplate

public void testSimpleQueue(){
    //队列名称
    String queueName = "simple.queue";
    //消息
    String message = "hello,spring amqp"
    //发送消息
    rabbitTemplate.convertAndSend(queueNae,message);

}

接受消息

@Component
public class SpringRabbitListener{
//绑定队列
    @RabbitListener(queues = "simple.queue")
    public void listenSimplequeueMessage(String msg) throws InterruptedException{
    log.info("Spring 消费者接受到消息:[" + msg + "]" );
    if(true){
        throw new essageConversionException("异常");
        }
     log.info("消息处理完成"):
}

消费者消息推送限制

默认情况下 RabbitMQ会将消息依次轮询投递给绑定在队列上的每一个消费者,但是没有考虑消费者是否已经处理完成消息,可能导致消息堆积
可以修改配置,设置preFetch的值为1,确保同一时刻最多投递给消费者1条信息

spring:
    rabbitmq:
        listener:
            simple:
                prefetch: 1

生产环境都会经过exchange来发送消息,而不是直接发送到队列,交换机的类别又三个
Fanout Exchange 广播模式 会将接受到的消息广播到每一个跟其绑定的queue
Direct 交换机 定向路由

  • 每一个Queue都与Exchange设置一个BindingKey
  • 发布者发送消息时,指定消息RoutingKey
  • Exchange将消息路由到BindingKey与消息RountingKey一致的队列
    toptic 可匹配交换机
    #匹配0个或多个单词
    *匹配多个单词

队列交换机创建
用bean直接new 或者用工厂创建

ExchangeBuilder.fanoutExchange("").build;
QueneBuilder.durable();//durable持久化
//简单创建用bean
@Bean
public Binding fanoutBindings(Queue q,FanoutExchange f){
    return BindingBuilder.bind(q).to(f); //.with()绑定key
    //return BindingBuilder.bind(Queue()).to(FanoutExchange());
    }

2简化创建和绑定
基于消费者的RabbitListener注解
@RabbitListener(bindIngs =@QueueBinding(
value= @Queue(name = ""),
exchange = @Exchange(name = "",type = ExchangeTypes.DIRECT),
key = {}
))
public void lestenDirectQueue1(String msg){
 System.out.println();
```}
消息转换器
当发送的消息是对象
spring的消息对象的处理是由org.springframework.amqp.support.converter.MessageConveerter 来处理的,而默认实现是SimplemessageConverter,基于JDKObjectOutputStream完成序列化

jdk存在的问题
jdk的序列化又安全风险
jdk序列化的消息太大
jdk序列化的消息可读性差
解决
建议采用json序列化替代
在publisher 和consumer中引入依赖
jackson-databind
在publisher 和consumer中配置MessageConveerter

```java
@Bean
public messageConverter messageConverter(){
    return new Jackson2JsonMmessageConverter();
}

生产者重连
又得时候由于网络波动,可能会出现客户端连接MQ失败的情况,通过配置我们可以开启链接失败后的重连机制
这中是阻塞式的,会影响性能。

spring:
    rabbitmq:
        connection-timeout: 1s //设置mq的连接超时时间
        template:
        retry:
        enabled: true // 开启超时重试机制
        initiaal-interval: 1000ms  //失败后的初始等待时间
        multiplier: 1// 失败后下次的等待时间倍数 下次等待时间 = initial-interval * multiplier
        max-attempts: 3 //最大重连次数
        

生产者确认

Rabbitmq 有两种确认机制 publisher confirm 和publisher return

  • 消息投递到了mq,但是路由失败,此时回通过publisherReturn返回路由异常,然后返回ack
  • 临时消息投递到了mq,并且成功入队,返回ack
  • 持久消息投递到了mq,并且入队完成持久化,返回ack
  • 其他情况返回nack,投递失败

在publisher微服务中添加配置

 spring:
     rabbitmq:
         publisher-confirm-type : correlated //开启认证机制是设置confirm类型
         publisher-returns: true  // 开启publisher return 机制

publisher-confirm-type 三种模式
none :关闭
simple: 同步阻塞等待mq的回执消息
correlated: mq异步回调方式返回回执消息

void test() throws InterruptedException{
    CorrelationData cd = new CorrelationData();
    cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>(){
    @Override
    public void onFailure(Throwable ex){
        log.error();
    }
    public void onSuccess(CorrelationData.Confirm result){
    'if(result.isAck()){
    }else{}
     log.error("",result.getReanson());
    }
    }
}

MQ的可靠性

在默认情况下,RabbitMQ会将接收到的信息保存在内存中意降低消息收发的延迟,但是这样会导致两个问题
一旦MQ掉线,内存的消息会丢失
内存的空间有限,当消费者故障或者处理过慢时,会导致消息积压,引发MQ阻塞

数据持久化

实现持久化有三个方面
交换机持久化
队列持久化
消息持久化

LazyQueue
从MQ的3.6.0版本开始,增加了LazyQueue概念
接收到消息后直接存入磁盘而非内存
消费者要消费消息时才会从磁盘读取并加载到内存
支持数百万条的额消息存储
3.12版本后所有队列都是LazyQueue 无法更改

设置一个队列为惰性队列
@Bean
public Queue LazyQueue(){
    return QueueBuilder
        .durable("lazy.queue")
        .lazy()
        .build();
}
@RabbitListener(queueToDeclare = @Queue(
    name = "lazy.queue",
    durable = "true",
    arguments = @Argument(name = "x-queue-mode", value = "lazy"))
public void listenLazyQueue(String msg){
    log.info();
}

开启持久化和生产者确认时,RabbitMQ只有在消息持久化完成后才会给生产者返回ACK回执

消费者确认

当消费者处理消息结束后,应该向RabbitMQ 发送一个回执,告知RabbitMQ
ack:当成功处理消息,RabbitMQ从队列中删除该消息
nack:消息处理失败,RabbitMQ需要再次投递消息
reject:消息处理失败并拒绝该消息,RabbitMQ从队列中删除该消息
springAMQP已经实现了消息确认功能,并允许我们通过配置文件选择ack处理方式 设置

spring:
  rabbitmq:
    listener:
      simple:
      acknowledge-mode : auto

none:不处理,
manual:手动模式
auto:自动模式,SpringAMQP利用对我们的消息处理逻辑做了环绕增强,当业务正常执行时则自动返回ack
当业务异常时,根据异常判断返回不同的结果
如果是业务异常,返回nack
如果是消息处理或校验异常,自动返回reject

失败重试机制

在开启重试模式后,重试次数耗尽,如果消息依然失败,则需要有MwssageRecover接口来处理,他包含三种不同的实现
RejectAndDontRequeueRecoverer: 重试耗尽后,直接reject,丢弃消息,默认
ImmediateRequeuMessageRecoverer:重试耗尽后,返回nack,消息重新入队
RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机

定义RepulishMessageRecoverer
@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
return new RepulishMessageRecoverer(rabbitTemplate,"error.direct","error");

消费者如何保证消息一定被消费

  • 开启消费者确认机制为auto,由spring确认消息处理成功后返回ack,异常时返回nack
  • 开启消费者失败重试机制,并设置MessageRecoverer,多次重试失败后将消息投递到异常交换机,交由人工处理

业务幂等性
消费者收到同意消息,只进行一次处理

方案-1
给每一个消息都设置一个唯一的id,与消息一起投递给消费者
消费者接受到消息后处理自己的业务,业务处理成功后将消息id保存到数据库
如果下次有收到相同的消息,去数据库验证是否存在,存在则为重复消息放弃处理

@Bean
public MessageConvertoer messageConverter(){
   //定义消息转换器
   Jackson2JsonMessageConverter jjmc = new Jackson2JsonMessageConverter();
   jjmc.setCreateMessageIds(true);
   return jjmc;
}

方案二,结合业务逻辑,基于业务本身做判断,修改订单状态前先查询订单状态,做判断

如何保证支付服务与交易服务之间的订单状态一致性
首先,支付服务会在支付成功后利用mq同志交易服务完成订单状态同步
为了保证mq消息的可靠性,我们采用生产者确认机制,消费者确认,消费者失败重试等策略,确保消息传递和处理的可靠性,同时开启mq的持久化避免消息丢失
最后 在交易服务更新订单时做业务幂等性判断,避免因消息重复导致的订单状态异常
当交易服务消息处理失败
实现RepublishMessageRecoverer失败消息绑定交换机,人工处理
我们可以在交易服务设置定时任务,springtask 定期查询订

延迟消息

延时消息
延迟任务
用来解决支付订单一直支付状态

死信交换机

死信:
消费者使用basic.reject或者basic.nack声明消费失败,并且消息的requeue参数设置为false
消息是个过期消息(达到了队列或消息本身设置的过期时间)无人消费
要投递的队列消息堆积满了,最早的消息可能成为死信
如果队列通过dead-letter-exchange属性指定了一个交换机,那么该队列中的死信就会投递到这个交换机中

@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "",durable = "true"),
exchangge = @Exchange(nae = "delay.direct",delayed = "true"),
key ="deley"
))
pbulic void listenDelayMessage(String sg){
    log.info("",msg)
}

或者使用bean的方式
public DirectExchange delayExchange(){
    return ExchangeBuilder
        .directExchange("delay.direct")
        .delayed(true)
        .durable(true)
        .build();
}

延迟消息插件
发送消息时需要通过消息头x-delay来设置过期时间
@Test
void testPublisherDelayMessage(){
String message = "";
rabbitTemplate.convertAndSend("delay.direct","delay",message, new MessagePostProcessor(){
    @Override
    public Message postProcessage(Message message) throws AmqpException{
    message.getMessageProperties().setDelay(5000);
    return message;
    }
});

取消超时订单
设置30分钟后检测订单支付状态实现存在问题
如果并发较高,30分钟可能堆积消息过多,对mq的压力很大
大多数的订单下单1分钟内就会支付,但是要在mq等待30分钟,浪费资源
优化,可以将30分钟分成几十次
比如先10s 检验一次,然后10s继续校验 ,10s 15s …
这样使的留在mq中的消息就很少

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值