RabbitMQ 工作模式

RabbitMQ 提供了 6 种工作模式:

  • 简单模式
  • work queues
  • Publish/Subscribe 发布与订阅模式
  • Routing 路由模式
  • Topics 主题模式
  • RPC 远程调用模式(远程调用,不太算MQ;暂不作介绍)。

实例代码引用的依赖:

    <dependencies>
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>4.0.2</version>
        </dependency>
    </dependencies>

1 simple - 简单队列

①模型:

P:消息的生产者

红色的:队列

C:消费者

3个对象: 生产者  队列 RabbitMQ  消费者

②获取MQ连接

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

public class ConnectionUtils {
    /*
    * 获取 MQ 的连接
    * */
    public static Connection getConnection() throws Exception {
        //1. 定义一个连接工厂
        ConnectionFactory factory = new ConnectionFactory();

        //2. 获取服务地址
        factory.setHost("127.0.0.1");

        //3. AMQP 5672
        factory.setPort(5672);

        //4. vhost
        factory.setVirtualHost("/vhost_lyd");

        //5. 用户名
        factory.setUsername("user_lyd");

        //6. 密码
        factory.setPassword("1234");

        return factory.newConnection();
    }
}

③生产者生产消息

import com.janet.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Send {
    private static final String QUEUE_NAME = "test_simple_queue";

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

        //1. 获取一个连接
        Connection connection = ConnectionUtils.getConnection();

        //2. 从连接中获取一个通道
        Channel channel = connection.createChannel();

        //3. 声明并创建一个队列,如果队列已存在,则使用这个队列
        //第一个参数:队列名称ID
        //第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
        //第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用,其他消费者不让访问
        //第四个:是否自动删除,false代表连接停掉后不自动删除掉这个队列
        //其他额外的参数, null
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        String msg = "hello simple";

        //第一个参数:exchange 交换机,暂时用不到,在后面进行发布订阅时才会用到
        //第二个参数:队列名称
        //第三个参数:额外的设置属性
        //最后一个参数:要传递的消息字节数组
        channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());

        System.out.println("-----send msg:"+msg);

        channel.close();
        connection.close();

    }
}

运行之后:

④消费者接收消息

import com.janet.util.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 *  消费者获取消息
 */
public class Recv {
    private static final String QUEUE_NAME = "test_simple_queue";

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

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

        //2. 创建通道
        Channel channel = connection.createChannel();

        //3. 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        DefaultConsumer consumer = new DefaultConsumer(channel) {
            //获取到达的消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("api recv: " + msg);
            }
        };

        //4. 监听队列
        //创建一个消息消费者
        //第一个参数:队列名
        //第二个参数代表是否自动确认收到消息,false代表手动编程来确认消息,这是MQ的推荐做法
        //第三个参数要传入DefaultConsumer的实现类
        channel.basicConsume(QUEUE_NAME,true,consumer);

    }
}

⑤简单队列的不足:

耦合性高,生产者一一对应消费者(如果想有多个消费者消费队列中消息,这时候就不行了),如果消费者的队列名变更,这时候消费者得同时变更。

2 work queues - 工作队列

为什么会出现工作队列:

Simple队列 是一一对应的,而且我们实际开发,生产者发送消息是毫不费力的,而消费者一般是要跟跟业务相结合的,消费者接收到消息之后就需要处理,可能需要花费时间,这时候队列就挤压了很多消息。

模型:

1)轮询分发

生产者:

import com.janet.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Send {
    /*
    *                  |-----C1
    * P ----Queue------|
    *                  |-----C2
    * */
    private static final String QUEUE_NAME = "test_work_queue";
    public static void main(String[] args) throws Exception {
        //1. 获取一个连接
        Connection connection = ConnectionUtils.getConnection();

        //2. 从连接中获取一个通道
        Channel channel = connection.createChannel();

        //3. 声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        for(int i = 0; i <40; i++){
            String msg = "hello"+i;
            System.out.println("[MQ] send:"+msg);
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
            Thread.sleep(i*20);
        }

        channel.close();
        connection.close();
    }
}

消费者1

import com.janet.util.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;

public class Recv1 {

    private static final String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws Exception {
        //1. 获取连接
        Connection connection = ConnectionUtils.getConnection();

        //2. 创建通道
        Channel channel = connection.createChannel();

        //3. 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        //4. 定义一个消费者
        Consumer consumer = new DefaultConsumer(channel) {
            // 消息到达,触发这个消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[1]: " + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[1] done");
                }
            }
        };

        //4. 监听队列
        boolean autoAck = true;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

消费者2:

import com.janet.util.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;

public class Recv2 {
    private static final String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws Exception {
        //1. 获取连接
        Connection connection = ConnectionUtils.getConnection();

        //2. 创建通道
        final Channel channel = connection.createChannel();

        //3. 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        //4. 定义一个消费者
        Consumer consumer = new DefaultConsumer(channel) {
            // 消息到达,触发这个消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[2]: " + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[2] done");
                }
            }
        };

        //4. 监听队列
        boolean autoAck = true;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

现象:
消费者1 和消费者 2 处理消息是一样快的。

消费者1:偶数

消费者2:奇数

这种方式叫做轮询分发(round-robin),结果就是不管谁忙谁清闲,都不会多给一个消息,任务消息总是你一个我一个。

2)公平分发 fair dipatch

生产者:

public class Send {
    /*
    *                  |-----C1
    * P ----Queue------|
    *                  |-----C2
    * */
    private static final String QUEUE_NAME = "test_work_queue";
    public static void main(String[] args) throws Exception {
        //1. 获取一个连接
        Connection connection = ConnectionUtils.getConnection();

        //2. 从连接中获取一个通道
        Channel channel = connection.createChannel();

        //3. 声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        //每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者,一次只能处理一个消息。
        //如果不写basicQos(1),则自动MQ会将所有请求平均发送给所有消费者
        //限制发送给同一个消费者 不得超过一条信息
        channel.basicQos(1);

        for(int i = 0; i <40; i++){
            String msg = "hello"+i;
            System.out.println("[MQ] send:"+msg);
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
            Thread.sleep(i*20);
        }

        channel.close();
        connection.close();
    }
}

消费者1:

public class Recv1 {

    private static final String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws Exception {
        //1. 获取连接
        Connection connection = ConnectionUtils.getConnection();

        //2. 创建通道
        final Channel channel = connection.createChannel();

        //3. 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        channel.basicQos(1); //保证每次只分发一个

        //4. 定义一个消费者
        Consumer consumer = new DefaultConsumer(channel) {
            // 消息到达,触发这个消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[1]: " + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[1] done");

                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };

        //4. 监听队列
        boolean autoAck = false; //自动应答 false
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

消费者2

public class Recv2 {
    private static final String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws Exception {
        //1. 获取连接
        Connection connection = ConnectionUtils.getConnection();

        //2. 创建通道
        final Channel channel = connection.createChannel();

        //3. 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        channel.basicQos(1); //保证每次只分发一个

        //4. 定义一个消费者
        Consumer consumer = new DefaultConsumer(channel) {
            // 消息到达,触发这个消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {

                String msg = new String(body, "utf-8");
                System.out.println("[2]: " + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[2] done");
                    //手动回执
                    channel.basicAck(envelope.getDeliveryTag(),false);

                }
            }
        };

        //4. 监听队列
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

生产者每次只发一个消息,消费者手动的确认消息,消费者消费完成之后,给生产者发消息,生产者再发下一个消息。

现象:消费者 2 比消费者 1 多 ,能者多劳。

3. 订阅模式 publish/subscribe

①模型

解读:

  • 一个生产者,多个消费者
  • 每一个消费者都有自己的队列
  • 生产者没有直接把消息发送到队列 而是发送到了交换机 转发器(exchange)
  • 每个队列都要绑定到交换机上,
  • 生产者发送的消息 经过交换机 达到队列 就能实现一个消息被多个消费者消费

注册 -> 邮件 -> 短信 -

②生产者

public class Send {
    private static final String EXCHANGE_NAME = "test_exchange_fanout";
    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        //声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");//分发
        //发送消息
        String msg = "hello_ps";
        channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
        System.out.println("Send: "+msg);
        channel.close();;
        connection.close();

    }
}

消息去哪了?丢失了!! 因为交换机没有存储的能力,在RabbitMQ里面只有队列有存储能力。因为这时候还没有队列绑定到这个交换机,所以数据丢失了。

③消费者1

public class Recv {

    private static final String QUEUE_NAME = "test_queue_fanout_email";
    private static final String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtils.getConnection();
        final Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        //绑定队列到交换机转发器
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");

        channel.basicQos(1); //保证每次只分发一个

        //定义一个消费者
        Consumer consumer = new DefaultConsumer(channel) {
            // 消息到达,触发这个消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[1]: " + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[1] done");

                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };

        //4. 监听队列
        boolean autoAck = false; //自动应答 false
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

④消费者2

public class Recv2 {

    private static final String QUEUE_NAME = "test_queue_fanout_sms";
    private static final String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtils.getConnection();
        final Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        //绑定队列到交换机转发器
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");

        channel.basicQos(1); //保证每次只分发一个

        //定义一个消费者
        Consumer consumer = new DefaultConsumer(channel) {
            // 消息到达,触发这个消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[2]: " + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[2] done");

                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };

        //4. 监听队列
        boolean autoAck = false; //自动应答 false
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

绑定了队列:

4. 路由模式

5. 主题模式 Topic

Topic exchange 将路由和某种模式匹配

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值