RabittMQ消息中间件的使用

基本概念

在这里插入图片描述

  • Broker: 接收和分发消息的应用,RabbitMQ Server就是Message Broker。
  • Virtual host: 出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念。当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以划分出多个vhost,每个用户在自己的vhost创建exchange/queue等。
  • Connection: publisher/consumer和broker之间的TCP连接。断开连接的操作只会在client端进行,Broker不会断开连接,除非出现网络故障或broker服务出现问题。
  • Channel: 如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低。Channel是在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel进行通讯,AMQP method包含了channel id帮助客户端和message broker识别channel,所以channel之间是完全隔离的。Channel作为轻量级的Connection极大减少了操作系统建立TCP connection的开销。
  • Exchange: message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到queue中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)。
  • Queue: 消息最终被送到这里等待consumer取走。一个message可以被同时拷贝到多个queue中。
  • Binding: exchange和queue之间的虚拟连接,binding中可以包含routing key。Binding信息被保存到exchange中的查询表中,用于message的分发依据。
连接工具
public class ConnectionUtils {
    // 连接工厂
    private static ConnectionFactory connectionFactory;

    static {
        connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        // 相当于数据库
        connectionFactory.setVirtualHost("/vhost_mmr");
        connectionFactory.setUsername("user_mmr");
        connectionFactory.setPassword("1234");
    }

    // 获取连接
    public static Connection getConnection() {
        try {
            return connectionFactory.newConnection();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 关闭信道、连接
    public static void closeConnectionAndChanel(Connection connection, Channel channel) {
        try {
            if (channel != null) {
                channel.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }
}
使用工具获取连接
/**
 * 获取和rabbitmq服务的一个TCP连接
 */
Connection connection = ConnectionUtils.getConnection();
获取信道
/**
 * 由于TCP连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈,
 * RabbitMQ使用信道的方式来传输数据
 * 信道是建立在真实的TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制。
 */
  Channel channel = connection.createChannel();
声明交换机
// 交换机名称
private static final String EXCHANGE_NAME ="exchange_fanout";

/**
 * 声明交换机
 * exchangeDeclare(String exchange, String type)
 * 参数1:交换机名称
 * 参数2:声明的交换机类型
 *	fanout(扇出)--- 广播模式
 *  
 */
channel.exchangeDeclare(EXCHANGE_NAME,String type);
声明消息队列
/**
 * 名称相同,其他参数不同的队列不是同一个队列,声明名称相同其他参数不同的队列会报错。
 * 同一个名字的队列只能声明一个
 * 发送消息和接受消息绑定的队列必须是严格一致的,队列的参数必须都对应。
 */
public class Receive {
    private static final String QUEUE_NAME = "simple_queue";
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionUtils.getConnection();
        // 获取信道
        Channel channel = connection.createChannel();
        /**
         * 队列声明 -- 给信道绑定队列,一个信道可绑定多个队列
         * queueDeclare(String queue,boolean durable,boolean exclusive,boolean autoDelete,Map<String, Object> arguments)
         * 参数1:队列名称,如果rabbitmq中不存在该队列会自动创建。
         * 参数2:队列是否持久化,true为持久化。持久化后重启rabbitmq服务后会恢复该队列,
         		 但队列中未消费的消息并未持久化,重启服务后还是会丢失。除非发送消息时指定持久化消息。
         * 参数3:是否独占队列,true为独占 -- 只能被当前信道和连接使用,其他连接、信道使用抛出异常
         * 参数4:是否在消息消费完且无连接占用该队列时自动删除队列,true为自动删除
                 队列中消息被消费完了但是有连接还在占用时队列不会被删除。
         * 参数5:额外附加参数
         */
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
    }
}
声明队列的可选额外参数
# 参数Map<String, Object> arguments的可选值
x-message-ttl 消息在队列中可以存活的时间超过会被丢弃(毫秒)。
x-expires 队列可以存活的时间设置为1000 队列1秒未被使用会被删除(毫秒)。
x-max-length 消息队列最大长度。
x-max-length-bytes 消息队列的容积。
x-dead-letter-exchange 死信队列如果队列中的消息过期被删除会被重新发布到交换器exchange
x-dead-letter-routing-key 死信队列的路邮键
x-max-priority 队列支持的最大优先级数;如果未设置,队列将不支持消息优先级。
x-queue-mode 把消息尽可能的都保存在磁盘上,仅在消费者请求的时候才会加载到RAM中。
x-queue-master-locator 将队列设置为主位置模式,确定在节点集群上声明时队列主机所在的规则。
绑定队列到交换机
/**
 * 绑定队列到指定交换机
 * 参数1:队列名称
 * 参数2:交换机名称
 * 参数3:路由键
 */
channel.queueBind(String queue, String exchange, String routingKey);
发送消息
/**
 * 发送消息
 * void basicPublish(String exchange, String routingKey, BasicProperties props,byte[]
 * body)
 * 参数1:信道绑定的交换机名称
 * 参数2:信道绑定的队列名称 或 路由键
 * 参数3:传递消息额外设置
 *       实参:MessageProperties.PERSISTENT_TEXT_PLAIN传入该参数会持久化该消息
 * 参数4: 消息的具体内容
 */

channel.basicPublish("",QUEUE_NAME,null,message.getBytes());

/**
 * 重载参数最全的方法
 * mandatory:交换器无法根据自身类型和路由键找到一个符合条件的队列时的处理方式
 * 			  true:RabbitMQ会调用Basic.Return命令将消息返回给生产者
 * 			  false:RabbitMQ会把消息直接丢弃
 * immediate:设置true时,如果该消息关联的队列上有消费者,则立即投递,否则这条消息不存入队列;
 *			  如果与路由键匹配的所有队列都没有消费者时,该消息会通过Basic.Return返回至生产者
 */

void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
接受消息
 // 定义消费者
Consumer consumer = new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        String msg = new String(body,"utf-8");
        System.out.println("---new API [receive] msg:" + msg);
    }
};

/**
 * 接受消息 -- 消费者会一直监听队列,队列中有消息就会被消费
 * String basicConsume(String queue, boolean autoAck, Consumer callback)
 * 参数1:监听的队列名称
 * 参数2:消息应答机制 
 		 ture为自动确认模式,一旦rabbitmq将消费分发给消费者,就会从内存中删除该消息。
         	如果正在执行的消费者突然挂掉,就会丢失正在处理的消息。
         false为非自动默认,如果正在消费的消费者挂掉,就会将该消息交付给其他消费者。
		 	消费者处理完消息会发送一个消息应答告诉rabbitmq消息已经处理完成,然后rabbitmq才从内存中				删除该消息
 */
channel.basicConsume(QUEUE_NAME,true,consumer);
消息发送确认机制(重点)
# 生产者将消息发送出去后,生产者是不知道消息有没有正确到达broker的。如果在消息到达broker之前已经丢失的话,该怎么解决呢?
RabbitMQ为我们提供了两种解决方式:
1. 通过AMQP事务机制实现,这也是AMQP协议层面提供的解决方案;
2. 通过将channel设置成confirm模式来实现。

注意:以上两种方式,只能任选一种。不能同时使用
事务机制
1. 代码实现
    try {
        // 开启事务
        channel.txSelect();
        // 发送并持久化消息
        channel.basicPublish(exchange, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
        // 模拟异常
        int result = 1 / 0;
        // 提交事务
        channel.txCommit();
    } catch (Exception e) {
        e.printStackTrace();
        // 事务回滚
        channel.txRollback();
    }
2. 事务步骤、性能分析
	带事务后发送消息会多四个步骤:
		client(发送者)发送Tx.Select
		broker(rabbitmq)发送Tx.Select-Ok(之后再publish)
        client发送Tx.Commit
        broker发送Tx.Commit-Ok
	以上过程是一个同步过程(必须等待所有步骤执行完毕),注意这里的Tx.Commit与Tx.Commit-Ok之间的时间间隔294ms,由此可见事务还是很耗时的。
3. 异常后消息重发
		事务确实能够解决producer与broker之间消息确认的问题,只有消息成功被broker接受,事务提交才能成功,否则我们便可以在捕获异常进行事务回滚操作同时进行消息重发,但是使用事务机制的话会降低RabbitMQ的性能
/**
 * AMQP协议事务机制 -- 发送者
 * Topic模式
 * 发送者
 * 
 */
public class Send {
    // 交换机名称
    private static final String EXCHANGE_NAME = "exchange_transaction";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 声明交换机 --- topic
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        String msg = "hello transaction";
        try {
            // 开启事务,将channel设置为transaction模式
            channel.txSelect();
            // 路由key值
            String routingKey = "goods.add.e";
            // 发布消息到交换机
            channel.basicPublish(EXCHANGE_NAME, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
            // 模拟异常
            //int i = 1 / 0;
            // 提交事务
            channel.txCommit();
            System.out.println("发布消息 :" + msg);
        } catch (Exception e) {
            channel.txRollback();
            System.out.println("send msg Rollback");
        } finally {
            channel.close();
            connection.close();
        }
    }
}

/**
 * 接受者
 * Created by Administrator on 2019/12/9.
 */
public class Receive1 {
    // 交换机名称
    private static final String EXCHANGE_NAME = "exchange_transaction";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        // 获取临时队列
        String queueName = channel.queueDeclare().getQueue();
        // 绑定队列到交换机转发器
        channel.queueBind(queueName,EXCHANGE_NAME,"goods.#");
        // 定义消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            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]  receive msg:" + msg);
                try {
                    Thread.sleep(2000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                }
            }
        };
        boolean autoAck = true;
        // 监听队列
        channel.basicConsume(queueName,autoAck,consumer);
    }
}
confirm模式
  • 每次确认一条发送的信息(串行)

    /**
     * 发送一条消息,串行
     * Created by Administrator on 2019/12/10.
     */
    public class Send {
        // 交换机名称
        private static final String EXCHANGE_NAME ="exchange_common_confirm";
        // 队列名称
        private static final String QUEUE_NAME = "common_confirm_queue";
    
        public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
            // 获取一个连接
            Connection connection = ConnectionUtils.getConnection();
            // 从连接中获取一个通道
            Channel channel = connection.createChannel();
            // 声明交换机 --- topic
            channel.exchangeDeclare(EXCHANGE_NAME,"topic");
            // 将channel设置为confirm模式
            channel.confirmSelect();
            String msg = "hello common confirm";
            // 路由key值
            String routingKey = "goods.add.e";
            // 发布消息到交换机
            channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes());
            System.out.println("发布消息 :" + msg);
            // true则发送成功
            if (!channel.waitForConfirms()) {
                System.out.println("msg send failed");
            } else {
                System.out.println("msg send success");
            }
            channel.close();
            connection.close();
    
        }
    }
    
    /**
     * 接受者
     * Created by Administrator on 2019/12/9.
     */
    public class Receive {
        // 交换机名称
        private static final String EXCHANGE_NAME ="exchange_common_confirm";
        // 队列名称
        private static final String QUEUE_NAME = "common_confirm_queue";
        public static void main(String[] args) throws IOException, TimeoutException {
            Connection connection = ConnectionUtils.getConnection();
            Channel channel = connection.createChannel();
            // 队列声明
            channel.queueDeclare(QUEUE_NAME,false,false,false,null);
            // 绑定队列到交换机转发器
            channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");
            // 定义消费者
            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body,"utf-8");
                    System.out.println("msg:" + msg);
                }
            };
            // 自动应答
            boolean autoAck = true;
            // 监听队列
            channel.basicConsume(QUEUE_NAME,autoAck,consumer);
        }
    }
    
  • 一次确认多条发送的信息,即批量确认

channel.waitForConfirms()也可一次确认多条消息,循环发送消息完毕后再一次性确认,即批量确认。
  • 异步
持续更新
消息接受确认机制(重点)
1. rabbitmq服务是默认一次性的分发信道容量大小的消息给消费者的(如果有那么多消息),消费者则是一个一个的执行。
2. 消费者如果自动确认消息,即rabbitmq将消息传递给消费者后,不用等消费者回复就会将已传递的消息删除。如果一批消息传递后消费者宕机了,此时就会产生消息丢失问题。
解决办法 -- 服务传递一个消息给消费者,必须等确认回复后才给传递下一个。且一次只传递一个消息
1. 关闭自动确认(消费者),需要给rabbitmq服务回复信息确认标志
	boolean autoAck = false
2. 回复信息确认标志(消费者)
	channel.basicAck(envelope.getDeliveryTag(),false)
	参数1:消息标志 -- 具体是确认的哪一个消息
	参数2:是否多个消息同时确认 -- false关闭,一个一个确认
3. 传递消息时不能一次性的将多个消息传递到信道中(消费者)
	channel.basicQos(1) ,1代表信道一次只接受一个未确认的消息
模式
1. 简单模式
  • 一个队列只对应一个消费者
  • 使用rabbitmq提供的默认交换机
简单队列
消息接收者
消息队列
消息发送者
缺点:只有一个消费者,消费消息过慢,会造成消息在队列中堆积。

/**
 * 发送者
 */
public class Send {
    // 队列名称
    private static final String QUEUE_NAME = "simple_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 创建  队列声明----使用哪个队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        String message = "hello simple";
        channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        System.out.println("--- send msg:" + message);
        channel.close();
        connection.close();

    }
}

/**
 * 接受者
 */
public class Receive {
    private static final String QUEUE_NAME = "simple_queue";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.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 msg = new String(body,"utf-8");
                System.out.println("---new API [receive] msg:" + msg);
            }
        };
        // 监听队列
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}
2. 工作模式
  • 一个队列可对应多个消费者
  • 使用rabbitmq提供的默认交换机
  • 分发消息可分为公平模式和轮询模式
工作模式
消息接收者1
消息队列
消息发送者
消息接收者2
消息接收者3
2.1 轮询模式
  • 不管消费者的处理能力如何,总是一人一个的分发
/**
 * 工作队列---轮询分发
 *
 * 发送者
 */
public class Send {
    // 队列名称
    private static final String QUEUE_NAME = "work_polling_queue";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 创建  队列声明----使用哪个队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        for (int i = 0;i < 50;i++) {
            String message = "hello simple [" + i +"]";
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
            System.out.println("--- send msg:" + message);
            Thread.sleep(i * 20);
        }
        channel.close();
        connection.close();

    }
}

/**
 * 接受者
 */
public class Receive1 {
    private static final String QUEUE_NAME = "work_polling_queue";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.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 msg = new String(body,"utf-8");
                System.out.println("[1]--receive msg:" + msg);

                try {
                    Thread.sleep(2000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                }
            }
        };
        // 监听队列
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}
2.2 公平模式
  • 接收消息多少跟消费者的处理能力有关
  • 每个消费者发送确认消息回执之前,消息队列不发送下一个消息到消费者,一次只发一个消息
/**
 * 工作模式---公平分发
 * 发送者
 */
public class Send {
    // 队列名称
    private static final String QUEUE_NAME = "work_fair_queue";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 创建  队列声明----使用哪个队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        /**
         *  限制发送给同一个消费者,不得超过一条消息
         */
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        for (int i = 0; i < 50; i++) {
            String message = "hello simple {" + i + "}";
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println("--- send msg:" + message);
            Thread.sleep(i * 5);
        }
        channel.close();
        connection.close();

    }
}

/**
 * 接受者
 * 代码可复制多份,代表不同消费者 
 */
public class Receive1 {
    private static final String QUEUE_NAME = "work_fair_queue";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        // 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //TODO 保证每次只分发一个
        channel.basicQos(1);
        // 定义消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            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]--receive msg:" + msg);

                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // TODO 手动回执
                    channel.basicAck(envelope.getDeliveryTag(),false);
                    System.out.println("[2] done");
                }
            }
        };
        //TODO 自动应答改为手动应答
        boolean autoAck = false;
        // 监听队列
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}
3. 广播模式
  • 交换机类型:fanout,即使指定routingKey也不会起作用,所有消费者都还会接受到消息

  • 一条消息被多个消费者同时消费

  • 有多个消费者,每个消费者都有自己的队列

  • 队列都需要绑定到指定的交换机

  • 生产者发送消息只能发送到交换机,由交换机决定发送给哪些队列,生产者无法决定

  • 使用场景:注册时发送手机验证码、邮件等

广播模式 -- 交换机类型Fanout
消息接收者1
消息队列1
交换机
消息发送者
消息接收者2
消息队列2
消息接收者3
消息队列3
/**
 * 发布订阅。一个生产者、一个交换机、多个队列、多个消费者,一个队列对应一个消费者。
 * 发送者
 */
public class Send {
    // 交换机名称
    private static final String EXCHANGE_NAME ="exchange_fanout";
    
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 声明交换机 --- fanout为分发类型(不处理路由键,只要和交换机绑定的所有队列都能收到消息)
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
        String msg = "hello publish/subscribe";
        // 发布消息到交换机
        channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
        System.out.println("发布消息 :" + msg);
        channel.close();
        connection.close();

    }
}

/**
 * 接受者
 * Created by Administrator on 2019/12/9.
 */
public class Receive1 {
    // 交换机名称
    private static final String EXCHANGE_NAME ="exchange_fanout";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();

        // 声明交换机 -- 跟发送者的交换机要一致
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");

        // 获取一个临时队列,一个消费者对应一个队列,消费完消息临时队列就会销毁,有助于资源利用
        String queueName = channel.queueDeclare().getQueue();

        // 绑定临时队列到交换机
        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 {
                String msg = new String(body,"utf-8");
                System.out.println("[1]-- receive msg:" + msg);
                try {
                    Thread.sleep(2000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                }
            }
        };
        boolean autoAck = true;
        // 监听队列
        channel.basicConsume(queueName,autoAck,consumer);
    }
}
4.路由模式(订阅)
  • 交换机类型:direct

  • 队列和交换机不再是任意绑定,需要指定一个routingKey(路由key)

  • 发送者向交换机发送消息时,也需要指定相同的routingKey

  • 交换机不再是把消息发送给每一个绑定的队列,而是根据routingKey判断,只有队列的routingKey与消息的routingKey完全一致,才能接受到消息

  • 使用场景:日志系统 – 所有日志在控制台打印,只有错误信息输出到磁盘

路由模式 -- 交换机类型Direct
error
info
warning
消息接收者1
消息队列1
交换机
消息发送者
消息接收者2
消息队列3
/**
 * 路由模式
 * <p>
 * 发送者
 */
public class Send {
    // 交换机名称
    private static final String EXCHANGE_NAME ="exchange_direct";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 声明交换机 --- direct为路由类型(处理路由键,key值匹配才能收到消息)
        channel.exchangeDeclare(EXCHANGE_NAME,"direct");
        String msg = "hello direct";
        // 路由key值
        String routingKey = "warning";
        // 发布消息到交换机
        channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes());
        System.out.println("发布消息 :" + msg);
        channel.close();
        connection.close();

    }
}

/**
 * 接受者
 * Created by Administrator on 2019/12/9.
 */
public class Receive1 {
    // 交换机名称
    private static final String EXCHANGE_NAME ="exchange_direct";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        // 获取一个临时队列,一个消费者对应一个队列,消费完消息临时队列就会销毁,有助于资源利用
        String queueName = channel.queueDeclare().getQueue();
        // 基于routingKey绑定队列到交换机转发器,4种类型的routingKey都会接受
        // 此方式太冗余,不灵活
        channel.queueBind(queueName,EXCHANGE_NAME,"debug");
        channel.queueBind(queueName,EXCHANGE_NAME,"info");
        channel.queueBind(queueName,EXCHANGE_NAME,"error");
        channel.queueBind(queueName,EXCHANGE_NAME,"warning");
        // 定义消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            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] -- receive msg:" + msg);
                try {
                    Thread.sleep(2000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                }
            }
        };
        boolean autoAck = true;
        // 监听队列
        channel.basicConsume(queueName,autoAck,consumer);
    }
}
5. Topic话题模式
  • 交换机类型:topic

  • Topic和路由模式都是根据RoutingKey把消息路由到不同的队列。

  • 区别在于路由的routingKey是固定的字符串,而topic的routingKey是使用通配符,动态路由

  • 一般由一个或多个单词组成routingKey,多个单词以"."分割,例如:item.insert

# 通配符
	* 只匹配一个单词
	# 可匹配一个或多个单词
# 示例
	item.#	可匹配以item开头的所有值,如:item.insert或item.irs.corporate
	#.item  可匹配以item结尾的所有值
	item.*  只能匹配以item开头再加一个任意单词的值。如:item.insert
	*.item  只能匹配一个任意词 + item结尾的值
	*.*.item 只能匹配一个任意单词 + 一个任意单词 + item结尾的值
/**
 * topic模式。一个生产者、一个交换机、多个队列、多个消费者,一个队列对应一个消费者。
 * <p>
 * 发送者
 * Created by Administrator on 2019/12/9.
 */
public class Send {
    // 交换机名称
    private static final String EXCHANGE_NAME ="exchange_topic";
    // 队列名称
    private static final String QUEUE_NAME = "topic_queue";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 声明交换机 --- topic
        channel.exchangeDeclare(EXCHANGE_NAME,"topic");
        String msg = "hello topic";
        // 路由key值
        String routingKey = "goods.add.e";
        // 发布消息到交换机
        channel.basicPublish(EXCHANGE_NAME,routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes());
        System.out.println("发布消息 :" + msg);
        channel.close();
        connection.close();

    }
}

/**
 * 接受者
 * Created by Administrator on 2019/12/9.
 */
public class Receive1 {
    // 交换机名称
    private static final String EXCHANGE_NAME ="exchange_topic";
    // 队列名称
    private static final String QUEUE_NAME = "topic_queue_one";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        // 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        // 绑定队列到交换机转发器
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.*");
        // 定义消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            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] --- receive msg:" + msg);
                try {
                    Thread.sleep(2000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                }
            }
        };
        boolean autoAck = true;
        // 监听队列
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

ueDeclare(QUEUE_NAME,false,false,false,null);
// 绑定队列到交换机转发器
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,“goods.*”);
// 定义消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
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] — receive msg:" + msg);
try {
Thread.sleep(2000);
}catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[1] done");
}
}
};
boolean autoAck = true;
// 监听队列
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值