RabbitMQ(三):工作队列

一、工作队列

官方文档:http://www.rabbitmq.com/tutorials/tutorial-two-java.html

简单队列不足:不支持多个消费者

即一个生产者可以对应多个消费者同时消费,相比简单队列支持多消费者。因为实际工作中,生产者服务一般都是很简单的业务逻辑处理之后就发送到队列, 消费者接收到队列的消息之后,进行复杂的业务逻辑处理,所以一般都是多个消费者进行处理。如果是一个消费者进行处理,那么队列会积压很多消息。

image

工作队列分两种情况:

  1. 轮询分发

    不管消费者处理速度性能快慢,每个消费者都是按顺序分别每个拿一个的原则,比如3个消费者, 消费者1拿1个,然后消费者2拿一个,然后消费者3拿一个,然后消费者1开始拿,即使中间有消费者已经处理完了,也必须等待其他消费者都拿完一个才能消费到。

  2. 公平分发

根据消费者处理性能,性能好的消费的数据量多,性能差的消费的数据量少。

如上所示,如果配置有用户名密码以及vhost,则配置即可。

二、轮询分发

一个生产者,两个消费者,其中消费者处理只要1s,消费者2 处理需要2s

连接RabbitMQ工具类

package cn.saytime.rabbitmq.util;

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * RabbitMQ连接工具类
 */
public class ConnectionUtil {

    private static final String host = "192.168.239.128";
    private static final int port = 5672;

    /**
     * 获取RabbitMQ Connection连接
     * @return
     * @throws IOException
     * @throws TimeoutException
     */
    public static Connection getConnection() throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(host);
        connectionFactory.setPort(port);

//        connectionFactory.setUsername("test");
//        connectionFactory.setPassword("123456");
//        connectionFactory.setVirtualHost("/vhost_test");

        return connectionFactory.newConnection();
    }
}
生产者
package cn.saytime.rabbitmq.work;

import cn.saytime.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 生产者
 */
public class Send {

    private static final String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionUtil.getConnection();
        // 从连接开一个通道
        Channel channel = connection.createChannel();
        // 申明这个通道连接的队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        for (int i = 0; i < 20; i++) {
            String message = "Hello RabbitMQ " + i;
            // 发送消息
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");
        }

        // 关闭通道和连接
        channel.close();
        connection.close();
    }

}
消费者1
package cn.saytime.rabbitmq.work;

import cn.saytime.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 消费者
 */
public class Recv {

    private static final String QUEUE_NAME = "test_work_queue";

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

        // 获取连接
        Connection connection = ConnectionUtil.getConnection();

        // 打开通道
        Channel channel = connection.createChannel();

        // 申明要消费的队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 创建一个回调的消费者处理类
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 接收到的消息
                String message = new String(body);
                System.out.println(" [1] Received '" + message + "'");

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(" [1] done ");
                }
            }
        };

        // 消费消息
        channel.basicConsume(QUEUE_NAME, true, consumer);

    }

}
消费者2
package cn.saytime.rabbitmq.work;

import cn.saytime.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 消费者
 */
public class Recv2 {

    private static final String QUEUE_NAME = "test_work_queue";

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

        // 获取连接
        Connection connection = ConnectionUtil.getConnection();

        // 打开通道
        Channel channel = connection.createChannel();

        // 申明要消费的队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 创建一个回调的消费者处理类
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 接收到的消息
                String message = new String(body);
                System.out.println(" [2] Received '" + message + "'");

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(" [2] done ");
                }
            }
        };

        // 消费消息
        channel.basicConsume(QUEUE_NAME, true, consumer);

    }

}
测试
  1. 先启动两个消费者Recv、Recv2
  2. 再启动生产者Send

生产者控制台:

 [x] Sent 'Hello RabbitMQ 0'
 [x] Sent 'Hello RabbitMQ 1'
 [x] Sent 'Hello RabbitMQ 2'
 [x] Sent 'Hello RabbitMQ 3'
 [x] Sent 'Hello RabbitMQ 4'
 [x] Sent 'Hello RabbitMQ 5'
 [x] Sent 'Hello RabbitMQ 6'
 [x] Sent 'Hello RabbitMQ 7'
 [x] Sent 'Hello RabbitMQ 8'
 [x] Sent 'Hello RabbitMQ 9'
 [x] Sent 'Hello RabbitMQ 10'
 [x] Sent 'Hello RabbitMQ 11'
 [x] Sent 'Hello RabbitMQ 12'
 [x] Sent 'Hello RabbitMQ 13'
 [x] Sent 'Hello RabbitMQ 14'
 [x] Sent 'Hello RabbitMQ 15'
 [x] Sent 'Hello RabbitMQ 16'
 [x] Sent 'Hello RabbitMQ 17'
 [x] Sent 'Hello RabbitMQ 18'
 [x] Sent 'Hello RabbitMQ 19'

Process finished with exit code 0

消费者1:

 [1] Received 'Hello RabbitMQ 0'
 [1] done 
 [1] Received 'Hello RabbitMQ 2'
 [1] done 
 [1] Received 'Hello RabbitMQ 4'
 [1] done 
 [1] Received 'Hello RabbitMQ 6'
 [1] done 
 [1] Received 'Hello RabbitMQ 8'
 [1] done 
 [1] Received 'Hello RabbitMQ 10'
 [1] done 
 [1] Received 'Hello RabbitMQ 12'
 [1] done 
 [1] Received 'Hello RabbitMQ 14'
 [1] done 
 [1] Received 'Hello RabbitMQ 16'
 [1] done 
 [1] Received 'Hello RabbitMQ 18'
 [1] done 

消费者2:

 [2] Received 'Hello RabbitMQ 1'
 [2] done 
 [2] Received 'Hello RabbitMQ 3'
 [2] done 
 [2] Received 'Hello RabbitMQ 5'
 [2] done 
 [2] Received 'Hello RabbitMQ 7'
 [2] done 
 [2] Received 'Hello RabbitMQ 9'
 [2] done 
 [2] Received 'Hello RabbitMQ 11'
 [2] done 
 [2] Received 'Hello RabbitMQ 13'
 [2] done 
 [2] Received 'Hello RabbitMQ 15'
 [2] done 
 [2] Received 'Hello RabbitMQ 17'
 [2] done 
 [2] Received 'Hello RabbitMQ 19'
 [2] done 

可以发现消费者1 消费的数字全是整数,消费者2消费的全是奇数。

那么如果我事先启动三个消费者了,那么结果如何了?

因为20/3 除不了,如果消费者1,2,3按顺序启动,那么消费者1, 2会消费7条数据,消费者3消费6条,其中

消费者1 消费 0, 3, 6, 9, 12, 15, 18
消费者2 消费 1, 4, 7, 10, 13, 16, 19
消费者3 消费 2, 5, 8, 11, 14, 17

注意点:

如果生产者一次性发送完消息之后,再依次启动消费者1, 2, 3, 之后只有消费者1 能消费到数据,消费者都启动之后,再生产的消息就会轮询分发到消费者1, 2, 3

三、公平分发

官方示例图:

image

因为生产者发送消息到队列之后,队列不知道消费者有没有处理完,所以多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者。为了实现公平分发,我们需要告诉队列,每次发一个给我,然后我反馈给你我有没有处理完,处理完了你再发一条给我。

在默认轮询分发的基础上,要实现公平分发,需要两点:

  1. 限制发送给同一个消费者不得超过1条消息,在这个消费者确认消息之前,不会发送下一条消息给这个消费者
int prefetchCount = 1;
channel.basicQos(prefetchCount);
  1. 默认自动应答改成手动应答

关闭自动应答
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);

手动应答
channel.basicAck(envelope.getDeliveryTag(), false);

DeliveryTag 用来标识信道中投递的消息, RabbitMQ 推送消息给 Consumer 时,会附带一个 Delivery Tag,以便 Consumer 可以在消息确认时告诉 RabbitMQ 到底是哪条消息被确认了。
生产者
package cn.saytime.rabbitmq.workfair;

import cn.saytime.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 生产者
 */
public class Send {

    private static final String QUEUE_NAME = "test_workfair_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionUtil.getConnection();
        // 从连接开一个通道
        Channel channel = connection.createChannel();
        // 申明这个通道连接的队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        channel.basicQos(1);

        for (int i = 0; i < 20; i++) {
            String message = "Hello RabbitMQ " + i;
            // 发送消息
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");
            try {
                Thread.sleep(i*100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 关闭通道和连接
        channel.close();
        connection.close();
    }

}
消费者1
package cn.saytime.rabbitmq.workfair;

import cn.saytime.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 消费者
 */
public class Recv {

    private static final String QUEUE_NAME = "test_workfair_queue";

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

        // 获取连接
        Connection connection = ConnectionUtil.getConnection();

        // 打开通道
        final Channel channel = connection.createChannel();

        // 申明要消费的队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理一个Message。换句话说,在接收到该Consumer的ack前,他它不会将新的Message分发给它。
        channel.basicQos(1);

        // 创建一个回调的消费者处理类
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 接收到的消息
                String message = new String(body);
                System.out.println(" [1] Received '" + message + "'");

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(" [1] done ");
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        // 消费消息
        channel.basicConsume(QUEUE_NAME, false, consumer);

    }

}
消费者2
package cn.saytime.rabbitmq.workfair;

import cn.saytime.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 消费者
 */
public class Recv2 {

    private static final String QUEUE_NAME = "test_workfair_queue";

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

        // 获取连接
        Connection connection = ConnectionUtil.getConnection();

        // 打开通道
        Channel channel = connection.createChannel();

        // 申明要消费的队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理一个Message。换句话说,在接收到该Consumer的ack前,他它不会将新的Message分发给它。
        channel.basicQos(1);

        // 创建一个回调的消费者处理类
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 接收到的消息
                String message = new String(body);
                System.out.println(" [2] Received '" + message + "'");

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(" [2] done ");
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        // 消费消息
        channel.basicConsume(QUEUE_NAME, false, consumer);

    }

}
测试

消费者1:

 [1] Received 'Hello RabbitMQ 0'
 [1] done 
 [1] Received 'Hello RabbitMQ 2'
 [1] done 
 [1] Received 'Hello RabbitMQ 4'
 [1] done 
 [1] Received 'Hello RabbitMQ 5'
 [1] done 
 [1] Received 'Hello RabbitMQ 7'
 [1] done 
 [1] Received 'Hello RabbitMQ 8'
 [1] done 
 [1] Received 'Hello RabbitMQ 10'
 [1] done 
 [1] Received 'Hello RabbitMQ 11'
 [1] done 
 [1] Received 'Hello RabbitMQ 13'
 [1] done 
 [1] Received 'Hello RabbitMQ 14'
 [1] done 
 [1] Received 'Hello RabbitMQ 15'
 [1] done 
 [1] Received 'Hello RabbitMQ 17'
 [1] done 
 [1] Received 'Hello RabbitMQ 19'
 [1] done 

消费者2:

 [2] Received 'Hello RabbitMQ 1'
 [2] done 
 [2] Received 'Hello RabbitMQ 3'
 [2] done 
 [2] Received 'Hello RabbitMQ 6'
 [2] done 
 [2] Received 'Hello RabbitMQ 9'
 [2] done 
 [2] Received 'Hello RabbitMQ 12'
 [2] done 
 [2] Received 'Hello RabbitMQ 16'
 [2] done 
 [2] Received 'Hello RabbitMQ 18'
 [2] done 

显然消费者1处理速度只要1s,所以消费的记录数要比消费者2要多很多。表示确实是公平分发。

注意点:

当关闭自动应答autoAck=false之后,在消费者处理消费数据之后一定要对消息进行手动反馈处理,可以是basicAck,也可以是basicNack, basicReject


BasicReject一次只能拒绝接收一个消息,而BasicNack方法可以支持一次0个或多个消息的拒收,并且也可以设置是否requeue。


// 拒绝当前消息,并使这条消息重新返回到队列中
channel.basicNack(envelope.getDeliveryTag(), false, true);
相当于
channel.basicReject(envelope.getDeliveryTag(), true);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值