RabbitMQ

MQ基本概念

  1. MQ为消息队列,存储消息的中间件
  2. 分布式系统通信的两种方式:(1)直接远程调用。(2)借助第三方 完成通信
  3. 发送方-----消息中间件---->接收方

MQ的优势

1. 应用解耦

在新增额外系统、删除系统、子系统发生错误,都不会影响订单系统,只需要对MQ发送消息即可实现
在这里插入图片描述

在这里插入图片描述

2. 异步提速

在订单系统和数据库系统之间添加消息MQ,订单系统从MQ中获取数据,子系统将数据可以提前提交至MQ中。
在这里插入图片描述
在这里插入图片描述

3. 削峰填谷

  1. 瞬时消息请求在增多,承受不住请求的压力

  1. 引入消息队列MQ,将请求挂靠在MQ中,让MQ承担请求的压力

在这里插入图片描述
在这里插入图片描述

总结

  • 应用解耦:提高了系统的容错性可维护性
  • 异步提速:提升了系统的吞吐量用户体验度
  • 削峰填谷:提升了系统的稳定性

MQ的劣势

1. 系统可用性降低

添加了MQ之后,增加了系统的组件,增加了系统出错的概率:MQ宕机了,系统也会受影响
在这里插入图片描述

2. 系统复杂性提高

MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?

3. 一致性问题

A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理
失败。如何保证消息数据处理的一致性?

总结

  • 生产者不需要从消费者处获取反馈,则可以用MQ
  • 解耦、提速、削峰的收益,超过维护MQ的成本,可以使用。

常见MQ产品

目前业界有很多的 MQ 产品,例如 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,也有直接使用 Redis 充当消息队列的案例,而这些消息队列产品,各有侧重,在实际选型时,需要结合自身需求及 MQ 产品特征,综合考虑。
在这里插入图片描述

RabbitMQ简介

  • RabbitMQ是基于AMQP协议使用的Erlang语言开发的消息队列产品。
  • AMQP(高级消息队列协议)类比HTTP。
连接
连接
生产者
中介
消费者

Connection:生产者/消费者与broker之间的TCP连接;
broker:接收和分发消息的中介,就是消息服务器;
Virtual host:每一个用户有一个host,类似私人账户一样;
channel:每个channel之间相互隔离,可以减少TCP connection的开销;
exchange:根据分发规则,将message分发到不同的queue中;
Queue:装在message,被消费者取走;
Binding:exchange和queue之间的虚拟连接。
在这里插入图片描述

RabbitMQ六种工作模式

1. 简单模式 “Hello World”

RabbitMQ 是一个消息代理: 它接受和转发消息。你可以把它想象成一个邮局: 当你把你想要投递的邮件放在一个邮箱里时,你可以确定信件的承运人最终会把邮件投递给你的收件人。在这个类比中,RabbitMQ 是一个邮箱、一个邮局和一个信件载体。
在这里插入图片描述
P是一个生产者,红色部分是一个消息缓存队列,C是消费者

Producer

public class Producer{
  private final static String QUEUE_NAME = "hello";
  public static void main(String[] argv) throws Exception {
  		//1. 创建连接工厂
  		ConnectionFactory factory = new ConnectionFactory();
  		//2. 设置参数
		factory.setHost("localhost"); 	//设置主机ip

		//3. 创建连接
		Connection connection = factory.newConnection();
		//4. 创建Channel
		Channel channel = connection.createChannel()//5. 创建消息队列
    	/*
     		参数1: 队列名
     		参数2:durable,是否持久化
     		参数3:exclusive,是否独占,只能有一个消费者监听队列,一般为false
     		参数4:autodelete,是否自动删除队列,没有消费者时自动删除
     		参数5:参数
    	*/
    	channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    	String message = "Hello World!";
    	//6. 发送消息
     	/*
			参数1: exchange,交换机,默认为“”
			参数2:routingkey,路由名,简单模式下,交换机与路由名相互绑定,为队列名称QUEUE_NAME
			参数3:props,配置信息
			参数4:body,message 消息内容,字节信息
		*/
    	channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
    	System.out.println(" [x] Sent '" + message + "'");
    	
    	//7. 关闭资源,可以选择性关闭
    	channel.close();
    	connection.close();
  }
}

consumer

public class Consumer{

  private final static String QUEUE_NAME = "hello";

 public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        //接收消息
        /*
        	回调方法	,当收到消息之后,会自动执行
        	参数:
        		参数1:consumerTag,消息标识
        		参数2:envelope,交换机,routingkey名
        		参数3:properties,配置信息
        		参数4:message,消息数据
        */
        Consumer deliverCallbackConsumer = new DefaultConsumer(channel){
          	@override
        	public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] message){
				System.out.println(“consumerTag:”+consumerTag);
				System.out.println(“exchange:”+envelope.getExchange());
				System.out.println(“rountingkey:”+envelope.getrountingkey());
				System.out.println(“properties:”+properties);
				System.out.println(“message:”+new String(message));
			}
        } 
        /*
			参数1: routingkey,路由名,简单模式下,交换机与路由名相互绑定,为队列名称QUEUE_NAME
			参数2:autoAck,自动确认
			参数3:callback,回调对象,deliverCallback
			参数4:body,message 消息内容,字节信息
		*/
        channel.basicConsume(QUEUE_NAME, true, deliverCallbackConsumer);
        
        //消费者不能关闭资源,因为需要不间断的监听消息队列
    }
}

2. work queues模式

工作队列(又名: 任务队列)背后的主要思想是避免立即执行资源密集型任务,并且不得不等待它完成。相反,我们将任务安排在以后完成。我们将任务封装为消息并将其发送到队列。在后台运行的辅助进程将弹出任务并最终执行作业。当您运行许多工作线程时,任务将在它们之间共享。

  • C1、C2是相互竞争资源的关系
  • 应用场景:适用任务过重过多情况,提升任务处理的速度
  • 同时启动多个consumer,然后启动producer,发送message,观察消息消费情况


Producer

public class Producer{
  private final static String QUEUE_NAME = "hello";
  public static void main(String[] argv) throws Exception {
  		//1. 创建连接工厂
  		ConnectionFactory factory = new ConnectionFactory();
  		//2. 设置参数
		factory.setHost("localhost"); 	//设置主机ip

		//3. 创建连接
		Connection connection = factory.newConnection();
		//4. 创建Channel
		Channel channel = connection.createChannel()//5. 创建消息队列
    	/*
     		参数1: 队列名
     		参数2:durable,是否持久化
     		参数3:exclusive,是否独占,只能有一个消费者监听队列,一般为false
     		参数4:autodelete,是否自动删除队列,没有消费者时自动删除
     		参数5:参数
    	*/
    	channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    	//6. 发送10条消息
    	for( int i= 0 ; i< 10; i++){
    		String message = i + "Hello World!";
     		/*
				参数1: exchange,交换机,默认为“”
				参数2:routingkey,路由名,简单模式下,交换机与路由名相互绑定,为队列名称QUEUE_NAME
				参数3:props,配置信息
				参数4:body,message 消息内容,字节信息
			*/
    		channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
    	}
    	System.out.println(" [x] Sent '" + message + "'");
    	
    	//7. 关闭资源,可以选择性关闭
    	//channel.close();
    	//connection.close();
  }
}

consumer1

public class Consumer1{

  private final static String QUEUE_NAME = "hello";

 public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        //接收消息
        /*
        	回调方法	,当收到消息之后,会自动执行
        	参数:
        		参数1:consumerTag,消息标识
        		参数2:envelope,交换机,routingkey名
        		参数3:properties,配置信息
        		参数4:message,消息数据
        */
        Consumer deliverCallbackConsumer = new DefaultConsumer(channel){
          	@override
        	public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] message){
				System.out.println(“message:”+new String(message));
			}
        } 
        /*
			参数1: routingkey,路由名,简单模式下,交换机与路由名相互绑定,为队列名称QUEUE_NAME
			参数2:autoAck,自动确认
			参数3:callback,回调对象,deliverCallback
			参数4:body,message 消息内容,字节信息
		*/
        channel.basicConsume(QUEUE_NAME, true, deliverCallbackConsumer);
        
        //消费者不能关闭资源,因为需要不间断的监听消息队列
    }
}

consumer2

public class Consumer2{

  private final static String QUEUE_NAME = "hello";

 public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        //接收消息
        /*
        	回调方法	,当收到消息之后,会自动执行
        	参数:
        		参数1:consumerTag,消息标识
        		参数2:envelope,交换机,routingkey名
        		参数3:properties,配置信息
        		参数4:message,消息数据
        */
        Consumer deliverCallbackConsumer = new DefaultConsumer(channel){
          	@override
        	public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] message){
				System.out.println(“message:”+new String(message));
			}
        } 
        /*
			参数1: routingkey,路由名,简单模式下,交换机与路由名相互绑定,为队列名称QUEUE_NAME
			参数2:autoAck,自动确认
			参数3:callback,回调对象,deliverCallback
			参数4:body,message 消息内容,字节信息
		*/
        channel.basicConsume(QUEUE_NAME, true, deliverCallbackConsumer);
        
        //消费者不能关闭资源,因为需要不间断的监听消息队列
    }
}

================================================================================================

3. Publish/Subscribe模式

producer发送一条消息,两个消费者都可以收到
在这里插入图片描述

  • producer将消息发送给X(exchange),不直接发送给队列。
  • X:交换机。转发消息,有三种处理方式:
    • Fanout:广播,将消息交给所有与其绑定的队列
    • Direct:定向,将消息交给指定的routingkey队列
    • Topic:通配符,将消息交给符合的routing pattern的队列
  • X,交换机,只负责消息的转发,不具备存储能力,如果没有队列接收,消息将被丢失。

Producer

public class Producer{
  
  public static void main(String[] argv) throws Exception {
  		//1. 创建连接工厂
  		ConnectionFactory factory = new ConnectionFactory();
  		//2. 设置参数
		factory.setHost("localhost"); 	//设置主机ip

		//3. 创建连接
		Connection connection = factory.newConnection();
		//4. 创建Channel
		Channel channel = connection.createChannel()//5. 创建交换机
    	String exchange_name = "exc_name"; 
    	/*
    		交换机参数列表:
    			参数1:交换机名称
    			参数2:交换机类型,四种
    			参数3:是否持久化
    			参数4:自动删除
    			参数5:内部使用
    			参数6:参数
    		*/
		channel.exchangeDeclare(exchange_name ,'fanout',true,false,false,null);
		
		//6. 创建队列
		private String queue_name1 = "hello1";
		private String queue_name2 = "hello2";
		channel.exchangeQueue(queue_name1 ,true,false,false,null);
		channel.exchangeQueue(queue_name2 ,true,false,false,null);
		
		//7. 绑定交换机与队列
		/*
			绑定参数列表:
				参数1:队列名
				参数2:交换机名
				参数3:routingkey,绑定规则,默认为fonout,“”
		*/
		channel.queueBind(queue_name1 ,exchange_name ,"");
		channel.queueBind(queue_name2 ,exchange_name ,"");

		//8. 发送消息
		String message = "publish/subscribe";
		channel.basicPublish(exchange_name,"",null,message.getBytes() )

		//9. 释放资源
		channel.close();
		channel.connection();
	}
}

consumer1

public class Consumer1{
 public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        //接收消息
        Consumer deliverCallbackConsumer = new DefaultConsumer(channel){
          	@override
        	public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] message){
				System.out.println(“message:”+new String(message));
			}
        } 
        //两个消费者,监听自己的队列,绑定对应的队列名
        channel.basicConsume(queue_name1, true, deliverCallbackConsumer);
        
        //消费者不能关闭资源,因为需要不间断的监听消息队列
    }
}

==============================================================================

4. Routing模式

  • 队列与路由指定routingkey绑定
  • producer向exchange转发消息的时候,也必须指定routingkey
  • exchange与消息队列不在固定绑定,而是根据消息的routingkey进行判断,队列的routingkey与消息的routingkey一致,才可以接收到消息。

在这里插入图片描述
Producer

public class Producer{
  
  public static void main(String[] argv) throws Exception {
  		//1. 创建连接工厂
  		ConnectionFactory factory = new ConnectionFactory();
  		//2. 设置参数
		factory.setHost("localhost"); 	//设置主机ip

		//3. 创建连接
		Connection connection = factory.newConnection();
		//4. 创建Channel
		Channel channel = connection.createChannel()//5. 创建交换机
    	String exchange_name = "exc_name"; 
		channel.exchangeDeclare(exchange_name ,BuiltinExchangeType.DIRECT,true,false,false,null);
		
		//6. 创建队列
		private String queue_name1 = "hello1";
		private String queue_name2 = "hello2";
		channel.exchangeQueue(queue_name1 ,true,false,false,null);
		channel.exchangeQueue(queue_name2 ,true,false,false,null);
		
		//7. 绑定交换机与队列
			//队列1绑定一个routingkey
		channel.queueBind(queue_name1 ,exchange_name ,"error");
			//队列2绑定三个routingkey
		channel.queueBind(queue_name2 ,exchange_name ,"info");
		channel.queueBind(queue_name2 ,exchange_name ,"error");
		channel.queueBind(queue_name2 ,exchange_name ,"warning");

		//8. 发送消息
		String message = "publish/subscribe";
			//发送一个routingkey=="info"的消息
		channel.basicPublish(exchange_name,"info",null,message.getBytes() )

		//9. 释放资源
		channel.close();
		channel.connection();
	}
}

consumer1
consumer与publish/subscribe一样。。。。。。

============================================================================

5. Topics通配符模式

与routing模式类似,将routingkey改进为通配符模式,通配符之间用“ . ”隔开

  • *,代表一个单词
  • #,代表多个单词

在这里插入图片描述

============================================================================

6. RPC远程调用模式(不介绍)

============================================================================

SpringBoot整合RabbitMQ

生产者

  • 创建生产者SpringBoot工程
  • 引入start,依赖坐标
<dependency> 	
	<groupId>org.springframework.boot</groupId>
   	<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 编写yml配置,基本信息配置
# 配置ip,端口,username ,password
spring:
	rabbitmq:
		host: loscakhost
		port: 5672
		username: ....
		password: ****
		virtual-host: /
  • 定义交换机,队列以及绑定关系的配置类
@Configuration
public class RabbitMQConfig{

	public static final String EXCHANGE_NAME = "EXCHANGE_NAME";
	public static final String QUEUE_NAME = "QUEUE_NAME";
	//1. 创建交换机
	@Bean("createExchage")
	public Exchage createExchage(){
		return ExchageBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
	}

	//2. 创建队列
	@Bean("createQueue")
	public Queue createQueue(){
		return QueueBuilder..durable(QUEUE_NAME).build();
	}
	
	//3. 绑定队列和交换机
	@Bean
	public Binding bindQueueExchange(Queue queue,Exchage exchange){
		return BindingBuilder.bind(queue).to(exchange).with("*.*");
	}
}
  • 注入RabbitTemplate,调用方法,完成消息发送
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProducerTest{

	//1. 注入RabbitTemplate 
	@AutoWired
	private RabbitTemplate rabbitTempate;
	
	@Test
	public void testSend(){
		//参数列表(exchange,routingkey,message)
		rabbitTempate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "xxx.xxx", “hello world” );
	}
	
}

消费者

创建消费者SpringBoot工程
引入start,依赖坐标

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

编写yml配置,基本信息配置

定义监听类,使用@RabbitListener注解完成队列监听

@Component
public class RabbitMQListener{

	// @RabbitListener完成消息接收
	@RabbitListener(queue = QUEUE_NAME )
	public void listenerQueue(Message message){
		System.out.println(message.getBytes());
	}
}

============================================================================

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值