RabbitMQ

一、MQ

1、什么是MQ

MQ全称Message Queue(消息队列),多用于分布式系统之间消息的传递,可作为两个系统间远程调用的中间件,保存两系统传输的消息。

2、MQ的优势

  • 应用解耦
    加入MQ后,应用间不再是直接调用,而是通过MQ进行通信,降低了耦合度,提升了容错性和可维护性。

  • 异步处理
    某些任务如果同步处理会导致系统的响应时间很慢,因为同步就需要等到所有被调用的系统都完成了操作,该任务才能完成。如果这些任务不需要进行同步处理的话,可以使用MQ进行异步处理,这样系统就只要和MQ完成通信就可响应,其他进行异步处理就行。

  • 削峰填谷
    当某一时间系统被大量访问时,每秒并发量可能会超过数据库所能承受的范围,从而导致系统宕机。使用MQ则可以把消息先存起来,再由系统根据自己的消费能力来消费。这样把高峰期的消息积压到MQ里,高峰就被“削”掉了。高峰期过后,由于还有消息堆积在MQ里,系统会在这之后持续消费这些消息,知道消息被消费完,这就是“填谷”。

3、MQ的劣势

  • 系统的可用性降低、系统的复杂度提高
    要想引入MQ作为中间件,系统就要引入外部依赖,引入的外部依赖越多,系统的稳定性越差,系统也越复杂。
  • 一致性问题
    MQ的消息会被多个不同的系统消费,如何确保这些消息的一致性是个需要思考的问题。

二、RabbitMQ

官网:https://www.rabbitmq.com/

1、什么是RabbitMQ

RabbitMQ是一种MQ产品,是基于 AMQP 标准开发的,该协议不受开发语言的限制。

RabbitMQ提供了6种模式:简单模式,work模式,Publish/Subscribe发布与订阅模式,Routing路由
模式,Topics主题模式,RPC远程调用模式。
参考官网:https://www.rabbitmq.com/getstarted.html

2、简单模式

在这里插入图片描述

使用步骤如下:

2.1 导入相关依赖

maven项目导入这个

<dependency>
 	<groupId>com.rabbitmq</groupId>
 	<artifactId>amqp-client</artifactId>      
 	<version>5.6.0</version>
</dependency>

2.2 编写生产者

public class Send {
	//定义队列的名字
  	private final static String QUEUE_NAME = "hello";
  	
  	public static void main(String[] argv) throws Exception {
	  	//创建连接工厂
	    ConnectionFactory connectionFactory = new ConnectionFactory();    
	    //主机地址;默认为localhost,RabbitMQ不是安装在本地的则写安装主机的地址,如192.168.133.133
	    connectionFactory.setHost("localhost");
	    //连接端口;默认为5672
	    connectionFactory.setPort(5672);
	    //虚拟主机名称;默认为/
	    connectionFactory.setVirtualHost("/send-demo");
	    //连接用户名;默认为guest
	    connectionFactory.setUsername("guest");
	    //连接密码;默认为guest
	    connectionFactory.setPassword("guest");
	    
	    //创建连接
	    Connection connection = connectionFactory.newConnection();
	
		//创建频道
	    Channel channel = connection.createChannel();
	    
	    //声明(创建)队列
	    /**
	    *参数1:队列名称
	    *参数2:是否定义持久化队列(关闭RabbitMQ时,数据是否持久化到磁盘)
	    *参数3:是否独占本次连接(只能有一个消费者监听这个队列)
	    *参数4:是否在不使用的时候自动删除队列(当没有消费者时,自动删除)
	    *参数5:队列其它参数
	    */
	    channel.queueDeclare(QUEUE_NAME,true,false,false,null);
	    
	    //要发送的信息
	    String message="Hello World!";
	    
	    /**将消息发送到队列中
	    *参数1:交换机名称,如果没有指定则传入空字符串使用默认DefaultExchage
	    *参数2:路由key,简单模式可以传递队列名称
	    *参数3:消息其它属性
	    *参数4:消息内容
	    */
	    channel.basicPublish("",QUEUE_NAME,null,message.getBytes());    
	    
	    System.out.println("已发送消息:"+message);

		//关闭资源
	    channel.close();
	    connection.close();
	}
}

2.3 编写消费者

代码基本和生产者的一样,相同部分可自行提取出来作为工具类。

public class Recv {
	//定义队列名称
	private final static String QUEUE_NAME = "hello";

  	public static void main(String[] argv) throws Exception {
  		//下面几点代码和上面是一样的,这里只列出步骤,不再重复
	    //1、创建连接工厂
	    //2、在工厂里设置连接属性
	    //3、从工厂里获取一个连接
	    //4、在连接中创建一个频道
	    //频道中声明一个队列
	    channel.queueDeclare(QUEUE_NAME, false, false, false, null);
	    System.out.println("等待接收消息...");
		
		//创建消费者;并设置消息处理
    DefaultConsumer consumer = new DefaultConsumer(channel){
	      @Override
	      /**
	      *consumerTag消息者标签,在channel.basicConsume里可以指定
	      *envelope消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
	      *properties属性信息
	      *body消息
	      */
	      public void handleDelivery(String consumerTag,Envelope envelope,
	AMQP.BasicProperties properties,byte[] body) throws IOException{
	        //路由key
	        System.out.println("路由key为:"+envelope.getRoutingKey());
	        //交换机
	        System.out.println("交换机为:"+envelope.getExchange());
	        //消息id
	        System.out.println("消息id为:"+envelope.getDeliveryTag());
	        //收到的消息
	        System.out.println("接收到的消息为:"+newString(body,"utf-8"));
	     }
    };
		/监听消息
	    /**
	    *参数1:队列名称
	    *参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
	    *参数3:消息接收到后回调
	    */
    	channel.basicConsume(QUEUE_NAME,true,consumer);


//另一种接收消息的方式
//    //定义消费成功的回调函数
//    DeliverCallback deliverCallback = (consumerTag, delivery) -> {
//        String message = new String(delivery.getBody(), "UTF-8");
//        System.out.println("接收到消息:'" + message + "'");
//    };
//    //监听消息
//    /**
//     *参数1:队列名称
//     *参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
//     *参数3:消息接收成功的回调函数
//     *参数4:消息接收失败的回调函数
//     */
//    channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });


		//消费者要持续监听消息,所有不关闭资源
//	    channel.close();
//	    connection.close();
  }
}

3、工作队列模式

在这里插入图片描述

  • 一个生产者对应多个消费者,与简单模式的区别只是消费者多了,多个消费者共同消费同一个队列中的消息。
  • 多个消费者之间是竞争关系,通过竞争来决定某个消息由谁来执行。

官网案例:https://www.rabbitmq.com/tutorials/tutorial-two-java.html

4、Exchange交换机

在这里插入图片描述

  • X:即Exchange,交换机,用于转发消息。
  • 交换机不具备存储功能,如果没有队列绑定到交换机,消息将丢失。
  • 交换机有direct、topic、headers 和fanout四种模式
    • direct:定向,把消息交给符合指定routing key 的队列
    • topic:通配符,把消息交给符合routing pattern(路由模式)的队列
    • fanout:广播,将消息交给所有绑定到交换机的队列

5、Publish/Subscribe发布与订阅模式

在这里插入图片描述

  • 交换机把消息发送给所有队列
  • 每一个队列都收到一样的消息,每个队列都处理一样的消息

官网例子:https://www.rabbitmq.com/tutorials/tutorial-three-java.html

Publish/Subscribe发布与订阅模式的交换机是:fanout模式

5.1 生产者

大部分代码和上面一样

public class Producer {

	//定义交换机
  	private static final String EXCHANGE_NAME = "fanout_exchange";

  	public static void main(String[] argv) throws Exception {
  		//下面几点代码和上面是一样的,这里只列出步骤,不再重复
	    //1、创建连接工厂
	    //2、在工厂里设置连接属性
	    //3、从工厂里获取一个连接
	    //4、在连接中创建一个频道
	    
		/**
	    *声明交换机
	    *参数1:交换机名称
	    *参数2:交换机类型,fanout、topic、direct、headers
	    */
		channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

		for(int i=1;i<=10;i++){
			//要发送的消息
	        String message = "发布订阅模式,发送消息"+i;
	        /**向交换机发送消息
	      	*参数1:交换机名称,如果没有指定则使用默认DefaultExchage
	      	*参数2:路由key,简单模式可以传递队列名称
	      	*参数3:消息其它属性
	     	*参数4:消息内容
	      	*/
	       	channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
	       	System.out.println("发送消息:'" + message + "'");
       	}
       	//关闭资源
	    channel.close();
	    connection.close();
   }
}

5.2 消费者1

public class Consumer1{
	//定义交换机
  	private static final String EXCHANGE_NAME = "fanout_exchange";
	//定义队列
	private final static String QUEUE_NAME_1 = "fanout_queue_1";

	public static void main(String[] argv) throws Exception {
  		//下面几点代码和上面是一样的,这里只列出步骤,不再重复
	    //1、创建连接工厂
	    //2、在工厂里设置连接属性
	    //3、从工厂里获取一个连接
	    //4、在连接中创建一个频道

		/**
	    *声明交换机
	    *参数1:交换机名称
	    *参数2:交换机类型,fanout、topic、direct、headers
	    */
		channel.exchangeDeclare(EXCHANGE_NAME, "fanout");		
	
	    /**声明(创建)队列
	    *参数1:队列名称
	    *参数2:是否定义持久化队列
	    *参数3:是否独占本次连接
	    *参数4:是否在不使用的时候自动删除队列
	    *参数5:队列其它参数
	    */
	    channel.queueDeclare(QUEUE_NAME_1,true,false,false,null);
		
		 /**队列绑定交换机
	    *参数1:队列名称
	    *参数2:交换机名称
	    *参数3:routingKey,路由模式会说到,这里传空字符串
	    */
    	channel.queueBind(QUEUE_NAME_1, EXCHANGE_NAME, "");

		System.out.println("等待接收消息...");
		//成功接收消息的回调函数
		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
	        String message = new String(delivery.getBody(), "UTF-8");
	        System.out.println("成功接收消息:'" + message + "'");
	    };
	    //监听队列QUEUE_NAME_1的消息
	    channel.basicConsume(QUEUE_NAME_1, true, deliverCallback, consumerTag -> { });
}

5.3 消费者2

public class Consumer2{
	//定义交换机
  	private static final String EXCHANGE_NAME = "fanout_exchange";
	//定义队列
	private final static String QUEUE_NAME_2 = "fanout_queue_2";

	public static void main(String[] argv) throws Exception {
  		//下面几点代码和上面是一样的,这里只列出步骤,不再重复
	    //1、创建连接工厂
	    //2、在工厂里设置连接属性
	    //3、从工厂里获取一个连接
	    //4、在连接中创建一个频道

		/**
	    *声明交换机
	    *参数1:交换机名称
	    *参数2:交换机类型,fanout、topic、direct、headers
	    */
		channel.exchangeDeclare(EXCHANGE_NAME, "fanout");		
	
	    /**声明(创建)队列
	    *参数1:队列名称
	    *参数2:是否定义持久化队列
	    *参数3:是否独占本次连接
	    *参数4:是否在不使用的时候自动删除队列
	    *参数5:队列其它参数
	    */
	    channel.queueDeclare(QUEUE_NAME_2,true,false,false,null);
		
		 /**队列绑定交换机
	    *参数1:队列名称
	    *参数2:交换机名称
	    *参数3:routingKey,路由模式会说到,这里传空字符串
	    */
    	channel.queueBind(QUEUE_NAME_2, EXCHANGE_NAME, "");

		System.out.println("等待接收消息...");
		//成功接收消息的回调函数
		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
	        String message = new String(delivery.getBody(), "UTF-8");
	        System.out.println("成功接收消息:'" + message + "'");
	    };
	    //监听队列QUEUE_NAME_2的消息
	    channel.basicConsume(QUEUE_NAME_2, true, deliverCallback, consumerTag -> { });
}

6、Routing路由模式

在这里插入图片描述

  • 队列与交换机的绑定要指定一个RoutingKey
  • 生产者向交换机发送消息时也要指定RoutingKey
  • 交换机根据RoutingKey来决定把消息发送给哪个队列

官网例子:https://www.rabbitmq.com/tutorials/tutorial-four-java.html

Routing路由模式的交换机是:direct模式

6.1 生产者

public class RoutingProducer {

	//定义交换机
  	private static final String EXCHANGE_NAME = "direct_exchange";

  	public static void main(String[] argv) throws Exception {
  		//下面几点代码和上面是一样的,这里只列出步骤,不再重复
	    //1、创建连接工厂
	    //2、在工厂里设置连接属性
	    //3、从工厂里获取一个连接
	    //4、在连接中创建一个频道
	    
		/**
	    *声明交换机
	    *参数1:交换机名称
	    *参数2:交换机类型,fanout、topic、direct、headers
	    */
		channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

		//要发送的消息
        String message = "这个是向'routingKey_1'发送的消息";
        /**向交换机发送消息
      	*参数1:交换机名称,如果没有指定则使用默认DefaultExchage
      	*参数2:路由key,简单模式可以传递队列名称
      	*参数3:消息其它属性
     	*参数4:消息内容
      	*/
       	channel.basicPublish(EXCHANGE_NAME, "routingKey_1", null, message.getBytes("UTF-8"));
       	System.out.println("向路由key为'routingKey_1'的队列发送了消息:'" + message + "'");

		//发送的消息
        String message = "这个是向'routingKey_2'发送的消息";
        //向交换机发送消息
       	channel.basicPublish(EXCHANGE_NAME, "routingKey_2", null, message.getBytes("UTF-8"));
       	System.out.println("向路由key为'routingKey_2'的队列发送了消息:'" + message + "'");
       	
       	//关闭资源
	    channel.close();
	    connection.close();
   }
}

6.2 消费者1

public class RoutingConsumer1{
	//定义交换机
  	private static final String EXCHANGE_NAME = "direct_exchange";
	//定义队列
	private final static String QUEUE_NAME_1 = "direct_queue_1";

	public static void main(String[] argv) throws Exception {
  		//下面几点代码和上面是一样的,这里只列出步骤,不再重复
	    //1、创建连接工厂
	    //2、在工厂里设置连接属性
	    //3、从工厂里获取一个连接
	    //4、在连接中创建一个频道

		/**
	    *声明交换机
	    *参数1:交换机名称
	    *参数2:交换机类型,fanout、topic、direct、headers
	    */
		channel.exchangeDeclare(EXCHANGE_NAME, "direct");		
	
	    /**声明(创建)队列
	    *参数1:队列名称
	    *参数2:是否定义持久化队列
	    *参数3:是否独占本次连接
	    *参数4:是否在不使用的时候自动删除队列
	    *参数5:队列其它参数
	    */
	    channel.queueDeclare(QUEUE_NAME_1,true,false,false,null);
		
		 /**队列绑定交换机
	    *参数1:队列名称
	    *参数2:交换机名称
	    *参数3:routingKey,这里绑定routingKey_1,就能收到生产者发送到routingKey为"routingKey_1"的队列的消息
	    */
    	channel.queueBind(QUEUE_NAME_1, EXCHANGE_NAME, "routingKey_1");

		System.out.println("等待接收消息...");
		//成功接收消息的回调函数
		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
	        String message = new String(delivery.getBody(), "UTF-8");
	        System.out.println("成功接收routingKey为'"+ delivery.getEnvelope().getRoutingKey() + "'的消息:" + message);
	    };
	    //监听队列QUEUE_NAME_1的消息
	    channel.basicConsume(QUEUE_NAME_1, true, deliverCallback, consumerTag -> { });
}

6.3 消费者2

public class RoutingConsumer1{
	//定义交换机
  	private static final String EXCHANGE_NAME = "direct_exchange";
	//定义队列
	private final static String QUEUE_NAME_2 = "direct_queue_2";

	public static void main(String[] argv) throws Exception {
  		//下面几点代码和上面是一样的,这里只列出步骤,不再重复
	    //1、创建连接工厂
	    //2、在工厂里设置连接属性
	    //3、从工厂里获取一个连接
	    //4、在连接中创建一个频道

		/**
	    *声明交换机
	    */
		channel.exchangeDeclare(EXCHANGE_NAME, "direct");		
	
	    /**声明(创建)队列
	    */
	    channel.queueDeclare(QUEUE_NAME_2,true,false,false,null);
		
		 /**队列绑定交换机,并绑定routingKey_2
	    */
    	channel.queueBind(QUEUE_NAME_1, EXCHANGE_NAME, "routingKey_2");

		System.out.println("等待接收消息...");
		//成功接收消息的回调函数
		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
	        String message = new String(delivery.getBody(), "UTF-8");
	        System.out.println("成功接收routingKey为'"+ delivery.getEnvelope().getRoutingKey() + "'的消息:" + message);
	    };
	    //监听队列QUEUE_NAME_1的消息
	    channel.basicConsume(QUEUE_NAME_2, true, deliverCallback, consumerTag -> { });
}

7、Topics通配符模式

在这里插入图片描述

  • 该模式与路由模式一样,交换机是根据RoutingKey来发送消息
  • 但该模式的RoutingKey可以使用通配符
  • 通配符规则:
    • *:可以只替换一个单词。
    • #:可以代替零个或多个单词

例如:
demo.# : 够匹配 demo.abc 或者 demo.abc.efg 等,后面还能接多个单词
demo.* : 只能匹配 demo.abc ,后面只能接一个单词

通配符模式的交换机模式为: topic

官网例子:https://www.rabbitmq.com/tutorials/tutorial-five-java.html
用法和路由模式差不多,只是模式改成“topic”,路由key可以使用通配符。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值