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四种
- fanout:他会把所有发送到该交换器的消息,路由到所有与该交换器绑定的队列中;
- direct:把消息路由到那些BingingKey和RoutingKey完全匹配的队列中;
- topic:类似direct,但可以使用通配符
- 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.交换器与队列
交换器与队列绑定
//将交换器与队列通过路由键绑定
- channel.queueBind(QUEUE_NAME , EXCHANGE_NAME , ROUTING_KEY);
- channel.queueBind(queue, exchange, routingKey, arguments)
- 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(); }
- queue:队列名称
- autoAck:是否自动确认,false表示不自动确认
- arguments:设置消费的其他参数
- consumerTag:消费者标签,用来区分多个消费者
- noLocal:设置为true则表示不能将同一个connection中生产者的消费传送给这个Connection中的消费者
- exclusive:设置是否排他
- 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的队列就称为死信队列
消息变为死信一般由于以下几种情况
- 消息被拒绝,并且设置requeue参数为false
- 消息过期
- 队列达到最大长度
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(); }