C# rabbitmq 安装步骤以及使用方法

Posted on 2021-01-13 15:03  熊先生丶  阅读(973)  评论(1编辑  收藏  举报

1. 因为 rabbit 是用erlang 语言写的 需要分别下载 erlang的运行库和rabbitmq 服务器
       a.erlang的运行库: https://www.erlang.org/downloads

              

 

 

        b.rabbitmq 服务器: https://www.rabbitmq.com/

           第一步。 点击get  started 

                                                    

          第二步 点击下载并且选择环境 

                                                 

 

 

 

 

                                                

 

                                                           

 

 

       注意需要 rabbitmq 的版本与erlang的版本要对应
2. 安装并配置环境
       a.安装erlang OTP 环境
       b.配置环境变量 Path 添加第一步安装的文件位置

              

 

 


       c.打开cmd 输入erl 看是否配置成功

                         

 

 


      d.安装rabbitmq server


      e.在安装目录下 运行cmd 输入 rabbitmq-plugins enable rabbitmq_management 启动界面管理服务

           

 

 


      f.启动以后 在浏览器中输入 http://127.0.0.1:15672/,进入管理页面,账户密码都是guest。

                            

 

 


      g.rabbitmq server 默认端口是 udp的5672端口 如果是远程连接需要开启防火墙
      h.guest 用户是本地账号如果 不在同一台服务器上无法连接 需要新建一个账号并且给这个账号对应的权限

        

 

 

 

   直接点击设置默认权限

 

 

       现在权限就有了

 

 


3. 在vs中引用客户端 RabbitMQ.Client 可以在官网下载 也可以在 vs的nuget中下载

 

 

 ConnectionFactory Factory1 = new ConnectionFactory();
            Factory1.HostName = "127.0.0.1";
            Factory1.UserName = "xy";
            Factory1.Password = "xy";
            Factory1.Port = 5672;
            var conn1 = Factory1.CreateConnection();
            //创建一个channel通道
            var channel1 = conn1.CreateModel();
            channel1.QueueDeclare("csdata", false, false, false, null);//创建一个 csdata的队列
            var pro = channel1.CreateBasicProperties();
            pro.DeliveryMode = 1; //设置消息不持久保存
            Task.Run(() =>
            {
                while (true)
                {
                    channel1.BasicPublish("", "csdata", pro, Encoding.ASCII.GetBytes(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffffff"))); //生产消息
                    Thread.Sleep(100);
                }
            });

            ConnectionFactory Factory = new ConnectionFactory();
            Factory.HostName = "127.0.0.1";
            Factory.UserName = "xy";
            Factory.Password = "xy";
            Factory.Port = 5672;
            var conn = Factory.CreateConnection();

            //创建一个channel通道
            var channel = conn.CreateModel();
            var consumer = new EventingBasicConsumer(channel);//消费者
            channel.BasicConsume("csdata", true, consumer);//消费消息
            consumer.Received += (model, ea) =>
            {
                var body = ea.Body;
                var message = Encoding.UTF8.GetString(body.ToArray());
                Console.WriteLine($"send {message} rcv {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffffff")}");
            };


            Console.ReadLine();

注意如果需要消息持久化   需要 吧队列设置为持久化 并且每次发送消息都需要设置为持久化,重启以后会自动去加载队列以及队列的消息

 

一 不用交换机的队列

这里写图片描述

生产者示例:

        //声明队列  
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //发送10条消息,依次在消息后面附加1-10个点  
        for (int i = 6; i > 0; i--)  
        {  
            String message = "helloworld";
            channel.basicPublish("", QUEUE_NAME,null, message.getBytes());  
        }   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

消费者示例:

        //声明队列  
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);  
        QueueingConsumer consumer = new QueueingConsumer(channel);  
        // 指定消费队列  
        channel.basicConsume(QUEUE_NAME, true, consumer);  
        while (true)
        {  
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            doWork(message);
        }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

API介绍
(1) channel.queueDeclare(queue, durable, exclusive, autoDelete, arguments) ;
可以看到生产者和消费者用同样的参数声明了队列,官方推荐该做法,事实上对于一个已经存在的队列即使该方法试图用不同的参数去创建队列也不会有任何效果,这意味着不会改变队列更不会影响队列现在的工作。
queue:队列名字
durable:队列持久化标志,true为持久化队列
exclusive:exclusive:排他队列,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。这里需要注意三点:其一,排他队列是基于连接可见的,同一连接的不同信道是可以同时访问同一个连接创建的排他队列的。其二,“首次”,如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。其三,即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。这种队列适用于只限于一个客户端发送读取消息的应用场景。
autoDelete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于临时队列。
arguments:Map类型,关于队列及队列中消息的详细设置

arguments键意义
x-message-ttl 数字类型,标志时间,以豪秒为单位 标志队列中的消息存活时间,也就是说队列中的消息超过了制定时间会被删除
x-expires 数字类型,标志时间,以豪秒为单位 队列自身的空闲存活时间,当前的queue在指定的时间内,没有consumer、basic.get也就是未被访问,就会被删除。
x-max-length和x-max-length-bytes 数字 最大长度和最大占用空间,设置了最大长度的队列,在超过了最大长度后进行插入会删除之前插入的消息为本次的留出空间,相应的最大占用大小也是这个道理,当超过了这个大小的时候,会删除之前插入的消息为本次的留出空间。
x-dead-letter-exchange和x-dead-letter-routing-key 字符串 消息因为超时或超过限制在队列里消失,这样我们就丢失了一些消息,也许里面就有一些是我们做需要获知的。而rabbitmq的死信功能则为我们带来了解决方案。设置了dead letter exchange与dead letter routingkey(要么都设定,要么都不设定)那些因为超时或超出限制而被删除的消息会被推动到我们设置的exchange中,再根据routingkey推到queue中
x-max-priority 数字 队列所支持的优先级别,列如设置为5,表示队列支持0到5六个优先级别,5最高,0最低,当然这需要生产者在发送消息时指定消息的优先级别,消息按照优先级别从高到低的顺序分发给消费者

(2)channel.basicPublish(exchange, routingKey, mandatory, immediate, basicProperties, body);
exchange: 交换机名
routingKey: 路由键
mandatory:当mandatory标志位设置为true时,如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,那么会调用basic.return方法将消息返还给生产者, channel.addReturnListener添加一个监听器,当broker执行basic.return方法时,会回调handleReturn方法,这样我们就可以处理变为死信的消息了;当mandatory设为false时,出现上述情形broker会直接将消息扔掉;通俗的讲,mandatory标志告诉broker代理服务器至少将消息route到一个队列中,否则就将消息return给发送者。
immediate: rabbitMq3.0已经不在支持了,3.0以前这个标志告诉服务器如果该消息关联的queue上有消费者,则马上将消息投递给它,如果所有queue都没有消费者,直接把消息返还给生产者,不用将消息入队列等待消费者了。
basicProperties:消息的详细属性,例如优先级别、持久化、到期时间,headers类型的exchange要用到的是其中的headers字段。

            public BasicProperties(
            String contentType,//消息类型如:text/plain
            String contentEncoding,//编码
            Map<String,Object> headers,
            Integer deliveryMode,//1:nonpersistent 2:persistent
            Integer priority,//优先级
            String correlationId,
            String replyTo,//反馈队列
            String expiration,//expiration到期时间
            String messageId,
            Date timestamp,
            String type,
            String userId,
            String appId,
            String clusterId)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

body:消息实体,字节数组。
(3)QueueingConsumer:这是一个已经实现好了的Consumer,相比于自己实现Consumer接口,这是个比较安全快捷的方式。该类基于jdk的BlockingQueue实现,handleDelivery方法中将收到的消息封装成Delivery对象,并存放到BlockingQueue中,这相当于消费者本地存放了一个消息缓存队列。nextDelivery()方法底层调用的BlockingQueue的阻塞方法take()。
(4)channel.basicConsume(queue, autoAck, consumer);
queue:队列名。
autoAck:自动应答标志,true为自动应答。
consumer:消费者对象,可以自己实现Consumer接口,建议使用QueueingConsumer。

二 fanout类型的交换机

这里写图片描述
1、消息与队列匹配规则:fanout类型交换机会将接收到的消息广播给所有与之绑定的队列。
2、现在我们来演示一下如图所示的消息广播机制,不难注意到这种情况生产者P只关心消息发送给哪个交换机,由交换机X决定消息发送到哪些队列,,而消费者C只关注订阅哪个队列。

生产者示例:

        // 声明转发器和类型  
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout" );     
        String message = "消息1";  
        // 往转发器上发送消息  
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes()); 
  • 1
  • 2
  • 3
  • 4
  • 5

消费者示例:

        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");  
        // 创建一个非持久的、唯一的且自动删除的队列  
        String queueName = channel.queueDeclare().getQueue();  
        // 为转发器指定队列,设置binding  
        channel.queueBind(queueName, EXCHANGE_NAME, "");  
        QueueingConsumer consumer = new QueueingConsumer(channel);  
        // 指定接收者,第二个参数为自动应答,无需手动应答  
        channel.basicConsume(queueName, true, consumer);  
        while (true)  
        {  
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();  
            String message = new String(delivery.getBody());  
            doSomething(message);  
        }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

示例代码所用API介绍:
(1)channel.exchangeDeclare(exchange, type );
exchange: 交换机名。
type:交换机类型,值为fanout、direct、topic、headers其中之一。
(2)channel.queueDeclare():创建一个非持久的、唯一的、自动删除的队列且队列名称由服务器随机产生。
(3)channel.queueBind(queue, exchange, bindingKey);
queue:队列名。
exchange:交换机名。
bindingKey:绑定键。
(4)channel.basicPublish(exchange, routingKey, basicProperties, body)
exchange:交换机名。
routingKey:消息绑定的路由键。
basicProperties:消息属性,详细字段见上一节不用交换机的队列
body:消息实体,字节数组。

三 direct类型的交换机

这里写图片描述
1、消息分发规则:消息会被推送至绑定键(binding key)和消息发布附带的选择键(routing key)完全匹配的队列。
2、图示说明:消息1附带路由键“error”、与绑定键“error”匹配,而队列Q4、Q5与交换机X间都存在绑定键“error”所以消息1被分发到Q4、Q5;消息2附带路由键“info”,而队列Q4与交换机间存在绑定建“info”,所以消息2被分发到队列Q4。

3、分发到队列的消息不再带有绑定键,事实上分发到队列的消息不再带有发送者的任何信息,当然如果消息实体里面包含了发送者消息,那么消费者可以获取发送者信息。

生产者示例:

        chanel.exchangeDeclare(EXCHANGE_NAME, "direct"); 
        // 发布消息至转发器,指定routingkey  
        chanel.basicPublish(EXCHANGE_NAME, "error", null, message1.getBytes()); 
        chanel.basicPublish(EXCHANGE_NAME, "info", null, message2.getBytes()); 
  • 1
  • 2
  • 3
  • 4

消费者示例:

        channel.exchangeDeclare(EXCHANGE_NAME, "direct");  
        String queueName = channel.queueDeclare().getQueue();  
        // 指定binding_key  
        channel.queueBind(queueName, EXCHANGE_NAME, "error");   
        channel.queueBind(queueName, EXCHANGE_NAME, "warning");
        QueueingConsumer consumer = new QueueingConsumer(channel);  
        channel.basicConsume(queueName, true, consumer);  
        while (true)  
        {  
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();  
            String message = new String(delivery.getBody()); 
            doSomething(message);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

四 topic类型的交换机

这里写图片描述

1、消息分发规则:一个附带特殊的选择键将会被转发到绑定键与之匹配的队列中。

2、routingKey于bindingKey匹配规则:
routingKey必须是由点隔开的一系列的标识符组成。标识符可以是任何东西,但是一般都与消息的某些特性相关。一些合法的选择键的例子:”stock.usd.nyse”, “nyse.vmw”,”quick.orange.rabbit”.你可以定义任何数量的标识符,上限为255个字节。
绑定键和选择键的形式一样。。需要注意的是:关于绑定键有两种特殊的情况。

  • *可以匹配一个标识符。
  • #可以匹配0个或多个标识符。

3、图示说明:
消息1附带路由键“fast.orange.*”与绑定键“#”、“*.orange.*”匹配,所以消息1被分发给队列Q6、Q7;消息2附带路由键“lazy.orange.a.b”与绑定键“#”、“lazy.#”匹配,所以消息2被分发给队列Q6、Q8。

代码示例与direct类型转发器基本雷同,只是路由键和绑定键格式不一样,这里不再赘述。

四 headers类型的交换机

这里写图片描述

1、消息分发规则:headers类型的交换机分发消息不依赖routingKey,是使用发送消息时basicProperties对象中的headers来匹配的。headers是一个键值对类型,发送者发送消息时将这些键值对放到basicProperties对象中的headers字段中,队列绑定交换机时绑定一些键值对,当两者匹配时,队列就可以收到消息。匹配模式有两种,在队列绑定到交换机时用x-match来指定,all代表定义的多个键值对都要满足,而any则代码只要满足一个就可以了。fanout,direct,topic exchange的routingKey都需要要字符串形式的,而headers exchange则没有这个要求,因为键值对的值可以是任何类型。

2、图示说明:
消息1附带的键值对与Q9绑定键值对的color匹配、speed不匹配,但是Q9的x-match设置为any,因此只要有一项匹配消息1就可以被分发到Q9。消息1与Q10完全匹配,消息2与Q10部分匹配,由于Q10的x-match设置为all,所以只能接受到消息1。

3、代码示例

生产者示例:

        //声明转发器和类型headers  
        channel.exchangeDeclare(EXCHANGE_NAME, ExchangeTypes.HEADERS,false,true,null);  
        String message = "消息1";  
        Map<String,Object> headers =  new Hashtable<String, Object>();  
        headers.put("aaa", "01234");  
        Builder properties = new BasicProperties.Builder();  
        properties.headers(headers);  
        // 指定消息发送到的转发器,绑定键值对headers键值对  
        channel.basicPublish(EXCHANGE_NAME, "",properties.build(),message.getBytes()); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

消费者示例:

        //声明转发器和类型headers  
        channel.exchangeDeclare(EXCHANGE_NAME, ExchangeTypes.HEADERS,false,true,null);  
        channel.queueDeclare(QUEUE_NAME,false, false, true,null);  

        Map<String, Object> headers = new Hashtable<String, Object>();  
        headers.put("x-match", "any");//all any  
        headers.put("aaa", "01234");  
        headers.put("bbb", "56789");  
        // 为转发器指定队列,设置binding 绑定header键值对  
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME,"", headers);  
        QueueingConsumer consumer = new QueueingConsumer(channel);  
        // 指定接收者,第二个参数为自动应答,无需手动应答  
        channel.basicConsume(QUEUE_NAME, true, consumer);  
        while (true) {  
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();  
            String message = new String(delivery.getBody());  
            System.out.println(message);  
        }   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

示例代码API介绍:
(1) channel.exchangeDeclare(exchange, type, durable, autoDelete, arguments);
exchange:交换机名
type:交换机类型
durable:持久化标志
autoDelete:自动删除
arguments:扩展参数,具体如下表

扩展参数键意义
alternate-exchange 下面简称AE,当一个消息不能被route的时候,如果exchange设定了AE,则消息会被投递到AE。如果存在AE链,则会按此继续投递,直到消息被route或AE链结束或遇到已经尝试route过消息的AE。

(2)channel.queueBind(queue, exchange, routingKey, arguments);
queue:队列名
exchange:交换机名
routingKey:选择键(路由键)

arguments:headers类型交换机绑定时指定的键值对 

 

 

 

 

queueDeclare(String queue, 
            boolean durable, 
            boolean exclusive, 
            Map<String, Object> arguments);
    • queue: 队列名称

    • durable: 是否持久化, 队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失,如果想重启之后还存在就要使队列持久化,保存到Erlang自带的Mnesia数据库中,当rabbitmq重启之后会读取该数据库

    • exclusive:是否排外的,有两个作用,一:当连接关闭时connection.close()该队列是否会自动删除;二:该队列是否是私有的private,如果不是排外的,可以使用两个消费者都访问同一个队列,没有任何问题,如果是排外的,会对当前队列加锁,其他通道channel是不能访问的,如果强制访问会报异常:com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=405, reply-text=RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'queue_name' in vhost '/', class-id=50, method-id=20)一般等于true的话用于一个队列只能有一个消费者来消费的场景

    • autoDelete:是否自动删除,当最后一个消费者断开连接之后队列是否自动被删除,可以通过RabbitMQ Management,查看某个队列的消费者数量,当consumers = 0时队列就会自动删除

    • arguments: 
      队列中的消息什么时候会自动被删除?

      • Message TTL(x-message-ttl):设置队列中的所有消息的生存周期(统一为整个队列的所有消息设置生命周期), 也可以在发布消息的时候单独为某个消息指定剩余生存时间,单位毫秒, 类似于redis中的ttl,生存时间到了,消息会被从队里中删除,注意是消息被删除,而不是队列被删除, 特性Features=TTL, 单独为某条消息设置过期时间AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder().expiration(“6000”); 
        channel.basicPublish(EXCHANGE_NAME, “”, properties.build(), message.getBytes(“UTF-8”));

      • Auto Expire(x-expires): 当队列在指定的时间没有被访问(consume, basicGet, queueDeclare…)就会被删除,Features=Exp

      • Max Length(x-max-length): 限定队列的消息的最大值长度,超过指定长度将会把最早的几条删除掉, 类似于mongodb中的固定集合,例如保存最新的100条消息, Feature=Lim

      • Max Length Bytes(x-max-length-bytes): 限定队列最大占用的空间大小, 一般受限于内存、磁盘的大小, Features=Lim B

      • Dead letter exchange(x-dead-letter-exchange): 当队列消息长度大于最大长度、或者过期的等,将从队列中删除的消息推送到指定的交换机中去而不是丢弃掉,Features=DLX

      • Dead letter routing key(x-dead-letter-routing-key):将删除的消息推送到指定交换机的指定路由键的队列中去, Feature=DLK

      • Maximum priority(x-max-priority):优先级队列,声明队列时先定义最大优先级值(定义最大值一般不要太大),在发布消息的时候指定该消息的优先级, 优先级更高(数值更大的)的消息先被消费,

      • Lazy mode(x-queue-mode=lazy): Lazy Queues: 先将消息保存到磁盘上,不放在内存中,当消费者开始消费的时候才加载到内存中
      • Master locator(x-queue-master-locator)

一 不用交换机的队列

这里写图片描述

生产者示例:

        //声明队列  
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //发送10条消息,依次在消息后面附加1-10个点  
        for (int i = 6; i > 0; i--)  
        {  
            String message = "helloworld";
            channel.basicPublish("", QUEUE_NAME,null, message.getBytes());  
        }   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

消费者示例:

        //声明队列  
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);  
        QueueingConsumer consumer = new QueueingConsumer(channel);  
        // 指定消费队列  
        channel.basicConsume(QUEUE_NAME, true, consumer);  
        while (true)
        {  
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            doWork(message);
        }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

API介绍
(1) channel.queueDeclare(queue, durable, exclusive, autoDelete, arguments) ;
可以看到生产者和消费者用同样的参数声明了队列,官方推荐该做法,事实上对于一个已经存在的队列即使该方法试图用不同的参数去创建队列也不会有任何效果,这意味着不会改变队列更不会影响队列现在的工作。
queue:队列名字
durable:队列持久化标志,true为持久化队列
exclusive:exclusive:排他队列,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。这里需要注意三点:其一,排他队列是基于连接可见的,同一连接的不同信道是可以同时访问同一个连接创建的排他队列的。其二,“首次”,如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。其三,即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。这种队列适用于只限于一个客户端发送读取消息的应用场景。
autoDelete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于临时队列。
arguments:Map类型,关于队列及队列中消息的详细设置

arguments键意义
x-message-ttl 数字类型,标志时间,以豪秒为单位 标志队列中的消息存活时间,也就是说队列中的消息超过了制定时间会被删除
x-expires 数字类型,标志时间,以豪秒为单位 队列自身的空闲存活时间,当前的queue在指定的时间内,没有consumer、basic.get也就是未被访问,就会被删除。
x-max-length和x-max-length-bytes 数字 最大长度和最大占用空间,设置了最大长度的队列,在超过了最大长度后进行插入会删除之前插入的消息为本次的留出空间,相应的最大占用大小也是这个道理,当超过了这个大小的时候,会删除之前插入的消息为本次的留出空间。
x-dead-letter-exchange和x-dead-letter-routing-key 字符串 消息因为超时或超过限制在队列里消失,这样我们就丢失了一些消息,也许里面就有一些是我们做需要获知的。而rabbitmq的死信功能则为我们带来了解决方案。设置了dead letter exchange与dead letter routingkey(要么都设定,要么都不设定)那些因为超时或超出限制而被删除的消息会被推动到我们设置的exchange中,再根据routingkey推到queue中
x-max-priority 数字 队列所支持的优先级别,列如设置为5,表示队列支持0到5六个优先级别,5最高,0最低,当然这需要生产者在发送消息时指定消息的优先级别,消息按照优先级别从高到低的顺序分发给消费者

(2)channel.basicPublish(exchange, routingKey, mandatory, immediate, basicProperties, body);
exchange: 交换机名
routingKey: 路由键
mandatory:当mandatory标志位设置为true时,如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,那么会调用basic.return方法将消息返还给生产者, channel.addReturnListener添加一个监听器,当broker执行basic.return方法时,会回调handleReturn方法,这样我们就可以处理变为死信的消息了;当mandatory设为false时,出现上述情形broker会直接将消息扔掉;通俗的讲,mandatory标志告诉broker代理服务器至少将消息route到一个队列中,否则就将消息return给发送者。
immediate: rabbitMq3.0已经不在支持了,3.0以前这个标志告诉服务器如果该消息关联的queue上有消费者,则马上将消息投递给它,如果所有queue都没有消费者,直接把消息返还给生产者,不用将消息入队列等待消费者了。
basicProperties:消息的详细属性,例如优先级别、持久化、到期时间,headers类型的exchange要用到的是其中的headers字段。

            public BasicProperties(
            String contentType,//消息类型如:text/plain
            String contentEncoding,//编码
            Map<String,Object> headers,
            Integer deliveryMode,//1:nonpersistent 2:persistent
            Integer priority,//优先级
            String correlationId,
            String replyTo,//反馈队列
            String expiration,//expiration到期时间
            String messageId,
            Date timestamp,
            String type,
            String userId,
            String appId,
            String clusterId)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

body:消息实体,字节数组。
(3)QueueingConsumer:这是一个已经实现好了的Consumer,相比于自己实现Consumer接口,这是个比较安全快捷的方式。该类基于jdk的BlockingQueue实现,handleDelivery方法中将收到的消息封装成Delivery对象,并存放到BlockingQueue中,这相当于消费者本地存放了一个消息缓存队列。nextDelivery()方法底层调用的BlockingQueue的阻塞方法take()。
(4)channel.basicConsume(queue, autoAck, consumer);
queue:队列名。
autoAck:自动应答标志,true为自动应答。
consumer:消费者对象,可以自己实现Consumer接口,建议使用QueueingConsumer。

二 fanout类型的交换机

这里写图片描述
1、消息与队列匹配规则:fanout类型交换机会将接收到的消息广播给所有与之绑定的队列。
2、现在我们来演示一下如图所示的消息广播机制,不难注意到这种情况生产者P只关心消息发送给哪个交换机,由交换机X决定消息发送到哪些队列,,而消费者C只关注订阅哪个队列。

生产者示例:

        // 声明转发器和类型  
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout" );     
        String message = "消息1";  
        // 往转发器上发送消息  
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes()); 
  • 1
  • 2
  • 3
  • 4
  • 5

消费者示例:

        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");  
        // 创建一个非持久的、唯一的且自动删除的队列  
        String queueName = channel.queueDeclare().getQueue();  
        // 为转发器指定队列,设置binding  
        channel.queueBind(queueName, EXCHANGE_NAME, "");  
        QueueingConsumer consumer = new QueueingConsumer(channel);  
        // 指定接收者,第二个参数为自动应答,无需手动应答  
        channel.basicConsume(queueName, true, consumer);  
        while (true)  
        {  
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();  
            String message = new String(delivery.getBody());  
            doSomething(message);  
        }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

示例代码所用API介绍:
(1)channel.exchangeDeclare(exchange, type );
exchange: 交换机名。
type:交换机类型,值为fanout、direct、topic、headers其中之一。
(2)channel.queueDeclare():创建一个非持久的、唯一的、自动删除的队列且队列名称由服务器随机产生。
(3)channel.queueBind(queue, exchange, bindingKey);
queue:队列名。
exchange:交换机名。
bindingKey:绑定键。
(4)channel.basicPublish(exchange, routingKey, basicProperties, body)
exchange:交换机名。
routingKey:消息绑定的路由键。
basicProperties:消息属性,详细字段见上一节不用交换机的队列
body:消息实体,字节数组。

三 direct类型的交换机

这里写图片描述
1、消息分发规则:消息会被推送至绑定键(binding key)和消息发布附带的选择键(routing key)完全匹配的队列。
2、图示说明:消息1附带路由键“error”、与绑定键“error”匹配,而队列Q4、Q5与交换机X间都存在绑定键“error”所以消息1被分发到Q4、Q5;消息2附带路由键“info”,而队列Q4与交换机间存在绑定建“info”,所以消息2被分发到队列Q4。

3、分发到队列的消息不再带有绑定键,事实上分发到队列的消息不再带有发送者的任何信息,当然如果消息实体里面包含了发送者消息,那么消费者可以获取发送者信息。

生产者示例:

        chanel.exchangeDeclare(EXCHANGE_NAME, "direct"); 
        // 发布消息至转发器,指定routingkey  
        chanel.basicPublish(EXCHANGE_NAME, "error", null, message1.getBytes()); 
        chanel.basicPublish(EXCHANGE_NAME, "info", null, message2.getBytes()); 
  • 1
  • 2
  • 3
  • 4

消费者示例:

        channel.exchangeDeclare(EXCHANGE_NAME, "direct");  
        String queueName = channel.queueDeclare().getQueue();  
        // 指定binding_key  
        channel.queueBind(queueName, EXCHANGE_NAME, "error");   
        channel.queueBind(queueName, EXCHANGE_NAME, "warning");
        QueueingConsumer consumer = new QueueingConsumer(channel);  
        channel.basicConsume(queueName, true, consumer);  
        while (true)  
        {  
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();  
            String message = new String(delivery.getBody()); 
            doSomething(message);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

四 topic类型的交换机

这里写图片描述

1、消息分发规则:一个附带特殊的选择键将会被转发到绑定键与之匹配的队列中。

2、routingKey于bindingKey匹配规则:
routingKey必须是由点隔开的一系列的标识符组成。标识符可以是任何东西,但是一般都与消息的某些特性相关。一些合法的选择键的例子:”stock.usd.nyse”, “nyse.vmw”,”quick.orange.rabbit”.你可以定义任何数量的标识符,上限为255个字节。
绑定键和选择键的形式一样。。需要注意的是:关于绑定键有两种特殊的情况。

  • *可以匹配一个标识符。
  • #可以匹配0个或多个标识符。

3、图示说明:
消息1附带路由键“fast.orange.*”与绑定键“#”、“*.orange.*”匹配,所以消息1被分发给队列Q6、Q7;消息2附带路由键“lazy.orange.a.b”与绑定键“#”、“lazy.#”匹配,所以消息2被分发给队列Q6、Q8。

代码示例与direct类型转发器基本雷同,只是路由键和绑定键格式不一样,这里不再赘述。

四 headers类型的交换机

这里写图片描述

1、消息分发规则:headers类型的交换机分发消息不依赖routingKey,是使用发送消息时basicProperties对象中的headers来匹配的。headers是一个键值对类型,发送者发送消息时将这些键值对放到basicProperties对象中的headers字段中,队列绑定交换机时绑定一些键值对,当两者匹配时,队列就可以收到消息。匹配模式有两种,在队列绑定到交换机时用x-match来指定,all代表定义的多个键值对都要满足,而any则代码只要满足一个就可以了。fanout,direct,topic exchange的routingKey都需要要字符串形式的,而headers exchange则没有这个要求,因为键值对的值可以是任何类型。

2、图示说明:
消息1附带的键值对与Q9绑定键值对的color匹配、speed不匹配,但是Q9的x-match设置为any,因此只要有一项匹配消息1就可以被分发到Q9。消息1与Q10完全匹配,消息2与Q10部分匹配,由于Q10的x-match设置为all,所以只能接受到消息1。

3、代码示例

生产者示例:

        //声明转发器和类型headers  
        channel.exchangeDeclare(EXCHANGE_NAME, ExchangeTypes.HEADERS,false,true,null);  
        String message = "消息1";  
        Map<String,Object> headers =  new Hashtable<String, Object>();  
        headers.put("aaa", "01234");  
        Builder properties = new BasicProperties.Builder();  
        properties.headers(headers);  
        // 指定消息发送到的转发器,绑定键值对headers键值对  
        channel.basicPublish(EXCHANGE_NAME, "",properties.build(),message.getBytes()); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

消费者示例:

        //声明转发器和类型headers  
        channel.exchangeDeclare(EXCHANGE_NAME, ExchangeTypes.HEADERS,false,true,null);  
        channel.queueDeclare(QUEUE_NAME,false, false, true,null);  

        Map<String, Object> headers = new Hashtable<String, Object>();  
        headers.put("x-match", "any");//all any  
        headers.put("aaa", "01234");  
        headers.put("bbb", "56789");  
        // 为转发器指定队列,设置binding 绑定header键值对  
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME,"", headers);  
        QueueingConsumer consumer = new QueueingConsumer(channel);  
        // 指定接收者,第二个参数为自动应答,无需手动应答  
        channel.basicConsume(QUEUE_NAME, true, consumer);  
        while (true) {  
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();  
            String message = new String(delivery.getBody());  
            System.out.println(message);  
        }   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

示例代码API介绍:
(1) channel.exchangeDeclare(exchange, type, durable, autoDelete, arguments);
exchange:交换机名
type:交换机类型
durable:持久化标志
autoDelete:自动删除
arguments:扩展参数,具体如下表

扩展参数键意义
alternate-exchange 下面简称AE,当一个消息不能被route的时候,如果exchange设定了AE,则消息会被投递到AE。如果存在AE链,则会按此继续投递,直到消息被route或AE链结束或遇到已经尝试route过消息的AE。

(2)channel.queueBind(queue, exchange, routingKey, arguments);
queue:队列名
exchange:交换机名
routingKey:选择键(路由键)
arguments:headers类型交换机绑定时指定的键值对