RabbitMQ------四种模式

一. 工作队列模式

1. 消息生产能力大于消费能力,增加多几个消费节点

2. 默认策略:轮训(round robin)

缺点:存在部分节点消费过快,部分节点消费过慢,导致不能合理处理消息

例子:生产者发送了10条消息,有2个消费者,则每个消费者都处理5条消息

3. 公平策略

优点:解决消费者消费能力不足问题,降低消费时间

例子:能者多劳,消费者处理完当前的1条消息后,再处理下1条。生产者发送了6(a~f)条消息

时间/s             1           2           3           4           5

消费者1            a           c           d                        f

消费者2            b                                     e

如图:

p:生产者      红色:队列      c:消费者

代码:

1.添加pom.xml依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
View Code

 

2.添加消息生产者代码

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

class sender{
    private static final String QUEUE_Name = "hello";

    public static void main(String[] args){
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("guest");
        factory.setPassword("123456");
        factory.setVirtualHost("/dev");

        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()){
            channel.queueDeclare(QUEUE_Name, false, false, false, null);
            //一次发送10条消息
            for(Integer i = 0; i<10; i++){
                String msg = "hi hutao " + i;
                System.out.println(msg);
                channel.basicPublish("", QUEUE_Name, null, msg.getBytes("utf-8"));
            }
        }
        catch (Exception e){

        }
    }
}
View Code

 

3.添加消息消费者代码

公平策略代码:1表示消费者每次处理完1条消息再从生产者处获取消息

channel.basicQos(1);

import com.rabbitmq.client.*;

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

/**
 * 可以拷贝整个class代码模拟多个消费者接收消息
 * 再通过修改TimeUnit.SECONDS.sleep模拟消费者处理消息速度
 *
 * */
class receiver {
    private static final String QUEUE_Name = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("guest");
        factory.setPassword("123456");
        factory.setVirtualHost("/dev");

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_Name, false, false, false, null);

        //公平策略,能让处理消息快的消费者处理更多消息
        //限制消费者每次消费1个,处理完再消费下一个
        channel.basicQos(1);

//                官网的测试方法
//                DeliverCallback deliverCallback = (consumerTag, delivery) -> {
//                String message = new String(delivery.getBody(), "UTF-8");
//                System.out.println(" [x] Received '" + message + "'");
//            };
        //自定义的测试方法
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                //模拟消费者处理速度
                try {
                    TimeUnit.SECONDS.sleep(2);
                }
                catch (Exception e){}

                String msg = new String(body, "utf-8");
                System.out.println(msg);
                //手工确认消息 不是多条确认
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        //消费 多个消费者时要关闭消息自动确认
        channel.basicConsume(QUEUE_Name, false, consumer);
    }
}
View Code

 

二. 发布/订阅模式

1. 生产者将消息发送到交换机(Exchange),再由交换机转发到队列

2. 交换机只负责转发消息,不具备存储消息能力,如果没有队列和交换机绑定,或者没有符合的路由规则,则消息会被丢失

3. 交换机常见四种类型:

备注:路由键(RoutingKey)

Direct Exchange(定向)

a. 将一个队列绑定交换机,要求该消息与一个特定的路由键完全匹配

b. 处理路由键

例子:

一个队列绑定交换机的路由键是aaa.bb,则只有被标记为aaa.bb的消息才会被转发,标记为a.bb,aaa.b的都不会被转发

Fanout Exchange(广播)

a. 只需要将队列绑定交换机,则发送到交换机上的消息都会被转发到与之绑定的队列中

b. 速度是最快的

c. 不处理路由键

Topic Exchange(通配符)

a. topic交换机是一种发布/订阅模式,结合了direct交换机和fanout交换机的特点

b. 将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上

c. 符号“#”匹配一个或者多个词,“*”只匹配一个词

例子:

abc.#可以匹配到abc.aa,abc.ef,abc.ccc.dd

abc.*可以匹配到abc.aa,abc.ef

Headers Exchange(基本不用)

如图:

4.

a. 发布/订阅模式中消息生产者不再直接面对队列(queque),而是面对交换机(exchange),消息都需要经过交换机进行发送,

b. 所有发往同一个fanout交换机的消息都会被所有监听这个交换机的消费者接收到

c. 应用场景

微信公众号,新浪微博关注等等

如图:

 

代码:

1.添加生产者代码

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

import java.nio.charset.StandardCharsets;

public class sender {
    private static final String EXCHANGE_NAME = "exchange_fanout";

    public static void main(String[] args){
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("guest");
        factory.setPassword("123456");
        factory.setVirtualHost("/dev");

        //创建连接
        //jdk7之后版本会自动关闭
        try (Connection connection = factory.newConnection();
             //创建信道
             Channel channel = connection.createChannel()){
            //绑定交换机,fanout扇形,即广播模式
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
            String msg = "测试广播模式";
            channel. basicPublish(EXCHANGE_NAME, "", null, msg.getBytes(StandardCharsets.UTF_8));
        }
        catch (Exception e){

        }
    }
}
View Code

2.添加消费者代码

import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

/**
 * 可以拷贝多个此类来模拟多个消费者
 *
 * */
public class recvier {
    //交换机名称
    private static final String EXCHANGE_NAME = "exchange_fanout";

    public static void main(String[] args) throws IOException, TimeoutException {
        //配置连接rabbitmq
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("guest");
        factory.setPassword("12345");
        factory.setVirtualHost("/dev");

        //创建连接和信道
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        //绑定交换机,fanout扇形,即广播模式
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
        //获取队列,会自动分配队列
        //可以在rabbitmq管理界面 http://localhost:15672/
        // Queues -> Overview列表 -> Name中查看
        String queueName = channel.queueDeclare().getQueue();
        //绑定交换机和队列 fanout模式不需要routingKey
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body, StandardCharsets.UTF_8);
                //控制台打印发布者传过来的消息
                System.out.println(msg);
                //手工确认消息 不是多条确认
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        //消费 多个消费者时要关闭消息自动确认
        channel.basicConsume(queueName, false, consumer);
    }
}
View Code

 

三. 路由模式

1. 交换机类型是Direct

2. 队列和交换机绑定,需要指定路由键(RoutingKey)

3. 生产者发送消息给交换机,需要指定路由键

4. 交换机根据路由键,将消息发送给指定的队列

如图:

 

 代码:

1.添加生产者代码

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

import java.nio.charset.StandardCharsets;

public class sender {
    private static final String EXCHANGE_NAME = "exchange_direct";

    public static void main(String[] args){
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("guest");
        factory.setPassword("123456");
        factory.setVirtualHost("/dev");

        //创建连接
        //jdk7之后版本会自动关闭
        try (Connection connection = factory.newConnection();
             //创建信道
             Channel channel = connection.createChannel()){
            //绑定交换机,直连交换机 direct
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            String msgError = "错误信息";
            String msgInfo = "提示信息";
            String msgDebug = "调试信息";
            //路由模式需要指定routingKey
            channel. basicPublish(EXCHANGE_NAME, "errorRoutingKey", null, msgError.getBytes(StandardCharsets.UTF_8));
            channel. basicPublish(EXCHANGE_NAME, "infoRoutingKey", null, msgInfo.getBytes(StandardCharsets.UTF_8));
            channel. basicPublish(EXCHANGE_NAME, "debugRoutingKey", null, msgDebug.getBytes(StandardCharsets.UTF_8));
        }
        catch (Exception e){

        }
    }
}
View Code

2.添加消费者代码

import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

/**
 * 可以拷贝多个此类来模拟只接收info消息的消费者
 * 绑定交换机和队列时,不绑定errorRoutingKey和debugRoutingKey即可
 *
 * */
public class recvier {
    //交换机名称
    private static final String EXCHANGE_NAME = "exchange_direct";

    public static void main(String[] args) throws IOException, TimeoutException {
        //配置连接rabbitmq
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("guest");
        factory.setPassword("123456");
        factory.setVirtualHost("/dev");

        //创建连接和信道
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        //绑定交换机,直连交换机(direct)
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        //获取队列,会自动分配队列
        //可以在rabbitmq管理界面 http://localhost:15672/
        // Queues -> Overview列表 -> Name中查看
        String queueName = channel.queueDeclare().getQueue();
        //绑定交换机和队列 直连模式direct需要指定routingKey
        channel.queueBind(queueName, EXCHANGE_NAME, "errorRoutingKey");
        channel.queueBind(queueName, EXCHANGE_NAME, "infoRoutingKey");
        channel.queueBind(queueName, EXCHANGE_NAME, "debugRoutingKey");

        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body, StandardCharsets.UTF_8);
                //控制台打印发布者传过来的消息
                System.out.println(msg);
                //手工确认消息 不是多条确认
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        //消费 多个消费者时要关闭消息自动确认
        channel.basicConsume(queueName, false, consumer);
    }
}
View Code

 

四. 主题模式

1. 交换机是topic

2. 可以实现发布订阅模式(fanout)和路由模式(direct)功能,更加灵活,支持模式匹配,通配符等

3. 交换机通过通配符转发对对应队列

#代表一个或者多个词

*代表一个词

例子:

order.#会匹配order.log,order.log.info等等

order.*会匹配order.log,order.new等等

4.

生产者发送消息需要指定具体路由键

交换机和队列绑定使用通配符路由键

如图:

 

 代码:

1.添加生产者代码

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

import java.nio.charset.StandardCharsets;

public class sender {
    private static final String EXCHANGE_NAME = "exchange_topic";

    public static void main(String[] args){
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("guest");
        factory.setPassword("123456");
        factory.setVirtualHost("/dev");

        //创建连接
        //jdk7之后版本会自动关闭
        try (Connection connection = factory.newConnection();
             //创建信道
             Channel channel = connection.createChannel()){
            //绑定交换机,topic交换机
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
            String msgError = "订单错误信息";
            String msgInfo = "订单提示信息";
            String msgDebug = "商品调试信息";
            //topic模式需要指定routingKey
            channel. basicPublish(EXCHANGE_NAME, "order.log.error", null, msgError.getBytes(StandardCharsets.UTF_8));
            channel. basicPublish(EXCHANGE_NAME, "order.log.info", null, msgInfo.getBytes(StandardCharsets.UTF_8));
            channel. basicPublish(EXCHANGE_NAME, "product.log.debug", null, msgDebug.getBytes(StandardCharsets.UTF_8));
        }
        catch (Exception e){

        }
    }
}
View Code

2.添加消费者代码

import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

/**
 * 可以拷贝多个此类来模拟只接收订单日志错误信息的消费者
 * 绑定交换机和队列时,只要将routingKey改为order.log.error即可
 *
 * */
public class recvier {
    //交换机名称
    private static final String EXCHANGE_NAME = "exchange_topic";

    public static void main(String[] args) throws IOException, TimeoutException {
        //配置连接rabbitmq
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("guest");
        factory.setPassword("123456");
        factory.setVirtualHost("/dev");

        //创建连接和信道
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        //绑定交换机,topic交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        //获取队列,会自动分配队列
        //可以在rabbitmq管理界面 http://localhost:15672/
        // Queues -> Overview列表 -> Name中查看
        String queueName = channel.queueDeclare().getQueue();
        //绑定交换机和队列 topic模式需要指定routingKey
        //这里获取所有匹配*log.*的日志信息
        channel.queueBind(queueName, EXCHANGE_NAME, "*.log.*");

        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body, StandardCharsets.UTF_8);
                //控制台打印发布者传过来的消息
                System.out.println(msg);
                //手工确认消息 不是多条确认
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        //消费 多个消费者时要关闭消息自动确认
        channel.basicConsume(queueName, false, consumer);
    }
}
View Code

 

posted @ 2021-09-26 11:56  玉天恒  阅读(688)  评论(0编辑  收藏  举报