RabbitMQ相关概念及使用

相关概念解释

virtual host【虚拟主机】:简称vhost,每一个vhost可以对应一个用户,拥有独立的队列、交换机等,且可以重复命名。 

生产者【Producer】:创建消息,并发送到RabbitMQ

消费者【Consumer】:连接到RabbitMQ,并消费其中的消息

队列【Queue】:RabbitMQ的唯一存储消息的数据结构,生产者创建消息并将消息投递到队列中,消费者从队列中获取消息并消费。多个消费者若对应同一个队列,则消息会平摊给消费者进行消费,没有权重。

路由key【RoutingKey】:生产者将消息发送给交换机时会指定一个RoutingKey,告诉交换机我的消息要发送到哪个队列,当RoutingKey和BindingKey有某种关联关系时(比如direct模式的交换机,RoutingKey和BindingKey完全匹配一模一样的时候,才会将消息投递到与交换机绑定的队列),消息传递就会生效。

绑定key【BindingKey】:在队列和交换机绑定时会指定一个BindingKey,代表该队列和交换机的绑定关系。

交换机【Exchange】: 在实际情况中,生产者创建的消息不会直接投递到队列中,而是先发送到交换机,通过交换机根据某些规则将消息投递到一个或多个队列中。

常用的交换机种类有四种:headers、direct、fanout、topic

header【标题订阅模式】:性能差、已经不用。

direct【直接交换机:精确匹配】:RoutingKey和BindingKey完全匹配时,将消息投递到与交换机绑定的队列,若队列是多个,则消息会被分别投递到多个队列(多个队列都会收到同样的消息)。如下图:RountingKey=one,在该交换机模式下,消息会被分别投递至队列1和队列3。当生产者不指定RountingKey时(使用空字符串作为交换机的名称),例如:channel.basicPublish("", "test1", null, message.getBytes());,就会发送到RabbitMQ的默认交换机(default exchange),任何发往默认交换机的消息会被路由到RountingKet名称的队列上,即:第二个参数test1本来为RountingKey,但由于未指定交换机,则会被投递至名称为test1的队列上。

生产者代码示例:

1、 channel.basicPublish("", "队列1", null, message.getBytes());
2、channel.basicPublish("test.exchange", "one", null, message.getBytes());

fanout【广播订阅模式】:会把所有发送到该交换机的消息投递至所有与该交换机绑定的队列中。

生产者代码示例:channel.basicPublish("exchange", "", null, message.getBytes());

topic【主题订模式:模糊匹配】:在direct交换机严格匹配RoutingKey和BindingKey的基础上,增加了模糊匹配。BindingKey可以使用“*”匹配一个单词(注意:是一个单词,不是一个字母!),使用“#”匹配0-N个单词。同时,RountingKey和BindingKey都是用“.”分隔,例如:test.rabbitmq.topic。

如下图:

RountingKey=test.rabbitmq.topic,消息会同时投递至队列1、2、3;

RountingKey=test.mq.topic,消息会同时投递至队列2、3

RountingKey=test.mq.direct,消息只会投递至队列3

RountingKey=test.rabbitmq.direct,消息会投递至队列1、3

生产者代码示例: 同direct,但必须指定交换机。

1、安装RabbitMQ

【Mac安装请看我的另一篇文章,其他system请自行百度】https://blog.csdn.net/qq_26012495/article/details/88187945

2、打开RabbitMQ的Web管理端

URL:http://localhost:15672
用户名(默认):guest
密码(默认):guest

单击Admin页签,右侧选择Virtual hosts可以看到不同的vhost,并且可以新增,默认只有/,这里新增一个fjj的vhost。

然后点开Queues页签,在vhost下新增两个队列,分别命名为test1、test2

接着打开Exchanges页签,新建一个名为“test.exchange”的direct交换机,然后单击

单击后将队列绑定至交换机,在下面输入队列名称和RoutingKey,单击绑定按钮。将test1和test2队列通过同一个RoutingKey绑定至该交换机。

通过以上步骤,已经完成了vhost、queue、exchange、RoutingKey的创建和绑定,接下来通过代码来实现。

生产者:

public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();

        //设置RabbitMQ相关信息
        factory.setHost("127.0.0.1");
        factory.setPort(5672);// RabbitMQ服务端默认端口号
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/fjj");

        //创建一个新的连接
        Connection connection = factory.newConnection();

        //创建一个信道
        Channel channel = connection.createChannel();
        String message = "hello world";

        // 交换机为""空字符串,发送至名称为test1的队列
        //channel.basicPublish("", "test1", null, message.getBytes());

        // 发送至RoutingKey为test并且绑定至test.exhcange这个交换机的队列上
        channel.basicPublish("test.exchange", "test", null, message.getBytes());

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

由于名称为test的RoutingKey绑定了两个队列test1和test2,所以执行上述代码,会将一条消息同时发送给两个队列,看下图,两个队列确实都收到了消息。

消费者:

package myself;

import com.rabbitmq.client.*;

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

/**
 * @author create by FENGJINGJU
 * @Date 2019-07-15 20:18
 */
public class RabbitMQCustomer {
    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();

        //设置RabbitMQ相关信息
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/fjj");

//        Address[] addresses = new Address[]{
//                new Address("127.0.0.1", 5672)
//        };
        // 创建连接
        Connection connection = factory.newConnection();
        // 或者使用Address[]设置host和端口
        // Connection connection = factory.newConnection(addresses);

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

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String routingKey = envelope.getRoutingKey();
                System.out.println("路由KEY:" + routingKey);
                System.out.println("收到消息:" + new String(body, "UTF-8"));
                // 手动确认
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 参数1:队列名称;参数2:是否自动确认
        channel.basicConsume("test1", false, consumer);
        channel.close();
        connection.close();
    }
}

运行程序,可以看到,/fjj下的test1队列原先的一条消息已经被消费掉。

上述是一个简单的消费者接收代码,官方推荐Consumer的实现类使用DefaultConsumer,重写其中的handleDelivery方法比较简单方便,如果有更复杂需求可重写其他方法。

其中channel.basicConsume的autoAck参数若设置为true,不管消费者有没有真正接收到,只要投递了就从队列中将消息删除,容易在某些情况下造成消息丢失。建议设置false,那么消费者必须通过channel.basicAck方法来确认已收到消息,此时才会从队列中将该消息删除。


问题思考:

为什么要有交换机???

交换机的存在是必要的,只有通过交换机,才能实现分发到不同队列的不同场景,以及生产者和消费者的充分解耦。如果是生产者直接发动到队列中,那么生产者就必须指定队列名称,这种一对一或者一对多的关系耦合性太高,要实现不同场景的分发也十分困难。

为什么创建完Connection还要创建channel(信道)??

其实可以通过Collection直接完成操作,一个Collection就是一次TCP连接,若有很多线程使用,那就要建立多次TCP连接,消耗系统资源。采用channel方式实际上是复用TCP连接,类似NIO,减少连接次数,提高性能,便于管理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值