rabbitmq 概念

部分内容转载自:

http://blog.csdn.net/fxq8866/article/details/61629620

http://blog.csdn.net/rainday0310/article/details/22082503

AMQP

AMQP,即Advanced Message Queuing Protocol(高级消息队列协议),一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有 RabbitMQ等。

生产者

producter,消息发送者。

消费者

consumer,消息订阅者。

代理服务器

RabbitMq服务器。

信道

生产者和消费者与代理服务器之间的通信必须建立一个信道。

连接到RabbitMQ,才能消费或者发布消息,信道是建立在"真实"TCP连接内的虚拟连接。AMQP命令(发布消息,订阅消息,接收消息等)都是通过信道发送出去的。

TCP连接和信道的关系:我理解的这里的TCP连接应该是一个长连接,多个信道共享一个TCP长连接。

为什么不使用TCP连接发送AMQP命令
  1.创建和销毁TCP会话,开销大
  2.如果使用TCP,每个线程连接到RabbitMQ,都要创建连接,造成浪费和性能瓶颈
  3.使用一个TCP连接,能够满足性能,并且保证线程私密性。效果更好。

队列:

AMQP消息路由必须三部分:交换器,队列,绑定

生产者把消息发布到交换器,消息最后到达队列,被消费者接收,绑定决定了消息如何从路由器路由到特定的队列。

队列是消息通信的基础模块

  1.为消息提供了处所,消息在此等待消费

  2.对负载均衡来说,队列是绝佳方案,让RabbitMQ循环的方式均匀分配消息。

  3.队列是Rabbit中消息的最后终点(除非消息进入"黑洞")

  

上图注意几点:

  • 如果有多个消费者订阅了这个队列,那么队列将会以循环的方式发送给多个消费者。如消息1发送给了消费者1,那么消息2就会发送给消费者2.
  • 同一个队列可以重复创建,只要声明队列的参数完全匹配当前已有的队列,那么不会报错,直接返回成功。所以重要的消息,生产者和消费者都可以有创建逻辑。
  • 如果一个消费者接收了消息,但是没有确认之前就故障了,那么队列会以为消费者没有接收到消息,继续发送给下一个消费者去处理。一旦有消费者确认,队列就会删除这条消息。
  • 在收到一个消费者的消息确认操作之前,队列不会发送新的消息给这个消费者。所以如果一个消息的处理非常耗时,为了避免队列不停地发来消息,可以延迟确认操作。
  • 消费者获得消息有两种方式,一种使用consume订阅。当有新的消息的时候,会自动发送给消费者。另外一种是使用get来获取一条消息,尽量避免使用while和get来实现个性化的consume,因为get的逻辑是,订阅消息+获取消息+取消订阅。这样会大大影响系统的吞吐量。
  • 如果消息被发送到一个暂时没有消费者订阅的队列上,消息将会被暂存,等有了订阅者的时候,发送出去。(但是在我的实验里,如果先生成生产者,然后延迟生成消费者,那么生产者之前发送的消息是接受不到的。注:后来发现原因是因为只有消费者创建队列导致的消息丢失,见下文)
  • 消费者通过reject命令拒绝消息,如果命令参数设置为true,MQ会自动将消息发送给下一个消费者,如果为false,那么MQ会立刻将消息删除。这里的删除是逻辑删除,实际上是加入了一个死信队列,方便以后的排错和进一步的处理。但是如果消费者直接确认了消息又什么都不做,这条消息就真的蒸发了。
  • 消费者和生产者都可以创建队列,那么到底是消费者创建还是生产者创建呢。问题是,如果是只有消费者创建队列,那么如果在队列创建之前生产者发送了消息,但是交换器早不到相应的队列,那么这条消息就会被丢弃。如果只有生产者创建队列,那么如果消费者在还没有创建队列之前订阅一个队列,是没有意义的。所以如果是重要的消息,应该生产者和消费者都创建队列。

创建队列的参数:

exclusive:队列是私有的,只有你的应用程序才能消费队列消息。

auto-delete:当最后一个消费者取消订阅的时候,队列就会自动移除。

交换器和绑定

  消费者从队列中获取消息,但是消息是如何到达队列的呢?

  将消息投递到队列时,通过把消息发送给交换器来完成。根据确定的规则,RabbitMQ会决定消息投递到那个队列。这个规则叫路由键(routing key),队列通过路由键绑定到交换器。当把消息发送发送到代理服务器时,消息将拥有一个路由键,RabbitMQ会将其和绑定使用的路由键进行匹配。匹配成功,消息会投递到队列,不匹配则进入黑洞

  

  使用交换机和绑定完成解耦,对于发送消息给服务器的一方来说,不需要关系服务器另一端的逻辑。服务器会根据路由键将消息从交换器路由到队列。

  在AMQP中定义类4种类型的交换器:direct,fanout,topic和header。header交换器与其他3个不同,它允许匹配AMQP消息的header而不是路由键,不常用;

消息队列的使用过程大概如下:

  (1)客户端连接到消息队列服务器,打开一个channel。

  (2)客户端声明一个exchange,并设置相关属性。

  (3)客户端声明一个queue,并设置相关属性。

  (4)客户端使用routing key,在exchange和queue之间建立好绑定关系。

  (5)客户端投递消息到exchange。

  (6)exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。

上面说的有点抽象,其实我理解的,我们只会在发送消息的时候跟exchange打交道,如下是java中发送消息的一个示例:

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

第一个参数就是exchange,这里默认为空,就是使用了系统默认的名称为空字符串的direct交换器。也就是说,生产者可以在每次发送消息的时候动态的选择发送到那个交换器,不需要生产者和某个交换器绑定。

rabbitmq提供的交换器有:

Direct Exchange – 处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 "dog",则只有被标记为"dog"的消息才被转发,不会转发dog.puppy,也不会转发dog.guard,只会转发dog。 

 

 

Fanout Exchange – 不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。 

 

 

Topic Exchange – 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号"#"匹配一个或多个词,符号"*"匹配不多不少一个词。因此"audit.#"能够匹配到"audit.irs.corporate",但是"audit.*" 只会匹配到"audit.irs"。我在RedHat的朋友做了一张不错的图,来表明topic交换机是如何工作的: 

 

注:这种情况下队列会收到所有路由器中符合topic规则的消息

虚拟主机和隔离

RabbitMQ 虚拟主机,RabbitMQ 通过虚拟主机(vhost)来分发消息。vhost拥有自己独立的权限控制,不同的vhost之间是隔离的,单独的。

权限控制的基本单位:vhost。

用户只能访问与之绑定的vhost。

系统默认有一个名称为/的vhost,如果没有新增其他vhost,我们默认使用的这个。

一个简单的例子

 

 

//生产者

publicclass Producer {

    publicfinalstatic String QUEUE_NAME = "rabbitMQ.test";

 

    publicstaticvoid main(String[] args) throws IOException, TimeoutException {

        // 创建连接工厂

        ConnectionFactory factory = new ConnectionFactory();

        // 设置RabbitMQ相关信息

        factory.setHost("localhost");

        // factory.setUsername("lp");

        // factory.setPassword("");

        // factory.setPort(2088);

        // 创建一个新的连接

        Connection connection = factory.newConnection();

        // 创建一个通道

        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, true, null);

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

        String message = "Hello RabbitMQ";

        // 发送消息到队列中

        while (true) {

            channel.basicPublish("", QUEUE_NAME, null,

                    message.getBytes("UTF-8"));

            System.out.println("Producer Send +'" + message + "'");

            try {

                TimeUnit.SECONDS.sleep(1);

            } catch (InterruptedException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

        }

    }

}

 

 

 

 

 

//消费者

 

publicclass Customer {

    privatefinalstatic String QUEUE_NAME = "rabbitMQ.test";

 

    publicstaticvoid main(String[] args) throws IOException, TimeoutException {

        // 创建连接工厂

        ConnectionFactory factory = new ConnectionFactory();

        // 设置RabbitMQ地址

        factory.setHost("localhost");

        // 创建一个新的连接

        Connection connection = factory.newConnection();

        // 创建一个通道

        Channel channel = connection.createChannel();

        // 声明要关注的队列

        channel.queueDeclare(QUEUE_NAME, false, false, true, null);

        System.out.println("Customer Waiting Received messages");

        // DefaultConsumer类实现了Consumer接口,通过传入一个频道,

        // 告诉服务器我们需要那个频道的消息,如果频道中有消息,就会执行回调函数handleDelivery

        Consumer consumer = new DefaultConsumer(channel) {

            @Override

            publicvoid handleDelivery(String consumerTag, Envelope envelope,

                    AMQP.BasicProperties properties, byte[] body)

                    throws IOException {

                String message = new String(body, "UTF-8");

                System.out.println("Customer Received '" + message + "'");

            }

        };

        // 自动回复队列应答 -- RabbitMQ中的消息确认机制

        channel.basicConsume(QUEUE_NAME, true, consumer);

    }

}

posted on 2017-09-02 21:44  张小贱1987  阅读(289)  评论(0编辑  收藏  举报

导航