RabbitMQ(mq) 如何处理高并发、负载均衡、消息幂等性、丢失、消息顺序错乱问题?

本文介绍了RabbitMQ的基础概念,包括连接器、信道、交换机和队列,并通过工作队列(一对一)和订阅/广播模式(一对多)的实例展示了其工作原理。在工作队列模式中,生产者直接发送消息到队列,消费者进行监听和确认消费。而在订阅模式下,交换机用于广播消息到多个队列。文章还探讨了mq处理高并发、负载均衡、消息幂等性和丢失的策略。
摘要由CSDN通过智能技术生成

目录

 

介绍:

1.连接器(connection):

2.信道、通道(channel):

3.交换机(exchange):

4.队列(queue):

以下通过两个例子,让我们先来对rabbitmq 有个认识


介绍:

1.连接器(connection):

用于连接我们已安装在各个系统平台中的mq服务,类似于jdbc 连接器

2.信道、通道(channel):

用于创建交换机、队列之间通信 并设置各个属性和配置

3.交换机(exchange):

交换机是用来接收发送方发送的数据,将数据放到绑定的队列中,一个交换机可以绑定多个队列,交换机这里有很多种模式,会在此文中为大家一一阐述。

4.队列(queue):

 队列-在数据结构中,属于先进先出的一种集合结构,在mq中队列用来存储数据,数据可以是绑定的交换机传进来的,也可以直接由发送方发送到队列中。

 

以下通过两个例子,让我们先来对rabbitmq 有个认识

这两个例子,均指的是mq发送消息的各个模式

一、工作队列(Work Queues)

介绍:工作队列属于 一对多 的模式,无交换机介入,直接传输数据到队列中,看图:

 P (product): 发送方,发送数据到队列中

中间红色:队列,数据的读取是先进先出

C(client):客户端,用于监听接收队列中的数据

/**
 * 连接本地的mq服务
 */
@Component
public class MqConnectionUtils {

    public static Channel getChannel() {
        ConnectionFactory factory = new ConnectionFactory();
    //这里的 localhost 可以换成你想要连接的ip地址
        factory.setHost("localhost");
        Connection connection = null;
        Channel channel = null;
        try {
            connection = factory.newConnection();
            channel = connection.createChannel();
        } catch (IOException | TimeoutException e) {
            e.printStackTrace();
        }
        return channel;
    }
}
/**
 * 工作队列-发送类
 */
public class MqSendHandler {

    //队列名
    private static final String QUEUE_NAME = "hello";


    public static void main(String[] args) throws IOException {
        //调用发送方法
        tutorials();
    }

    //这是一个发送的方法
    public static void tutorials() throws IOException {
        Channel channel = MqConnectionUtils.getChannel();
        // //Boolean.FALSE 非持久化, 2 Boolean.FALSE 不排它  //3 Boolean.FALSE 不自动删除
        channel.queueDeclare(QUEUE_NAME, Boolean.FALSE, Boolean.FALSE, Boolean.FALSE, null);
        String message = "hello world ";
        for (int i = 0; i < 10; i++) {
            String x = message + i;
            channel.basicPublish("", QUEUE_NAME, MessageProperties.MINIMAL_BASIC, x.getBytes());
        }
        System.out.println("end");
    }
}

/**
 * 接收队列消息的类
 */
public class MqRecvHandler {

    private static final String QUEUE_NAME = "hello";

    protected static volatile int num = 0;

    public static void main(String[] args) throws IOException {

        recOne();
    }

    public static void recOne() throws IOException {
        Channel channel = MqConnectionUtils.getChannel();

        //创建一个队列
        //1  Boolean.FALSE 是否持久化 2  Boolean.FALSE 是否排它 3 Boolean.FALSE 是否自动删除
        channel.queueDeclare(QUEUE_NAME, Boolean.FALSE, Boolean.FALSE, Boolean.FALSE, null);
        DeliverCallback deliverCallback = (consumerTag, deliver) -> {
            String message = new String(deliver.getBody(), StandardCharsets.UTF_8);
            System.out.println("消费者1:" + message);

            boolean flag = judgment();
            if (flag) {
                //手动确接收消息, Boolean.TRUE 代表消息接收的状态
                channel.basicAck(deliver.getEnvelope().getDeliveryTag(), Boolean.TRUE);
                System.out.println("消费者1,已确认消费:" + message);
            } else {
                //驳回消息,Boolean.FALSE 未收到消息或异常,Boolean.TRUE:确认驳回
                channel.basicNack(deliver.getEnvelope().getDeliveryTag(), Boolean.FALSE, Boolean.TRUE);
                System.out.println("消费者1,未确认消费:" + message);
            }
        };

           //收到消息后用来处理消息的回调对象
            DeliverCallback deliverCallback = new DeliverCallback() {
                @Override
                public void handle(String consumerTag, Delivery message) throws IOException {
                    String msg = new String(message.getBody(), "UTF-8");
                    System.out.println("收到: "+msg);
                }
            };

            //消费者取消时的回调对象
            CancelCallback cancelCallBack = new CancelCallback() {
                @Override
                public void handle(String consumerTag) throws IOException {
                     System.out.println("驳回到: "+consumerTag);
                }
            };



        // Boolean.FALSE 开启手动消费,默认是关闭
        //deliverCallback 接收方确认回调方法
        //cancelCallBack 接收方取消回调方法
        channel.basicConsume(QUEUE_NAME, Boolean.FALSE, deliverCallback, cancelCallBack-> {

        });


    }
}

 

二、订阅、广播模式(Publish/Subscribe)

 

 P (product): 发送方,发送数据到队列中

蓝色X: 是我们的交换机

中间红色:队列,数据的读取是先进先出

C(client):客户端,用于监听接收队列中的数据

 

/**
 *
 *
 * 订阅模式发送类
 * @author wuxiaotian
 * @version V1.0
 * @date 2021/4/1 16:33
 */

public class SubcribeSendHandler {
    public static void main(String[] args) {

        try{

             Channel channel = MqConnectionUtils.getChannel();
            channel.queueDeclare(SUB_QUEUE_NAME, Boolean.FALSE, Boolean.FALSE, Boolean.FALSE, null);

             //设置交换机器的名称 EXCHANGE_NAME
             channel.exchangeDeclare(SUB_EXCHANGE_NAME, "fanout");
            System.out.println("订阅模式开始!!!");

            String message = "subscribe world ";
            for (int i = 0; i < 10; i++) {
                String x = message + i;
                channel.basicPublish(SUB_EXCHANGE_NAME,"", MessageProperties.MINIMAL_BASIC, x.getBytes());
            }

            System.out.println("订阅模式发送完毕!!!");

        }catch (Exception e){
            e.printStackTrace();
        }
    }

}
/**
 *
 * 订阅模式接收类
 * @author wuxiaotian
 * @version V1.0
 * @date 2021/4/1 16:45
 */
public class SubcribeReviHandler {

    public static void main(String[] args) {
        try {

            Channel channel = MqConnectionUtils.getChannel();

            channel.exchangeDeclare(SUB_EXCHANGE_NAME, "fanout");

            //获取一个临时队列
            String queueName = channel.queueDeclare().getQueue();
            //队列与交换机绑定(参数为:队列名称;交换机名称;routingKey忽略)
            channel.queueBind(queueName,SUB_EXCHANGE_NAME,"");


            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                String message = new String(delivery.getBody(), "UTF-8");
                System.out.println("订阅模式接收到:" + message + "'");
            };




            //收到消息后用来处理消息的回调对象
            DeliverCallback callback = new DeliverCallback() {
                @Override
                public void handle(String consumerTag, Delivery message) throws IOException {
                    String msg = new String(message.getBody(), "UTF-8");
                    System.out.println("收到: "+msg);
                }
            };

            //消费者取消时的回调对象
            CancelCallback cancel = new CancelCallback() {
                @Override
                public void handle(String consumerTag) throws IOException {
                }
            };

            //第一种方式
            String s = channel.basicConsume(queueName, true, callback, cancel);


            //第二种方式
            // Boolean.FALSE 手动消费 ,deliverCallback 成功回调,cancelCallBack失败回调
            channel.basicConsume(SUB_EXCHANGE_NAME, Boolean.FALSE, deliverCallback,cancelCallBack -> {

            });


        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

通过这两种方式,我们已经认识了 有无交换机的 常用两种mq模式。学会是不是感觉很简单呢? 其实我也很菜!

其他模式,去官网自己看哈,培养你们的阅读能力,官网地址: https://www.rabbitmq.com/getstarted.html

一共有7种模式,今天给大家介绍的就是这两种噢:

答疑:

1.mq如何处理高并发?

答: mq本身只是用于接收数据,处理高并发 还是得通过线程池多线程去业务处理,但是 为了保证用户提交的数据不丢失,引入mq。

mq默认是轮询向每个接收方发送消息,可以设置每个队列接收的 QOS大小,以及在接收方收到消息后,

deliverCallback 确认回调加入线程池,将消息先存入线程池多线程处理数据,以保证队列不拥堵,解决一部分高并发问题,这里的高并发只是mq的解决方式,

深入高并发不是一个中间件可以搞定的,有机会会给大家介绍其他的中间件解决高并发的问题。

 

2.负载均衡mq怎么实现的?

答:由于mq本身在向每个接收方发送消息时 是轮询状态 保证每个接收方可接收到的消息都是一样的数量,这里我们抛开QOS 设定大小的问题。

 

3.消息幂等性、丢失怎么办?

答:每条队列中的消息 都应该有一个唯一标识,比如:id、订单号等,保证数据在处理时不会重复处理,并且队列创建时 也是判断了 不存在即创建,存在即不会创建。

丢失的问题 一般存在于 消息接收方一直接收异常 导致消息被驳回数量超过5次,此时应将消息存入 异常队列 或 其他中间件(redis)中 并记录日志,待夜深人静的时候.........我们程序媛就开始活动处理这些异常数据了!

 

4.消息顺序错乱问题?

答:这个问题有点扯,队列本来就是先进先出的啊,有顺序的。 当遇到驳回异常数据时,我们尽量驳回到异常队列中,避免顺序错乱。

 

以上这些都是理论,因为没有办法 这种问题一般都会出现在业务场景中,没办法给大家用代码阐述,但是我相信 你们比我更聪明!一定都能看懂的(自我感觉良好)。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值