消息中间件之RabbitMQ

1.消息中间件简介

1:什么是消息中间件:

消息队列中间件(Message Queue Middleware,简称MQ)是指利用高可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成,通过提供消息传递和消息排队模型。他可以在分布式环境下扩展进程间的通信。

目前开源的消息中间件有很多,比较流行的有RabbitMQ,Kafka,ActiveMQ,RocketMQ等,面向消息的中间件(简称为MOM,Message Oriented Middleware)提供了以松散耦合的灵活方式集成应用程序通信的一种机制,他们提供了基于存储和转发的应用程序之间的异步数据发送,即应用程序彼此不直接通信,而是作为中介的消息中间件通信,消息中间件提供了有保证的消息发送,应用程序开发人员无须了解远程过程用(RPC)和网络通信协议的细节。

2:消息中间件的作用

异步:在很多时候应用不想也不需要立即处理消息,消息中间件提供了异步处理机制,允许应用把一些消息放入消息中间件中,但并不立即处理它,在之后需要的时候在慢慢处理。

解耦:在项目启动之初来预测将来碰到什么需求是极其困难得,消息中间件在处理过程中间件插入了一个隐含的,基于数据的接口层,两边的处理过程都要实现这一接口,这允许你独立的扩展或修改两边的处理过程,只要确保他们遵守同样的接口约束即可,

削峰:在访问量剧增的情况下,应用仍然需要发挥作用,但是这样的突发流量并不常见,如果以能处理这类峰值为标准而投入资源,无疑是巨大的浪费,使用消息中间件能够使关键组件支撑突发访问压力,不会因为突发的超负荷请求儿完全崩溃。

冗余:有些情况下,处理数据的过程会失败,消息中间件可以把数据进行持久化直到他们已经被完全处理,通过这一方法规避了数据丢失风险,在把一个消息从消息中间件删除前,需要你的处理系统明确的指出该消息已经处理完成,从而确保你的数据被安全的保存直到你使用完毕。

3:JMS规范

JMS(JAVA Message Service)规范本质是API,java平台消息中间件的规范,java应用程序之间进行消息交换,并且通过标准的产生,发送,接受消息的接口简化企业应用的开发

4:AMQP规范

AMQP(Advanced Message Queuing Protocol)协议是一套开放标准,支持不同语言的不同产品

2.RabbitMQ组件

 

2.1:broker

消息中间件的服务节点:对RabbitMQ来说,一个RabbitMQBroker可以简单的看做一个RabbitMQ服务节点,或者RabbitMQ服务实例

2.2:交换器(重要)

RabbitMQ常用的交换器类型有,fanout,direct,topic,headers四种

  1. fanout:他会把所有发送到该交换器的消息,路由到所有与该交换器绑定的队列中;
  2. direct:把消息路由到那些BingingKey和RoutingKey完全匹配的队列中;
  3. topic:类似direct,但可以使用通配符
  4. headers:消息不根据路由键的匹配规则路由,而是根据发送的消息内容的headers属性进行匹配

2.3:rabbitMQ运行流程

生产者:

生产者发送过程:

1:生产者与Broker建立连接(Connection),开启信道(Channel)

2:生产者声明交换器(交换器类型,是否持久化,是否自动删除等)

3:生产者声明队列(是否持久化,是否排他,是否自动删除)

4:生产者通过路由键将交换器和队列绑定

5:生产者发送消息至Broker(携带路由键等)

6:交换器根据接收到的路由键以及交换器类型查找匹配的队列

7:找到,队列将消息存入相应队列中

8:找不到,则根据生产者的配置选择丢弃还是回退给生产者

9:关闭信道

10:关闭连接

 

 

消费者:

消费者接收消息过程:

1:消费者与Broker建立连接(Connection),开启信道(Channel)

2:消费者向Broker请求相应队列的消息,可能设置回调函数

3:等待broker回应并投递相应队列中的消息,接收消息

4:消费者确认(ack)接收到消息

5:RabbitMQ从队列中删除已经被确认的消息

6:关闭信道

7:关闭连接

3.交换器与队列

3.1交换器/交换机

自动删除必须有绑定和解绑的过程

ConnectionFactory factory = new ConnectionFactory();
		factory.setHost(IP_ADDRESS);
		factory.setPort(PORT);
		factory.setUsername("rabbitstudy");
		factory.setPassword("123456");
		Connection connection = factory.newConnection();//创建连接
		Channel channel = connection.createChannel();//创建信道
		//创建一个非持久化交换器,RabbitMQ重启后,交换器会消失:durable=true
		channel.exchangeDeclare(EXCHANGE_NAME, "direct" , true , false , false , null);
		//channel.exchangeDeclare(exchange, type, durable, autoDelete, internal, arguments)
	/*	exchange:交换器名称
		type:交换器的类型,常见的如fanout,direct,topic,headers
		durable:设置是否持久化
		autoDelete:设置是否自动删除
		internal:设置是否是内置的
		arguments:(Map<String,Object> args)其他结构化参数*/
		//创建队列
		channel.queueDeclare(QUEUE_NAME, true, false , false , null);
		//将交换器与队列通过路由键绑定
		channel.queueBind(QUEUE_NAME , EXCHANGE_NAME , ROUTING_KEY);
		
		String message = "Hello Exchange.";
		channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY,
				MessageProperties.PERSISTENT_TEXT_PLAIN,
				message.getBytes());
		//关闭资源
		channel.close();
		connection.close();

3.2队列

		Map<String, Object> args = new HashMap<String, Object>(); //一些参数
//		args.put("x-message-ttl", 5000); //定义消息过期时间,单位毫秒ms
//		args.put("x-expires", 10000); //定义队列过期时间,单位毫秒ms,当队列在指定时间内未被使用时,自动删除该队列。
//		args.put("x-max-length", 10); //定义队列最大长度,队列中消息最大个数
//		args.put("x-max-length-bytes", 500); //定义队列最大占用空间大小,单位B,不仅消息还包括标签等
//		args.put("x-dead-letter-exchange", EXCHANGE_NAME_DEAD); //当队列消息长度大于最大长度、或者过期的 等,将从队列中删除的消息推送到指定的交换器中(死信交换器)
//		args.put("x-dead-letter-routing-key", "test");
//		args.put("x-max-priority", 1);优先级队列
//		args.put("x-queue-mode", );先将消息保存到磁盘上,不放在内存中,当消费开始消费的时候才加载到内存中
		
		//创建队列
		channel.queueDeclare(QUEUE_NAME, true, false , false , null);
		//channel.queueDeclareNoWait(queue, durable, exclusive, autoDelete, arguments);
/*		queue:队列的名称
		durable:设置是否持久化
		exclusive:设置是否排他
		autoDelete:设置是否自动删除
		arguments:设置队列的下一个参数
*/
  • 不等服务器通知创建队列成功:
//创建队列
channel.queueDeleteNoWait(queue, ifUnused, ifEmpty);
  • 检测队列是否存在,如果不存在则抛出异常:
//检查队列是否存在
channel.queueDeclarePassive(QUEUE_NAME);
  • 删除队列
//删除队列
channel.queueDelete(queue);
channel.queueDelete(queue, ifUnused, ifEmpty);
channel.queueDeleteNoWait(queue, ifUnused, ifEmpty);
  • 清空队列
//清空队列
channel.queuePurge(QUEUE_NAME);

4.交换器与队列

交换器与队列绑定

        //将交换器与队列通过路由键绑定

  1. channel.queueBind(QUEUE_NAME , EXCHANGE_NAME , ROUTING_KEY);
  2.  channel.queueBind(queue, exchange, routingKey, arguments)
  3.  channel.queueBindNoWait(queue, exchange, routingKey, arguments);

接触绑定

  • channel.queueUnbind(queue, exchange, routingKey);
  • channel.queueUnbind(queue, exchange, routingKey, arguments);

5.交换器与交换器

交换器与交换器绑定

  • channel.exchangeBind(destination, source, routingKey);
  • channel.exchangeBind(destination, source, routingKey, arguments);
  • channel.exchangeBindNoWait(destination, source, routingKey, arguments);

5.1发送消息

客户端发送消息

channel.basicPublish(exchange, routingKey, props, body);
channel.basicPublish(exchange, routingKey, mandatory, props, body);
channel.basicPublish(exchange, routingKey, mandatory, immediate, props, body);
		

exchange:交换器的名称
routingKey:路由键值
props:消息的一些基本属性
body:消息体
mandatory/immediate:当消息无法路由到队列或者路由到的队列没有消费者时如何处理

6.消费消息

6.1推模式

	private static final String QUEUE_NAME = "demo.queue.queue"; //队列
	private static final String IP_ADDRESS="192.168.110.130";
	private static final int PORT=5672;
	
	public static void push() throws Exception {
		Address[] addresses = new Address[]{new Address(IP_ADDRESS, PORT)};
		ConnectionFactory factory = new ConnectionFactory();
		factory.setUsername("name");
		factory.setPassword("password");
		
		//这里的连接方式与生产者的略有不同,注意辨别区别
		Connection connection = factory.newConnection(addresses); //创建连接
		final Channel channel = connection.createChannel(); //创建信道
		channel.basicQos(2);
		Consumer consumer = new DefaultConsumer(channel){
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
                //处理消费的消息
				System.out.println("recv message:"+ new String(body));
				System.out.println("RoutingKey:"+envelope.getRoutingKey());
				System.out.println("ContentType:"+properties.getContentType());
				System.out.println("DeliveryTag:"+envelope.getDeliveryTag()); //消息在队列中的下标
				System.out.println("------------------------------");
				channel.basicAck(envelope.getDeliveryTag(), false);
			}
		};
		channel.basicConsume(QUEUE_NAME, false, consumer); //设置autoAck为false
		/*channel.basicConsume(queue, autoAck, callback);
		channel.basicConsume(queue, autoAck, arguments, callback);
		channel.basicConsume(queue, autoAck, consumerTag, callback)
		channel.basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, callback);
		*/
		
		//等待回调行数执行完毕之后,关闭资源
		channel.close();
		connection.close();
	}
  1. queue:队列名称
  2. autoAck:是否自动确认,false表示不自动确认
  3. arguments:设置消费的其他参数
  4. consumerTag:消费者标签,用来区分多个消费者
  5. noLocal:设置为true则表示不能将同一个connection中生产者的消费传送给这个Connection中的消费者
  6. exclusive:设置是否排他
  7. callback:设置消息的回调函数,用来处理RabbitMQ推送过来消息

6.1拉模式

	//拉模式
	public static void pull() throws Exception {
		Address[] addresses = new Address[]{new Address(IP_ADDRESS, PORT)};
		ConnectionFactory factory = new ConnectionFactory();
		factory.setUsername("rabbitstudy");
		factory.setPassword("123456");
		
		//这里的连接方式与生产者的略有不同,注意辨别区别
		Connection connection = factory.newConnection(addresses); //创建连接
		final Channel channel = connection.createChannel(); //创建信道
		channel.basicQos(10);
		//拉模式
		GetResponse basicGet = channel.basicGet(QUEUE_NAME, false);
		System.out.println(new String(basicGet.getBody()));
		
		channel.basicAck(basicGet.getEnvelope().getDeliveryTag(), true); //确认消费完成
		
		//等待回调行数执行完毕之后,关闭资源
//		channel.close();
//		connection.close();
	}

basicGet()方法没有其他重载方法,其中queue代表队列名称,如果设置autoAck为false,那么童谣需要调用cannel.basicAck来确认消息已被接受成功

7.死信队列

DLX,全称为Dead-Letter-Exchange,可以称为死信交换器,也有人称为死信邮箱,当消息在一个队列中变为死信(deadmeassage)之后,他能被重新发送到另一个交换器中,这个交换器就是DLX,绑定DLX的队列就称为死信队列

消息变为死信一般由于以下几种情况

  1. 消息被拒绝,并且设置requeue参数为false
  2. 消息过期
  3. 队列达到最大长度

DLX也是一个正常交换器,和一般的交换器没有区别,他能在任何的队列上被指定

7.1消息过期时间TTL

有两种方式可以设置消息的TTL(Time to Live)

  • 通过设置队列属性值,队列中所有消息的过期时间都是相同的,
  • 通过设置消息本身的过期时间,则队列中每条消息的TTL都可以不同

注意

对于第一种方式设置队列的TTL属性的方法,一旦消息过期,就会从队列中抹去,

而在第二种方法中,及时消息过期,也不会马上从队列中抹去,因为每条消息是否过期是在即将投递到消费者之前判定的。

8.RabbitMQ消息事物

RabbitMQ中与事物机制相关的方法有三个,

    //开启事务机制
    channel.txSelect();        

    //回滚事务
    channel.txRollback();

    //提交事务
    channel.txCommit();

	private static final String EXCHANGE_NAME = "demo.exchange"; //交换器名称
	private static final String ROUTING_KEY = "demo.routingkey"; //路由键
	private static final String QUEUE_NAME = "demo.queue"; //队列名称
	private static final String IP_ADDRESS = "192.168.110.130";
	private static final int PORT = 5672;//RabbitMQ 服务端默认端口号为 5672
	
	public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
		ConnectionFactory factory = new ConnectionFactory() ;
		factory.setHost(IP_ADDRESS) ;
		factory.setPort(PORT) ;
		factory.setUsername("rabbitstudy");
		factory.setPassword("123456");
		Connection connection = factory.newConnection();//创建连接
		Channel channel = connection.createChannel();//创建信道
		//创建一个 type="direct" 、持久化的、非自动删除的交换器
		channel.exchangeDeclare(EXCHANGE_NAME, "direct" , true , false , null);
		//创建一个持久化、非排他的、非自动删除的队列
		channel.queueDeclare(QUEUE_NAME, true, false , false , null);
		//将交换器与队列通过路由键绑定
		channel.queueBind(QUEUE_NAME , EXCHANGE_NAME , ROUTING_KEY);
		//发送一条持久化的消息: hello world !
		String message = "Hello World !";
		
		//开启事务机制
		channel.txSelect();
		
		channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY ,
				MessageProperties.PERSISTENT_TEXT_PLAIN,
				message.getBytes());

		 //回滚事务
		 //channel.txRollback();
		
		//提交事务
		channel.txCommit();
		TimeUnit.SECONDS.sleep(1);
		//关闭资源
		channel.close() ;
		connection.close();
	}

 

 

   

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值