RabbitMQ

本文介绍了如何在RabbitMQ中传递对象类型参数,包括对象的序列化和反序列化。同时,讨论了RabbitMQ的发送消息并接收反馈结果的机制,以及ACK消息确认机制的重要性,强调了消息幂等性的解决方案。此外,还提到了如何保证消息的可靠传输和处理消息积压的方法,包括设置消息过期时间和动态调整消费者数量。
摘要由CSDN通过智能技术生成

一、如何传递对象类型参数?

在RabbitMQ中,如果Pushlisher发送的消息是基本数据类型或String类型,可以在Consumer中直接使用对应类型或可以转换的类型进行接收。
如果发送的消息是对象或集合这种复杂类型,旧版本RabbitMQ会把这些数据进行序列化后放入到Message的body中。Consumer接收时方法参数应该为Message,并对Message中body进行反序列获取到对象或集合数据。新版本则直接可以用实体类作为参数。

  1. 创建实体类,并序列化。如果消息是对象类型,此对象的类型必须进行序列化,且需要给定序列化值。
/**
 1. 用户表
 2.  3. @date 2022/4/18
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
//@TableName("rbac_user")
public class User implements Serializable {
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    private String username;
    private String password;
    private Integer age;
    private String phone;
    private String status;
    private String salt;
    private String email;
    private Date createTime;

    @TableField(exist = false)
    private Date start;
    @TableField(exist = false)

    private Date end;
    private int isDeleted;

    public User(String username, Integer age) {
        this.username = username;
        this.age = age;
    }

    public User() {

    }
}
  1. 生产方发送消息
/**
     * 对象类型的参数传递
     *
     * @Return:
     * @Author: wxy
     */
    @Override
    public String objectMsg() {
        amqpTemplate.convertAndSend("test.topic", "com.hetuan.send.object.message", new User("王五", 60));
        return "发送完毕";
    }
  1. 消费方接收消息
    /**
     * 对象类型消息的接收 <p>实体类的包名和类名必须保持一致</p>
     *
     * @Param: msg
     * @Return:
     * @Author: wxy
     */
    @RabbitListener(queues = {"topic3"})
    public void fanout3(User msg) {
        // 扇形交换器可以不写路由键名称
        System.out.println("topic3消费了消息:" + msg);
    }

注意: 正常情况下,应该有一个公共项目,里面写上实体类,这个项目分别被消息发送服务消息接收服务去依赖。如果是直接在Publisher服务和Consumer服务中新建的实体类,也必须保证两个服务中类的全限定路径完全一致,否则无法进行获取对象消息。

二、RabbitMQ发送消息并接收反馈结果

使用RabbitMQ很多情况都需要使用RabbitMQ的队列功能对数据进行排序。如果使用异步类型消息,Publisher发送完成消息后是没有任何反馈结果的,如果需要反馈结果就需要使用AmqpTemplate中convertSendAndReceive,并Consumer项目监听方法必须有返回值。


场景:秒杀、抢红包等功能时都适用。


当使用convertSendAndReceive消息由异步变成同步,阻塞主线程。发送给队列消息后需要Consumer返回ACK值(监听方法返回值),所以在使用这个功能时,都是先启动Consumer后发送消息。

  1. 生产方发送消息并接收消费方返回值
    @Override
    public Object sendAndReceive() {
        Object result = amqpTemplate.convertSendAndReceive("test.direct", "queue-sync", new User("王五", 20));
        if (result != null) {
            return result;
        }
        return result != null ? result : "未接收到消费方返回值";
    }
  1. 消费方接收消息
    @RabbitListener(queues = "queue-sync")
    public String sync(User msg) {
        return msg.getUsername();
    }

因为同步消费消息时,生产者会等待消费者的返回值,如果没有收到consumer的返回值,线程会一直阻塞,为了不在继续阻塞主线程,让队列继续向下执行,RabbitMQ设了默认等待时间,超过等待时间则返回值为null,也可自己设置等待时间。

spring:
  rabbitmq:
    template:
      # 同步操作时、设置等待消费者返回值的时间 ,单位毫秒
      reply-timeout: 10000

三、ACK消息确认机制

ACK 机制是消费者从 RabbitMa 收到消息并处理完成后,反馈给 RabbitMQ,RabbitMQ收到反馈后才将此消息从队列中删除。


如果一个消费者在处理消息出现了网络不稳定、服务器异常等现象(如int a = 10/0),那么就不会有 ACK反馈,RabbitMQ会认为这个消息没有正常消费,会将消息重新放入队列中,容易出现重复消费情况。


如果在集群的情况下,RabbitMQ 会立即将这个消息推送给这个在线的其他消费者。这种机制保证了在消费者服务端故障的时候,不丢失任何消息和任务。


消息永远不会从 RabbitMQ 中删除,只有当消费者正确发送 ACK 反馈,RabbitMQ 确认收到后,消息才会从 RabbitMQ 服务器的数据中删除消息的ACK 确认机制默认是打开的。

如果希望关闭ACK机制,可以开启重试,通过设置重试次数,到达指定次数后删除消息。

spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true # 默认false,开启重试。
          max-attempts: 2 # 默认3

注意: 不建议关闭ack保护机制。这么做虽然不会让当前消息一直阻塞下去,但是会造成消息丢失,因为消息会被从队列中删除了。

四、消息幂等性(重复发送和重复消费问题)

在MQ中可能出现消息幂等性的情况:

  1. (重复发送)Publisher给MQ发送消息的时候,MQ在给Publisher返回ACK时由于网络中断等问题,没有成功返回。Publisher会认为消息没有发送成功,在网络恢复后会重新发送消息。
  2. (重复消费)Consumer接收到消息后,在给MQ返回ACK时由于网络问题,MQ没有成功接收ACK,MQ会认为此消息没有正确消费。在网络重连后会把消息重新发送给此消费者,或重新广播给其他所有消费者。

解决办法

  1. 解决重复发送问题。MQ内部会给每个消息生成一个唯一ID。当消息接收到后会判断此ID。
    public Object repeatSend() {
        // RabbitMQ会自己内置的去比较全局的uuid是否存在,只要页面不刷新,产生的uuid一定不会变,检测到生产者发送过这个id,就不再发送消息
        MessageProperties mp = new MessageProperties();
        mp.setMessageId(IdUtil.randomUUID().toLowerCase());
        User user = getData();
        Message message = new Message("消息内容".getBytes(), mp);
        amqpTemplate.send("test.direct", "queue-sync", message);
        return null;
    }
  1. 解决重复消费问题。在Consumer中可以通过消息的唯一ID进行判断是否已经消费过(借助Redis等工具每次消费都要记录已经消费过),也可以在每条消息中自定义唯一标识,判断是否已经消费过。
    /**
     * 消费者避免消息重复消费
     *
     * @Param: msg
     * @Return:
     * @Author: wxy
     */
    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(name = "queue-sync"),
                    exchange = @Exchange(name = "test.direct", type = "direct"), key = "queue-sync"))
    public void sync(Message msg) {
        MessageProperties properties = msg.getMessageProperties();
        // 1、获取messageId
        String messageId = properties.getMessageId();
        // 2、根据messageId去redis查,如果key存在,说明已经被消费
        // 3、正常的进行消息消费

        // 4、如果消费成功,以messageId作为键,存入到redis
        System.out.println(messageId);
    }

五、如何保证消息的可靠性传输

生产者弄丢了数据

开启confirm机制或者RabbitMQ事务(不推荐,吞吐量会下降)
事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息RabbitMQ接收了之后会异步回调你一个接口通知你这个消息接收到了。

RabbitMQ弄丢了数据

开启持久化

消费端弄丢了数据

ack确认机制

六、如何保证消息的顺序性

七、消息积压怎么办?

消息的生产速度大于消息的消费速度

  1. 临时扩充几个消费者服务。

大量消息在mq里积压了几个小时了还没解决几千万条数据在MQ里积压了七八个小时,从下午4点多,积压到了晚上很晚,10点多,11点多。这个是我们真实遇到过的一个场景,确实是线上故障了,这个时候要不然就是修复consumer的问题,让他恢复消费速度,然后傻傻的等待几个小时消费完毕。这个肯定不能在面试的时候说吧。
  一个消费者一秒是1000条,一秒3个消费者是3000条,一分钟是18万条,1000多万条,所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大概1小时的时间才能恢复过来。
  一般这个时候,只能操作临时紧急扩容了,具体操作步骤和思路如下:
1、先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉。
2、新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的queue数量。
3、然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue。
4、接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据。
5、这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据。等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息。

  1. 给消息设置过期时间。

八、消息队列过期失效问题

假设你用的是rabbitmq,rabbitmq是可以设置过期时间的,就是TTL,如果消息在queue中积压超过一定的时间就会被rabbitmq给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在mq里,而是大量的数据会直接搞丢。
  这个情况下,就不是说要增加consumer消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。
  这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入mq里面去,把白天丢的数据给他补回来。也只能是这样了。
  假设1万个订单积压在mq里面,没有处理,其中1000个订单都丢了,你只能手动写程序把那1000个订单给查出来,手动发到mq里去再补一次。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值