RabbitMQ-消息队列

概念解析

消息队列具有三个重要特点:解耦、销峰控流、广播,根据这三个特性,用现实生活例子,解释其作用
解耦:比方说我要租房,哪我就需要知道都有哪些房源,但房源分布在各个业主手里,常规情况下,我需要一个个去询问,这样就造成了我的工作量特别多,我只是想租个房子,但有许多额外的事要做,这就违背了功能单一的设计模式。现实生活中,有房屋中介代理,这就解决了我和业主的直接联系,我只需要找中介即可,业主只需要在中间挂牌自己的房子,避免了从消费者(我)和生产者(业主)复杂的联系。
 
 以业务-程序来举例,12306上购票,选好票后,等待支付,并且要一定频率的给客户发送支付通知,如果把选票和支付,做在一起,那么这块就特别的重,不方便横行扩展
 
 销峰控流:以传统nginx代理多服务实例为例,如果在代理模块业务比较多,就会存在两个风险问题:1、入口压力大,入口就会成为瓶颈,都挤在门这  2、一般来说,入口就那么大,不方便水平扩展。使用消息队列,以异步方式,把压力分摊到下一层,下一层可以使用多实例的方式,水平扩展,增加处理能力。
 
 广播:以传统的告知所有人消息为例,最初要挨家挨户的去说,送信的人工作就很重,后来有了喇叭,有事就通过喇叭喊,送信的人一下子就轻松下来了,他的活就很单一了(模块功能单一),当然前提是每户能听到喇叭响(消费者订阅了某些队列)。实时上,消息队列要比大喇叭喊要全面的多,不仅可以广播消息,还能够针对性对用户订阅了哪些消息进行发送,并且能够收到用户的已收确认消息。
 

组成

最简单消息队列模型为:生产者、Broker(可以理解成一台服务器上起一个进程),消费者
但是有些消息要给A消费,有些消息要被B消费,所以就有了队列QUEUE(kafka中使用topic):
哪要把哪些消息分给队列1、哪些消息分给队列2呢,这时就需要对消息进行路由,就产生了消息交换机Exchanger
但这时还不行,有交换机可以进行消息分发,还是不知道按照什么规则进行分发呢,这时就有了路由规则routingKey和建立交换机和队列的绑定binding
现在业务是通了,但是是个单例模式,需要还需要转变成高可用、分布式模式
 

特例应用(定时消费)

真实消费队列中间件是没有对消息定时消费的功能的,但有消息定时失效的功能,可以把失效的消息转发到另外一个队列,消费者监控这个队列,即可实现消息定时消费的功能。

原生demo

1、交换机给队列广播模式:fanout
生产者:
package mq3;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class FanoutProducer {
     public static void main(String[] args) {
          
          
          // 创建连接
          ConnectionFactory factory = new ConnectionFactory();
          factory.setUsername("guest");
          factory.setPassword("guest");
          // 设置 RabbitMQ 地址
          factory.setHost("192.168.154.117");
          factory.setVirtualHost("/");
          
          Connection conn = null;
          
          Channel channel = null;
          
          String exchangerName = "fanout_exchanger_test1";
          String queueName1 = "fanout_queue_test_1";
          String queueName2 = "fanout_queue_test_2";
          
          try {
              conn = factory.newConnection();
              channel = conn.createChannel();
              
              // 设置交换机
              channel.exchangeDeclare(exchangerName, "fanout");
              // 设置队列
              channel.queueDeclare(queueName1, true, false, false, null);
              channel.queueDeclare(queueName2, true, false, false, null);
              
              // 绑定交换机和队列
              channel.queueBind(queueName1, exchangerName, "");
              channel.queueBind(queueName2, exchangerName, "");
              
              // 生产数据
              
              Scanner scanner = new Scanner(System.in);
              
              while(scanner.hasNextLine()) {
                   String line = scanner.nextLine();
                   
                   channel.basicPublish(exchangerName, "", null, line.getBytes());
              }
              
              scanner.close();
              
              
          }catch(Exception e ) {
              e.printStackTrace();
          }finally{
              try {
                   channel.close();
                   conn.close();
              } catch (IOException e) {
                   e.printStackTrace();
              } catch (TimeoutException e) {
                   e.printStackTrace();
              }
              
          }
          
          
     }
}
消费者:
package mq3;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
public class FanoutConsumer1 {
     private static Channel channel;
     private static Connection conn;
     public static void main(String[] args) {
          // 创建连接
          ConnectionFactory factory = new ConnectionFactory();
          factory.setUsername("guest");
          factory.setPassword("guest");
          // 设置 RabbitMQ 地址
          factory.setHost("192.168.154.117");
          factory.setVirtualHost("/");
          conn = null;
          channel = null;
          
          String queueName1 = "fanout_queue_test_1";
          
          try {
              
              conn = factory.newConnection();
              channel = conn.createChannel();
              
              DefaultConsumer consumer = new DefaultConsumer(channel) {
                   @Override
                   public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                             byte[] body) throws IOException {
                        System.out.println(new String(body));
                        
                   }
              };
              
              
              channel.basicConsume(queueName1, true, consumer);
              
          }catch(Exception e) {
              e.printStackTrace();
          }
     }
}
2、交换机给队列按照字段完全匹配分发模式:direct,通过字段"direct1"和"direct2"
生产者:
package mq3;
 
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
 
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
 
public class DirectProducer {
 
    public static void main(String[] args) {
        
        
        // 创建连接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUsername("guest");
        factory.setPassword("guest");
        // 设置 RabbitMQ 地址
        factory.setHost("192.168.154.117");
        factory.setVirtualHost("/");
        
        Connection conn = null;
        
        Channel channel = null;
        
        String exchangerName = "direct_exchanger_test1";
        String queueName1 = "fanout_queue_test_1";
        String queueName2 = "fanout_queue_test_2";
        
        try {
            conn = factory.newConnection();
            channel = conn.createChannel();
            
            // 设置交换机
            channel.exchangeDeclare(exchangerName, "direct");
            // 设置队列
            channel.queueDeclare(queueName1, true, false, false, null);
            channel.queueDeclare(queueName2, true, false, false, null);
            
            // 绑定交换机和队列
            channel.queueBind(queueName1, exchangerName, "direct1");
            channel.queueBind(queueName2, exchangerName, "direct2");
            
            // 生产数据
            
            Scanner scanner = new Scanner(System.in);
            
            int num = 0;
            
            while(scanner.hasNextLine()) {
                String line = scanner.nextLine();
                
                num++;
                if (num % 2 == 0)
                    channel.basicPublish(exchangerName, "direct1", null, line.getBytes());
                else
                    channel.basicPublish(exchangerName, "direct2", null, line.getBytes());
            }
            
            scanner.close();
            
            
        }catch(Exception e ) {
            e.printStackTrace();
        }finally{
            try {
                channel.close();
                conn.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
            
        }
        
        
 
    }
 
}
 
3、交换机给队列按照字段部分匹配分发模式:topic(通配符有两点注意:1、以"."进行分割 2、"*"代表1个或多个标识符,"#"代表0个或多个标识符)
下面这个例子表示:对于队列fanout_queue_test_1发送全部消息,对于队列fanout_queue_test_2只发送customer.normal消息
package mq3;
 
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
 
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
 
public class TopicProducer {
 
    public static void main(String[] args) {
        
        
        // 创建连接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUsername("guest");
        factory.setPassword("guest");
        // 设置 RabbitMQ 地址
        factory.setHost("192.168.154.117");
        factory.setVirtualHost("/");
        
        Connection conn = null;
        
        Channel channel = null;
        
        String exchangerName = "topic_exchanger_test1";
        String queueName1 = "fanout_queue_test_1";
        String queueName2 = "fanout_queue_test_2";
        
        try {
            conn = factory.newConnection();
            channel = conn.createChannel();
            
            // 设置交换机
            channel.exchangeDeclare(exchangerName, "topic");
//            // 设置队列
//            channel.queueDeclare(queueName1, true, false, false, null);
//            channel.queueDeclare(queueName2, true, false, false, null);
            
            // 绑定交换机和队列
            channel.queueBind(queueName1, exchangerName, "customer.*");
            channel.queueBind(queueName2, exchangerName, "customer.normal");
            
            // 生产数据
            
            Scanner scanner = new Scanner(System.in);
            
            int num = 0;
            
            while(scanner.hasNextLine()) {
                String line = scanner.nextLine();
                
                num++;
                if (num % 2 == 0)
                    channel.basicPublish(exchangerName, "customer.normal", null, line.getBytes());
                else
                    channel.basicPublish(exchangerName, "customer.all", null, line.getBytes());
            }
            
            scanner.close();
            
            
        }catch(Exception e ) {
            e.printStackTrace();
        }finally{
            try {
                channel.close();
                conn.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
            
        }
        
        
 
    }
 
}
 

整合springboot使用

1、pom引用
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
2、配置application.yml设置rabbitmq访问地址
 spring:
  rabbitmq:
    host: 192.168.154.117
    port: 5672
    username: guest
    password: guest
    virtual-host: springboot
3、编写交换机、队列、绑定关系
    设置广播fanout
      @Configuration
public class FanoutRabbitConfig {
 
    //队列 起名:TestFanoutQueue
    @Bean
    public Queue TestFanoutQueue() {
     
        return new Queue("TestFanoutQueue",true);
    }
 
    //Fanout交换机 起名:TestFanoutExchange
    @Bean
    FanoutExchange TestFanoutExchange() {
      //  return new FanoutExchange("TestFanoutExchange",true,true);
        return new FanoutExchange("TestFanoutExchange",true,false);
    }
 
    //绑定  将队列和交换机绑定, 并设置用于匹配键:TestFanoutRouting
    @Bean
    Binding bindingFanout() {
        return BindingBuilder.bind(TestFanoutQueue()).to(TestFanoutExchange());
    }
}
    设置direct
@Configuration
public class DirectRabbitConfig {
 
    //队列 起名:TestDirectQueue
    @Bean
    public Queue TestDirectQueue() {
        // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
        // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
        //   return new Queue("TestDirectQueue",true,true,false);
 
        //一般设置一下队列的持久化就好,其余两个就是默认false
     System.out.println("queue init....");
     
        return new Queue("TestDirectQueue",true);
    }
 
    //Direct交换机 起名:TestDirectExchange
    @Bean
    DirectExchange TestDirectExchange() {
      //  return new DirectExchange("TestDirectExchange",true,true);
        return new DirectExchange("TestDirectExchange",true,false);
    }
 
    //绑定  将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting
    @Bean
    Binding bindingDirect() {
        return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
    }
 
    @Bean
    DirectExchange lonelyDirectExchange() {
        return new DirectExchange("lonelyDirectExchange");
    }
}
    设置topic
@Configuration
public class TopicRabbitConfig {
     // 队列 起名:TestTopicQueue
     @Bean
     public Queue TestTopicQueue() {
          // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
          // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
          // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
          // return new Queue("TestTopicQueue",true,true,false);
          // 一般设置一下队列的持久化就好,其余两个就是默认false
          System.out.println("queue init....");
          return new Queue("TestTopicQueue", true);
     }
     // Topic交换机 起名:TestTopicExchange
     @Bean
     TopicExchange TestTopicExchange() {
          return new TopicExchange("TestTopicExchange", true, false);
     }
     // 绑定 将队列和交换机绑定, 并设置用于匹配键:TestTopicRouting
     @Bean
     Binding bindingTopic() {
          return BindingBuilder.bind(TestTopicQueue()).to(TestTopicExchange()).with("message.*");
     }
}
4、编写生产者
@RestController
public class SendMessageController {
 
    @Autowired
    RabbitTemplate rabbitTemplate;  //使用RabbitTemplate,这提供了接收/发送等等方法
 
    @GetMapping("/sendDirectMessage")
    public String sendDirectMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "test message, hello!";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map<String,Object> map=new HashMap<>();
        map.put("messageId",messageId);
        map.put("messageData",messageData);
        map.put("createTime",createTime);
        //将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
        rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", map);
        return "ok";
    }
   
   
    @GetMapping("/sendFanoutMessage")
    public String sendFanoutMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "test message, hello!";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map<String,Object> map=new HashMap<>();
        map.put("messageId",messageId);
        map.put("messageData",messageData);
        map.put("createTime",createTime);
        //将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
        rabbitTemplate.convertAndSend("TestFanoutExchange", "", map);
        return "ok";
    }
 
   
    @GetMapping("/sendTopicMessage")
    public String sendTopicMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "test message, hello!";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map<String,Object> map=new HashMap<>();
        map.put("messageId",messageId);
        map.put("messageData",messageData);
        map.put("createTime",createTime);
        //将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
        rabbitTemplate.convertAndSend("TestTopicExchange", "message."+messageId, map);
        return "ok";
    }
 
}
5、编写消费者
@Component
@RabbitListener(queues = "TestDirectQueue")//监听的队列名称 TestDirectQueue
public class ConsumerDirect {
 
    @RabbitHandler
    public void process(Object testMessage) {
        System.out.println("DirectReceiver消费者收到消息  : " + testMessage.toString());
    }
 
}
 

posted on 2020-12-22 16:40  李雷  阅读(96)  评论(0编辑  收藏  举报

导航