消息中间件RabbitMQ(一):RabbitMQ的五种基本模型与高级特性

前言:本文为原创 若有错误欢迎评论!

一.基本介绍

1.安装
请参考后面博客的centos安装应用的博客!

2.客户端

  1. 打开 http://192.168.56.129:15672(默认账号密码 guest guest)

  2. virtual host概念
    是虚拟划分资源 就好比redis有16个数据库 只是逻辑划分 并不是平分资源
    用于划分模块 每个virtual host下的exchange和quene不可以同名

  3. 创建一个新virtual host 并给guest添加该虚拟主机权限(也可以新创建一个用户 分配给新用户)

二.五种基本模型

1.准备

  1. 依赖:
			<dependency>
                <groupId>com.rabbitmq</groupId>
                <artifactId>amqp-client</artifactId>
                <version>3.6.6(和安装的rabbitmq版本一致)</version>
            </dependency>
  1. 生产者和消费者都获要在最前面先获得channel(通道):
   ConnectionFactory connectionFactory = new ConnectionFactory();
    connectionFactory.setHost("192.168.11.76");
    connectionFactory.setPort(5672);
    connectionFactory.setVirtualHost("/");//没有设置账号和密码 因为默认是guest guest

    Connection connection = connectionFactory.newConnection();
    // 从连接中创建通道,这是完成大部分API的地方。
    Channel channel = connection.createChannel();
  1. 生产者最后要关闭资源(消费者不关 要一直监听)
    //关闭通道和连接
    channel.close();
    connection.close();

2.模型(默认交换机)

  1. 基本消费模型
    (一个生产者、默认交换机、一个队列、一个消费者)

     producer:只声明一个队列 
      //队列名字
     String queneName= "simple_queue"
     // 消息内容
     String message = "Hello World!";
    
     // 声明(创建)队列,必须声明队列才能够发送消息,我们可以把消息发送到队列中。
     // 声明一个队列是幂等的 - 只有当它不存在时才会被创建
     channel.queueDeclare(queneName, false, false, false, null);
    
     channel.basicPublish("",queneName, null, message.getBytes());
    
     consumer:只声明一个队列 也不用任何绑定
                    / 声明队列
     channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    
     // 定义队列的消费者
     DefaultConsumer consumer = new DefaultConsumer(channel) {
         // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
         @Override
         public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException {
             // body 即消息体
             String msg = new String(body);
             System.out.println(" [x] received : " + msg + "!");
             // 手动进行ACK
             channel.basicAck(envelope.getDeliveryTag(), false);
         }
     };
    
                   // 监听队列,第二个参数false,手动进行ACK
     channel.basicConsume(QUEUE_NAME, false, consumer);
    
     (ACK:即收到消息后要确认消息被消费才会在队列中删除该消息)
    
  2. work消息模型
    (一个生产者、默认交换机、一个队列、多个消费者)

         producer:不变 但是channel.basicPublish()多调用几次发送消息
    
         consumer:增加:channel.basicQos(1);//一次可以消费多少个消息
                            且建立多个刚才的消费对象 还是不设置exchange且监听同一队列
    
             (如果不在consumer设置channel.basicQos() 如果消息有堆积 则会均匀的分配给每个消费者 因为各消费性能不同 可能造成资源)
    

3.模型(指定交换机)

  • 概念:
    多个消费者 -> 每个消费者都有自己的队列 -> 每个队列绑定到交换机
    生产者发送消息到交换机 -> 交换机决定发送到哪个队列)
  1. 订阅模型(fanout)
    (和fanout交换机绑定的队列都可以消费该消息 即一个消息多个消费者消费)

        producer:声名exchange并发送到exchange 不再声明quene与不发到quene
    
        private final static String EXCHANGE_NAME = "fanout_exchange_test";
    
       // 声明exchange,指定类型为fanout
       channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
    
       // 消息内容
       String message = "Hello everyone";
       // 发布消息到Exchange
       channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
    
    
       consumer(要复制多个):声明队列后还要声明交换机 只绑定到交换机
    
                    //声明队列
       channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    
       // 绑定队列到交换机
       channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
    
       // 定义队列的消费者
       DefaultConsumer consumer = new DefaultConsumer(channel) {
         // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
         @Override
         public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,byte[] body) throws IOException {
             // body 即消息体
             String msg = new String(body);
             System.out.println(" [消费者1] received : " + msg + "!");
         }
     };
    
     // 监听队列,自动返回完成
     channel.basicConsume(QUEUE_NAME, true, consumer);
    
  2. 订阅模型(Direct)
    (和direct交换机绑定的队列 只有routing对应才可以消费 即只能被一个队列消费)

      producer:声明交换机 在发送消息时指定routing
                     private final static String EXCHANGE_NAME = "direct_exchange_test";
    
     // 声明exchange,指定类型为direct
     channel.exchangeDeclare(EXCHANGE_NAME, "direct");
    
     // 消息内容
     String message = "商品新增了, id = 1001";
    
     // 发送消息,并且指定routing key 为:insert ,代表新增商品
     channel.basicPublish(EXCHANGE_NAME, "insert", null, message.getBytes());
    
    
        consumer(要复制多个):要同时声明队列和交换机 然后队列绑定交换机时routing不同 可以绑定多次交换机
     private final static String QUEUE_NAME = "direct_exchange_queue_1";
     private final static String EXCHANGE_NAME = "direct_exchange_test";
    
     channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    
     // 绑定队列到交换机,同时指定需要订阅的routing key。假设此处需要update和delete消息
     channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "update");
     channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "delete");
    
     // 定义队列的消费者
     DefaultConsumer consumer = new DefaultConsumer(channel) {
         // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
         @Override
         public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,byte[] body) throws IOException {
             // body 即消息体
             String msg = new String(body);
             System.out.println(" [消费者1] received : " + msg + "!");
         }
     };
    
     // 监听队列,自动ACK
     channel.basicConsume(QUEUE_NAME, true, consumer);
    

    注意:

    1. 一个channel连接可以声明多个不同名的quene和多个不同名exchange 且一个quene可以用不同的routing与exchange绑定多次 也可以和多个exchange进行绑定
    2. 但是每次声明一个队列就等于创建一个队列 绑定的结果可以修改 但是声明的时候的属性不可以不一样 不然会报错 此时应该调用修改的api
  3. 订阅模型(Topic)
    (比"Drict"多了通配符而已)

    “*”:只匹配" . “后一个单词(如audit.*:只能匹配audit.irs
    “#”:匹配不论” . "多少个单词(如audit.#:能够匹配audit.irs.corporate 或者 audit.irs

    producer:channel.basicPublish(EXCHANGE_NAME, "insert.test", null, message.getBytes());
    
    consumer:channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "insert.#");
    
    
    
  4. 总结

    • 前两个模型生产和消费都只声明一个quene
    • fanout
      生产者:声明exchange、发布时不用指定routing
      消费者:声明exchange和quene 但quene绑定exchange时不指定routing
    • direct和topic
      生产者:声明exchange、发布时指定routing
      消费者:声明exchange和quene、quene绑定exchange时通过routing不同可以绑定多次

4.设置消息的属性(AMQP.BasicProperties)

    producer:
    Map<String, Object> headers = new HashMap<>();
    headers.put("properties1", "111");
    headers.put("properties2", "222");


    AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
        .deliveryMode(2)//1:不持久化 2:持久化
        .contentEncoding("UTF-8")
        .expiration("10000")//过期时间 单位是毫秒
        .headers(headers)//添加一些自定义属性 可以在消费端用map形式取出
        .build();

    channel.basicPublish("", "test001", properties, msg.getBytes());//既没有声明队列 也没有声明交换机就使用的是默认交换机

consumer:
        DefaultConsumer consumer = new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        Map<String, Object> propertiesHeaders = properties.getHeaders();
    }
};

三.高级特性

1.confirm机制

  • 概念:
    如果消息发送到交换机不成功会返回失败的ack

  • provider:

      String exchangeName = "test_confirm_exchange";
      String routingKey = "confirm.save";
    
      String msg = "Hello RabbitMQ Send confirm message!";
    
                  //必须先指定消息投递模式: 确认模式
      channel.confirmSelect();
    
     //添加一个确认监听
      channel.addConfirmListener(new ConfirmListener() {
          @Override
          public void handleNack(long deliveryTag, boolean multiple) throws IOException {
              System.err.println("-------no ack!-----------");
          }
    
          @Override
          public void handleAck(long deliveryTag, boolean multiple) throws IOException {
              System.err.println("-------ack!-----------");
      }
    
      channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
    
      //有监听就不要在最后关闭资源
    
  • consumer:

      DefaultConsumer consumer = new DefaultConsumer(channel) {
          @Override
          public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
              if(true) {
                  channel.basicAck(envelope.getDeliveryTag(), false);
              }else {
                  channel.basicNack(envelope.getDeliveryTag(),false,false);
              }
          }
      };
    
      // 手工签收 必须要关闭 autoAck = false
      channel.basicConsume(queueName, false,consumer);
    

    Envelope的属性
    deliveryTag:消息的唯一标识 是判断是否返回ack成功的唯一标识
    String _exchange:来自哪个交换机
    String _routingKey:来自哪个路由

    void basicAck(long var1, boolean var3):
    var1:消息的标识
    var2:是否批量返回ack成功

    void basicNack(long var1, boolean var3, boolean var4) throws IOException;
    var1:消息的标识
    var3:是否批量返回ack失败
    var4:是否加入队列尾再次重试

2.Return机制

  • 概念:
    如果消息从交换机路由队列不成功就会回调该方法

  • producer:

      String exchange = "test_return_exchange";
      String routingKeyError = "abc.save";
    
      String msg = "Hello RabbitMQ Return Message";
    
      //添加一个return回调的监听
      channel.addReturnListener(new ReturnListener() {
          @Override
          public void handleReturn(int replyCode, String replyText, String exchange,String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
              
          }
      });
    
      //必须指定第三个参数boolean mandatory为true 开启委托
      channel.basicPublish(exchange, routingKeyError, true, null, msg.getBytes());
    

    总结:
    如果消息没有到exchange,则confirm回调,ack=false
    如果消息到达exchange,则confirm回调,ack=true
    exchange到queue成功,则不回调return
    exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)

3.消息限流

  • 概念:
    如果消息大量堆积不限流可能压垮服务器 且不限流会在堆积时均等分配 造成资源浪费

  • cousumer:

      //限流方式 第一件事就是 autoAck设置为 false 只有返回ack消费成功才接着接收消息
      channel.basicQos(0, 1, false);
    
      channel.basicConsume(queueName, false, new MyConsumer(channel));
    

    void basicQos(int prefetchSize, int prefetchCount, boolean global)
    prefetchSize:默认就设置为0
    prefetchCount:一次处理的消息个数 通常都设置为1
    global:是否在exchange级别进行限流 设为false则时consumer级别的限流

4.死信队列

  • 原因

    • 消息被拒绝 (如:当"basic.reject"、“basic.nack” 时requene=false)
    • 消息在路由到队列之后TTL过期(TTL有两种:在客户端给队列的全部消息设置一个过期时间、在发送消息时给消息单独设置)
    • 达到队列最大长度
  • 概念:
    如果消息变成死信 则会把消息投递到声明队列时设置的交换机上(routing在投递到死信的交换机时不变)

  • consumer:

      Map<String, Object> agruments = new HashMap<String, Object>();
      agruments.put("x-dead-letter-exchange", "dlx.exchange");
      agruments.put("x-dead-letter-routing-key", "dlx.routingkey");
      //这个agruments属性,要设置到声明队列上
      channel.queueDeclare(queueName, true, false, false, agruments);
    
      channel.exchangeDeclare(exchangeName, "topic", true, false, null);
      channel.queueBind(queueName, exchangeName, routingKey);
    
      channel.basicConsume(queueName, true, new MyConsumer(channel));
    

    x-dead-letter-exchange:
    是设置队列属性里的死信队列的名字(不可以改变 也可以通过这种方式设置队列其他属性)
    x-dead-letter-routing-key:
    设置死信在死信交换的路由(必须要设置 二者缺一不可)

    注意:必须提前把这个交换机及其接收对应消息的队列创建好才可以接收死信消息

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RabbitMQ 是一种流行的消息中间件,支持多种消息模型。以下是 RabbitMQ五种常见消息模型: 1. 简单模型(Simple Model):最基本的消息模型,包括一个生产者和一个消费者。生产者将消息发送到队列,消费者从队列中接收并处理消息。 2. 工作队列模型(Work Queue Model):也称为任务队列模型,包括一个或多个生产者和多个消费者。生产者将消息发送到队列,消费者按照一定的策略接收并处理消息。 3. 发布/订阅模型(Publish/Subscribe Model):包括一个生产者和多个消费者。生产者将消息发送到交换机(Exchange),交换机将消息广播给所有绑定到该交换机的队列,消费者从各自的队列中接收并处理消息。 4. 路由模型(Routing Model):类似于发布/订阅模型,但具有更灵活的消息路由机制。生产者将消息发送到交换机,并指定一个或多个路由键(Routing Key),交换机根据路由键将消息转发给绑定了相应路由键的队列,消费者从队列中接收并处理消息。 5. 主题模型(Topic Model):也是一种灵活的消息路由模型。生产者将消息发送到交换机,并指定一个主题(Topic),交换机根据主题将消息转发给与之匹配的队列,消费者从队列中接收并处理消息。 这些消息模型提供了不同的灵活性和可扩展性,可以根据具体的应用场景选择合适的模型来实现消息传递。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值