rabbitmq学习笔记4 : 工作队列work queue

本文介绍RabbitMQ中的工作队列概念及其两种分配策略:轮询分发与公平分发。通过示例代码展示了如何实现这两种策略,并讨论了消息应答机制的重要性。

一、概述和模型

  1. 工作队列,多个消费者处理同一个消息队列,每条消息只会发给其中一个消费者。工作队列有两种处理方式,Round-robin(轮询分发)和Fair dispatch(公平分发)。应用场景,订单系统中多台服务器处理上游系统的下发过来的订单(个人认为原理跟负载均衡类似)。

          

 

二、轮询分发 Round-robin

  • 轮询分发,即将信息平分成N份,并推送给N个消费者。
  • 轮询分发实现跟简单队列类似,只是多了N个消费者
  • 创建MQ连接工具类
public class RabbitmqUntil {
    //获取连接
    public static Connection getRabbitmqConnection() throws Exception{
        //定义连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //设置服务地址
        factory.setHost("127.0.0.1");
        //设置端口(这里的端口号指定是AMQP协议所用的端口号)
        factory.setPort(5672);
        //设置数据库
        factory.setVirtualHost("/test");
        //设置用户名
        factory.setUsername("test");
        //设置密码
        factory.setPassword("test");
        return factory.newConnection();
    }
}
  • 创建消息发送者
public class WorkSender {
    public static final String QUEUE_NAME="work query";
    //发送信息
    public void sendMessage() throws Exception{
        //1、获取连接
        Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
        //2、获取管道
        Channel channel = rabbitmqConnection.createChannel();
        /**
         * 3、队列创建(如果队列存在,则不进行创建)
         * 参数1:队列名
         * 参数2:是否支持持久化,默认队列在内存中,重启会丢失。设置为true,会保存到erlang自带的数据库中
         * 参数3:当连接关闭后是否自动删除队列;是否私有队列,如果私有,其他通道不能访问当前队列
         * 参数4:当没有消费者,是否自动删除队列
         * 参数5:其他参数(通过BasicProperties进行传输)
         */
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //4、发布消息
        for (int i = 0; i < 50; i++) {
            String message="hello ,I am work queue"+i;
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        }
        //5、关闭连接
        channel.close();
        rabbitmqConnection.close();
    }

    public static void main(String[] args)throws Exception {
        WorkSender workSender = new WorkSender();
        workSender.sendMessage();
    }
}

  • 创建两个消费者
public class WorkReceiver1 {
    //队列名称
    public static final String QUEUE_NAME="work query";

    //接收信息
    public void receiveMessage()throws Exception{
        //1、获取连接
        Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
        //2、获取管道
        Channel channel = rabbitmqConnection.createChannel();
        //3、创建队列(如果队列已存在,则不进行处理)
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        //4、创建消费用户
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收者1:"+new String(body,"utf-8"));
                //模拟业务处理,0.5秒
                try {
                    Thread.sleep(500);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        };
        /**
         * 监听rabbitmq队列信息(此时程序会自动堵塞,当有数据到达时,处理相应的数据)
         * 参数1:队列名
         * 参数2:是否自动问答
         * 参数3:消费用户
         */
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }

    public static void main(String[] args)throws Exception {
        WorkReceiver1 workReceiver1 = new WorkReceiver1();
        workReceiver1.receiveMessage();
    }
}
public class WorkReceiver2 {
    //队列名称
    public static final String QUEUE_NAME="work query";

    //接收信息
    public void receiveMessage()throws Exception{
        //1、获取连接
        Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
        //2、获取管道
        Channel channel = rabbitmqConnection.createChannel();
        //3、创建队列(如果队列已存在,则不进行处理)
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //4、创建消费用户
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收者2:"+new String(body,"utf-8"));
                //模拟业务处理,1秒
                try {
                    Thread.sleep(1000);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        };
        /**
         * 监听rabbitmq队列信息(此时程序会自动堵塞,当有数据到达时,处理相应的数据)
         * 参数1:队列名
         * 参数2:是否自动问答
         * 参数3:消费用户
         */
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }

    public static void main(String[] args)throws Exception {
        WorkReceiver2 workReceiver2 = new WorkReceiver2();
        workReceiver2.receiveMessage();
    }
}
  • 运行main函数,可以看到,即使两个消费者处理消息的时间不一样,但是每个消费者都得到相同的消息。这就是轮询分发

 

三、公平分发 Fair dispatch

  • 公平分发,在多个消费者中,谁将消息处理完了,就将消息推送给该消费者,这里涉及到消息应答,即消费处理完成后,发送一个响应告诉rabbitmq。
  • 创建消费者(每次只给消费者发送一条消息)
public class WorkFairSender {
    //队列名称
    public static final String QUEUE_NAME="work fair query";

    //发送信息
    public void sendMessage() throws Exception{
        //1、获取连接
        Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
        //2、获取管道
        Channel channel = rabbitmqConnection.createChannel();
        //3、创建队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //4、设置每次发送信息不超过1条
        channel.basicQos(1);

        //5、发布消息
        for (int i = 0; i < 50; i++) {
            String message="hello ,I am work queue"+i;
            System.out.println("发布信息:"+message);
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        }
    }

    public static void main(String[] args)throws Exception {
        WorkFairSender workFairSender = new WorkFairSender();
        workFairSender.sendMessage();
    }
}
  • 创建两个消费者(使用消息应答机制)
public class WorkFairReceiver1 {
    //队列名称
    public static final String QUEUE_NAME="work fair query";

    //接收消息
    public void receiveMessage()throws Exception{
        //1、获取连接
        Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
        //2、获取管道
        final Channel channel = rabbitmqConnection.createChannel();
        //3、创建队列(如果队列已存在,则不进行处理)
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //4、设置每次处理息不超过1条
        channel.basicQos(1);
        //5、创建消费用户
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("公平分发接收者1:"+new String(body,"utf-8"));
                try {
                    Thread.sleep(500);
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    //处理完成后给rabbitmq一个应答响应
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        /**
         * 6、监听rabbitmq队列信息(此时程序会自动堵塞,当有数据到达时,处理相应的数据)
         * 参数1:队列名
         * 参数2:是否自动问答(设置为非自动问答)
         * 参数3:消费用户
         */
        channel.basicConsume(QUEUE_NAME,false,consumer);
    }

    public static void main(String[] args)throws Exception {
        WorkFairReceiver1 workFairReceiver1 = new WorkFairReceiver1();
        workFairReceiver1.receiveMessage();
    }
}
public class WorkFairReceiver2 {
    //队列名称
    public static final String QUEUE_NAME="work fair query";
    //接收消息
    public void receiveMessage()throws Exception{
        //1、获取连接
        Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
        //2、获取管道
        final Channel channel = rabbitmqConnection.createChannel();
        //3、创建队列(如果队列已存在,则不进行处理)
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //4、设置每次处理息不超过1条
        channel.basicQos(1);
        //5、创建消费用户
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("公平分发接收者2:"+new Date().getTime()+"  "+new String(body,"utf-8"));
                try {
                    Thread.sleep(10000);
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    //处理完成后给rabbitmq一个应答响应
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        /**
         * 6、监听rabbitmq队列信息(此时程序会自动堵塞,当有数据到达时,处理相应的数据)
         * 参数1:队列名
         * 参数2:是否自动问答(设置为非自动问答)
         * 参数3:消费用户
         */
        channel.basicConsume(QUEUE_NAME,false,consumer);
    }

    public static void main(String[] args)throws Exception {
        WorkFairReceiver2 workFairReceiver2 = new WorkFairReceiver2();
        workFairReceiver2.receiveMessage();
    }
}

      代码解析:

  1. 在发送者中添加channel.basicQos(1),表示每次只推送给消费者一条消息
  2. 在消费者中添加channel.basicQos(1),表示每次只处理一条消息
  3. 消费这处理完成后给rabbitmq一个应答响应channel.basicAck(envelope.getDeliveryTag(),false)
  4. 监听rabbitmq队列是,是否自动问答设置为false channel.basicConsume(QUEUE_NAME,false,consumer)
  • 运行程序,就会发现消费者1处理的消息比消费者多,这就是公平分发。

 

注:

  • 当程序设置为自动问答的时候,队列中消息被获取的时候,消息会自动被删除,如果这时消费者处理出现异常导致系统奔溃,此时数据丢失。非自动问答,需要在得到消费者的应答相应后才会删除队列中的消息,所以非自动问答,在一定程度上可以保证数据安全
  • 非自动应答在rabbitmq奔溃时,也会丢失数据,这个可用通过在定义队列的时候申明为支持持久化即可channel.queueDeclare(QUEUE_NAME,true,false,false,null)
  • 如果直接在上面的例子中修改,让程序支持持久化channel.queueDeclare(QUEUE_NAME,true,false,false,null),程序会报错,因为已经存在的队列不能修改其属性,必须先删掉原本的队列再进行修改

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值