单机rabbitmq可靠性实战

rabbitmq全局架构图

在这里插入图片描述

这是一张经典的rabbitMQ模型图
我们可以简单把角色分为两类
客户端和服务端,客户端就是请求处理消息的机器,服务端就是完成路由,具体处理消息的机器。
客户端分为两个角色,生产者和消费者,生产者是发送消息方,消费者接收消息方。
服务端也称为broker。
Connection就是一次TCP连接,但是网络通信总是有阻塞的,所以我们需要更细粒度的channel来接收任务,执行任务,如果是消费端还要返还ack值。

配置项

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring:
  rabbitmq:
    host: 192.168.0.0 # 主机名
    port: 5672 # 默认端口
    virtual-host: / # 虚拟主机
    username: rabbitmqadmin # 用户名
    password: rabbitmqadmin # 密码

序列化

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

有两个重点类JSON和JSONObject,其中JSON是个抽象类,是JSONObject的父类
所以它们都有序列化的方法toJsonString(),以及反序列化的方法parseObject

//序列化过程
String msg = JSONObject.toJSONString(message, SerializerFeature.WriteNullListAsEmpty);
//反序列化过程
Message<MonitorMessageBody> msg = JSON.parseObject(msg,Message.class);

需要注意的是,反序列化只是一种浅层反序列化,自己的成员只解析它的结构,如果这个成员内部还有reference,就用JSONObject进行包装而不是继续解析,因为实际上并没有这个类的类文件,而只提供了外层类文件如Message.class;

Message<MonitorMessageBody> m = JSON.parseObject(msg,Message.class);
//单层get是允许的
MonitorMessageBody message = m.getBody().getMessageBody();
//对内部对象再次get是会报错的,因为此时是一个JSONObject对象

在这里插入图片描述
有两种解决方法:
第一种是根据JSON字符串自己写一个方法深拷贝这个对象(建议使用BeanUtils.copyproperties()这样的工具帮忙get和set属性加快速度)
第二种是使用JSON的API取到需要的层次再解析

JSONObject jsonObject = JSON.parseObject(msg);
JSONObject jsonObject1 = (JSONObject) jsonObject.get("body");
JSONObject jsonObject2 = (JSONObject) jsonObject1.get("messageBody");
MonitorMessageBody message = jsonObject2.toJavaObject(MonitorMessageBody.class);

队列和交换机

需要声明一个交换机,队列,和之间的绑定,绑定的原因是,可以有多个交换机,并且交换机被多个队列绑定

@Configuration
public class MQConfig {

    public static final String MONITOR_EXCHANGE = "monitor.direct";

    public static final String MONITOR_QUEUE= "monitor.queue";

    /**
     * * 交换机
     * @return
     */
    @Bean("monitorExchange")
    public DirectExchange monitorExchange(){
        return new DirectExchange(MONITOR_EXCHANGE);
    }

    /**
     * * 队列
     * @return
     */
    @Bean("monitorQueue")
    public Queue monitorQueue(){
        return QueueBuilder.durable(MONITOR_QUEUE).build();
    }

    /**
     * * 队列和交换机的绑定关系
     * @param queue
     * @param exchange
     * @return
     */
    @Bean
    public Binding queueABinding(@Qualifier("monitorQueue") Queue queue,
                                 @Qualifier("monitorExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("");
    }

}

生产消费

@Component
public class RabbitMqSender{

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public final String ROUTING_KEY = "key";
    
    /**
     * @param msg 需要发送的信息
     */
    public void send(String msg,String messageNo) {
        //让消息绑定一个 id 值
        rabbitTemplate.convertAndSend(MQConfig.MONITOR_EXCHANGE,ROUTING_KEY,msg);
    }

}
@Component
public class RabbitMqListener{
/*
 * 消费方法
 * @param msg
 */
    @RabbitListener(queues = MQConfig.MONITOR_QUEUE)
    public void listenDemo(String msg) {
    }
}

可靠性保证

可靠性分析

在这里插入图片描述
在RabbitMQ服务器非集群情况下,可以根据图把可靠分为三个阶段,保证到交换机的可靠,保证到队列的可靠,保证消费者收到的可靠。

到交换机的可靠

spring:
  rabbitmq:
    publisher-confirm-type: correlated
@Component
public class RabbitMqSender implements RabbitTemplate.ConfirmCallback{

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
    }

    /**
     * @param msg 需要发送的信息
     */
    public void send(String msg,String messageNo) {
        //让消息绑定一个 id 值
        CorrelationData correlationData = new CorrelationData(messageNo);
        log.info("发送消息id为:{}内容为{}",correlationData.getId(),msg);
        rabbitTemplate.convertAndSend(MQConfig.MONITOR_EXCHANGE,"",msg,correlationData);
        System.out.println("发送完成");
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "unknown";
        System.out.println(ack);
        if(!ack) {
            log.error("消息 id:{}未成功投递到交换机,原因是:{}", id, cause);
        }
    }

}

到队列的可靠

spring:
  rabbitmq:
    template:
      mandatory: true
@Component
public class RabbitMqSender implements RabbitTemplate.ReturnsCallback{

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        rabbitTemplate.setReturnsCallback(this);
    }

    /**
     * @param msg 需要发送的信息
     */
    public void send(String msg,String messageNo) {
        //让消息绑定一个 id 值
        CorrelationData correlationData = new CorrelationData(messageNo);
        log.info("发送消息id为:{}内容为{}",correlationData.getId(),msg);
        rabbitTemplate.convertAndSend(MQConfig.MONITOR_EXCHANGE,"",msg,correlationData);
        System.out.println("发送完成");
    }

    @Override
    public void returnedMessage(ReturnedMessage returned) {
        String id = returned.getMessage().getMessageProperties().getHeader("spring_returned_message_correlation").toString();
        log.error("消息 id:{}未成功投递到队列,内容为:{}",id,returned.getReplyText());
    }

}

服务器到消费者的可靠

在direct或fanout模式下,两个队列绑定交换机,会报错,交换机只要一个ack。
但是在simple模式下不会报错,simple模式下会等待两个ack。

spring:
  rabbitmq:
    listener:
      direct:
        acknowledge-mode: manual
  //  simple:
  //    acknowledge-mode: manual

配置之后只有手动ack,才会消费掉。

@RabbitListener(queues = MQConfig.MONITOR_QUEUE)
public void listenAlarmDirect(String msg, org.springframework.amqp.core.Message mes, Channel channel) throws IOException {
    try{
        long tag = mes.getMessageProperties().getDeliveryTag();
        //do something
        //逐个回应
        channel.basicAck(tag,false);
    }catch(Exception e){
        //自动返回队列
        channel.basicReject(tag,true);
    }
}

可靠性不一定非要保证

笔者接受过需求,每三秒发一条状态信息,这时候根本不需要应答,而自动应答反而可能会因为消息逻辑错误(尝试了向数据库存记录),不断重发消息,导致消息累加,这时候不如可以使用不应答或者手动直接应答ack

listener:
  simple:
    acknowledge-mode: NONE

来源

本人语雀笔记(语雀分享竟然收费了!失踪人口回归)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RabbitMQ在设计时考虑了可靠性投递的问题,并提供了多种机制来保证消息的可靠投递。在解决可靠性投递之前,需要明确一个问题:效率和可靠性往往无法兼得。如果对消息的收发效率要求较高,可以在一些业务一致性要求不高的场景下,牺牲一定的可靠性来换取效率,比如发送通知或记录日志的场景。 在使用RabbitMQ实现异步通信过程中,可能会遇到消息丢失或消息重复的问题。为了解决这些问题,可以采取以下方法: 1. 使用可靠性投递模式:RabbitMQ提供了可靠性投递的模式,确保消息被成功发送和接收。可以使用以下机制来实现可靠性投递: - 消息持久化:将消息标记为持久化,确保即使在RabbitMQ重启后,消息也不会丢失。 - 手动确认:在消费者接收到消息后,手动发送确认信号给RabbitMQ,告知消息已被处理。只有当RabbitMQ收到确认信号后,才会删除该消息,确保消息不会被重复消费。 - 限制重试次数:如果消息处理失败,可以将消息重新发送到队列中,并设置最大重试次数,避免无限循环重试。 2. 使用消息去重机制:为了避免重复消费消息,可以在消费者端引入消息去重的机制。可以使用唯一标识符(如消息ID)来判断消息是否已经处理过。在消费者处理消息之前,先查询数据库或缓存,检查消息是否已经存在,如果已经存在,则不再处理。 3. 使用幂等性操作:在处理消息的业务逻辑中,应该尽量保证操作的幂等性。即使消息重复投递,也不会对业务数据产生重复影响。通过设计幂等性的操作,即使消息被重复消费,也不会引起数据不一致的问题。 综上所述,在使用RabbitMQ实现可靠性投递时,可以采用可靠性投递模式、消息去重和幂等性操作等方法来保证消息的可靠性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值