3、RabbitMQ交换机作用_分类_特点_使用案例_五种形式队列
1、RabbitMQ交换机的作用:
生产者发送消息不会像传统方式直接将消息投递到队列中,而是先将消息投递到交换机中,在由交换机转发到具体的队列,队列再将消息以推送或者拉取方式给消费者进行消费。交换机的作用根据具体的路由策略分发到不同的队列中。
2、RabbitMQ的Exchange(交换器)分为四种类型:
direct(默认)、headers、fanout、topic。其中headers交换器允许你匹配AMQP消息的header而非路由键,除此之外headers交换器和direct交换器完全一致,但性能却很差,几乎用不到,所以本文也不做讲解。fanout、topic交换器是没有历史数据的,也就是说对于中途创建的队列,获取不到之前的消息。
2.1 Direct Exchange(直连交换器):
1)也是默认的交换机,是根据消息携带的路由键(routing key)将消息投递给对应队列的
2)直连交换机的特性:
a.公平调度:当接收端订阅者有多个的时候,direct会轮询公平的分发给每个订阅者(订阅者消息确认正常);
b.消息的发后既忘特性:发后既忘模式是指接受者不知道消息的来源,如果想要指定消息的发送者,需要包含在发送内容里面,这点就像我们在信件里面注明自己的姓名一样,只有这样才能知道发送者是谁;
c.消息确认:消息接收到之后必须使用channel.basicAck()方法手动确认(非自动确认删除模式下);
c.1如果应用程序接收了消息,缺忘记确认接收的话(可能因为bug),消息在队列的状态会从“Ready”变为“Unacked”;
c.2如果消息收到却未确认,Rabbit将不会再给这个应用程序发送更多的消息了,这是因为Rabbit认为你没有准备好接收下一条消息。此条消息会一直保持Unacked的状态,直到你确认了消息,或者断开与Rabbit的连接,Rabbit会自动把消息改回Ready状态,分发给其他订阅者。当然你可以利用这一点,让你的程序延迟确认该消息,直到你的程序处理完相应的业务逻辑,这样可以有效的防治Rabbit给你过多的消息,导致程序崩溃。
d.消息拒绝:消息在确认之前,可以有两个选择:
d.1 断开与Rabbit的连接,这样Rabbit会重新把消息分派给另一个消费者;
d.2 拒绝Rabbit发送的消息使用channel.basicReject(long deliveryTag, boolean requeue)
参数说明:【参数1:消息的id;
参数2:处理消息的方式;
如果是true,Rabbib会重新分配这个消息给其他订阅者,如果设置成false的话,Rabbit会把消息发送到一个特殊的“死信”队列,用来存放被拒绝而不重新放入队列的消息;
】
2.2 Fanout Exchange(广播交换器):Fanout 中文意思为 扇出
1)fanout有别于direct交换器,fanout是一种发布/订阅模式的交换器,当你发送一条消息的时候,交换器会把消息广播到所有附加到这个交换器的队列上。
比如用户上传了自己的头像,这个时候图片需要清除缓存,同时用户应该得到积分奖励,你可以把这两个队列绑定到图片上传的交换器上,这样当有第三个、第四个上传完图片需要处理的需求的时候,原来的代码可以不变,只需要添加一个订阅消息即可,这样发送方和消费者的代码完全解耦,并可以轻而易举的添加新功能了。
2)广播交换机的特点(也可以说是与直连交换器的区别)
a.在发送消息时需新增channel.exchangeDeclare(ExchangeName, "fanout"),这行代码声明fanout交换器;
b.在接受消息时需要声明fanout路由器,并且fanout需要绑定队列到对应的交换器用于订阅消息;
channel.queueDeclare().getQueue()为随机队列,Rabbit会随机生成队列名称,一旦消费者断开连接,该队列会自动删除。
注意:对于fanout交换器来说routingKey(路由键)是无效的,这个参数是被忽略的。
2.3 Topic Exchange(主题交换器):是一种匹配订阅模式
1)topic交换器运行和fanout类似,但是可以更灵活的匹配自己想要订阅的信息,这个时候routingKey路由键就排上用场了,使用路由键进行消息(规则)匹配。
2)假设我们现在有一个日志系统,会把所有日志级别的日志发送到交换器,warning、log、error、fatal,但我们只想处理error以上的日志,要怎么处理?这就需要使用topic路由器了。topic路由器的关键在于定义路由键,定义routingKey名称不能超过255字节。主题交换器使用“.”作为分隔符;"*"匹配一个分段(用“.”分割)的内容; "#"匹配0和多个字符;
示列:例如发布了一个“com.mq.rabbit.error”的消息:
能匹配上的路由键:
cn.mq.rabbit.*
cn.mq.rabbit.#
#.error
cn.mq.#
#
不能匹配上的路由键:
cn.mq.*
*.error
*
所以如果想要订阅所有消息,可以使用“#”匹配。
注意:fanout、topic交换器是没有历史数据的,也就是说对于中途创建的队列,获取不到之前的消息。
3、RabbitMQ 直连交换机、广播交换机、主题交换机使用案例:
3.1 依赖引入:
<!--rabbitmq依赖引用-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.6.5</version>
</dependency>
mq.properties文件配置:
rabbitmq.address=127.0.0.1
rabbitmq.port=5672
rabbitmq.username=guest
rabbitmq.password=guest
3.2 代码示列:
1)生产和消费请求:RabbitMqController
package com.zj.weblearn.controller.rabbitmq; import com.zj.weblearn.serviceImpl.rabbitmq.DirectExchangeServiceImpl; import com.zj.weblearn.serviceImpl.rabbitmq.FanoutExchangeServiceImpl; import com.zj.weblearn.serviceImpl.rabbitmq.TopicExchangeServiceImpl; import com.zj.weblearn.utils.ResponseDTO; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.List; /* * @Copyright (C), 2002-2020, * @ClassName: MqController * @Author: * @Date: 2020/9/23 9:49 * @Description: * @History: * @Version:1.0 */ @Controller @RequestMapping("/mq/") public class RabbitMqController { @Autowired DirectExchangeServiceImpl directExchangeServiceImpl; @Autowired FanoutExchangeServiceImpl fanoutExchangeServiceImpl; @Autowired TopicExchangeServiceImpl topicExchangeServiceImpl; private final static String CANCLED_ORDER_QUEUE = "cancled_order_queue"; private final static String CANCLED_ORDER_FAIR_QUEUE = "cancled_order_queue"; /** * @Method: * @Author: * @Description: 将退货的订单通过直连交换器(Direct Exchange)让入消费队里中 * 访问路径:http://localhost:8080/mq/sendCancleOrderToRabbitMqQueue.do?orderNos=1111111,2222222 * param: * @Return: * @Exception: * @Date: 2020/12/8 14:18 */ @RequestMapping("/sendCancleOrderToRabbitMqQueue") @ResponseBody public Object saveOrderInfoToQueue(HttpServletRequest request) { String orderNos = request.getParameter("orderNos"); List orderList = null; if (StringUtils.isNotEmpty(orderNos)) { orderList = Arrays.asList(orderNos.split(",")); } return directExchangeServiceImpl.productMessageByDirectExchange(orderList, CANCLED_ORDER_QUEUE); } /** * @Method: * @Author: * @Description: 通过直连交换器(Direct Exchange)消费 队列 cancle_order_queue 消费消息 * http://localhost:8080/mq/consumerQueueInfo.do * param: * @Return: * @Exception: * @Date: 2020/12/8 15:07 */ @RequestMapping("/consumerQueueOnNoFairWay") @ResponseBody public Object toConsumeQueueInfo() { return directExchangeServiceImpl.consumeMessageByDirectExchange(CANCLED_ORDER_QUEUE); } //http://localhost:8080/mq/saveOrderToQueueFairForward.do /** * @Method: * @Author: * @Description: 通过直连交换器(Direct Exchange)创建消费队列,但是采用 RabbitMQ的公平转发进行消费 * * param: * @Return: * @Exception: * @Date: 2020/12/8 15:32 */ @RequestMapping("/sendCancleOrderToRabbitMqFairQueue") @ResponseBody public Object saveOrderToQueueFairForward(HttpServletRequest request) { String orderNos = request.getParameter("orderNos"); List orderList = null; if (StringUtils.isNotEmpty(orderNos)) { orderList = Arrays.asList(orderNos.split(",")); } return directExchangeServiceImpl.productMessQueueOnFairForward(CANCLED_ORDER_FAIR_QUEUE,orderList); } @RequestMapping("/consumerQueueOnFairWay") @ResponseBody public Object consumerQueueOnFairWay() { ResponseDTO responseDTO= directExchangeServiceImpl.consumerMessageOnFariWay(CANCLED_ORDER_FAIR_QUEUE); ResponseDTO responseDTO2= directExchangeServiceImpl.consumerMessageOnFariWay(CANCLED_ORDER_FAIR_QUEUE); if(!responseDTO.isSuccess()){ return responseDTO; }else if(!responseDTO2.isSuccess()){ return responseDTO2; } return responseDTO2; } }
2)创建连接工具方法:RabbitMqConnectionUtils
package com.zj.weblearn.utils; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.concurrent.TimeoutException; /* * @Copyright (C), 2002-2020, * @ClassName: RabbitMqConnectionUtils * @Author: * @Date: 2020/9/23 9:42 * @Description: * @History: * @Version:1.0 */ public class RabbitMqConnectionUtils { public static Connection getConnection() { //创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //设置服务器地址 factory.setHost(ReadPropertiesUtils1.getValue("rabbitmq.address")); //设置端口号 factory.setPort(Integer.parseInt(ReadPropertiesUtils1.getValue("rabbitmq.port"))); //设置用户名 factory.setUsername(ReadPropertiesUtils1.getValue("rabbitmq.username")); //设置密码 factory.setPassword(ReadPropertiesUtils1.getValue("rabbitmq.password")); //设置vhost factory.setVirtualHost("/"); try { //创建连接 return factory.newConnection(); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } return null; } }
3)通过直连交换器生产和消费消息:DirectExchangeServiceImpl
package com.zj.weblearn.serviceImpl.rabbitmq; import com.rabbitmq.client.*; import com.zj.weblearn.enums.ErrorCodeEnum; import com.zj.weblearn.utils.RabbitMqConnectionUtils; import com.zj.weblearn.utils.ResponseDTO; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.List; import java.util.concurrent.TimeoutException; /* * @Copyright (C), 2002-2020, * @ClassName: DirectExchangeServiceImpl * @Author: * @Date: 2020/9/23 20:39 * @Description: * @History: * @Version:1.0 */ @Service public class DirectExchangeServiceImpl { /** * @Method: * @Author: * @Description: 通过直连交换器(Direct Exchange)向 queueName 中生产消息 * param: * @Return: * @Exception: * @Date: 2020/9/23 20:43 */ public ResponseDTO productMessageByDirectExchange(List<String> orderNos, String queueName) { ResponseDTO responseDTO = new ResponseDTO(ErrorCodeEnum.OK); //1、获取连接 Connection connection = RabbitMqConnectionUtils.getConnection(); Channel channel = null; try { //2、创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务 channel = connection.createChannel(); //3、声明队列 如果Rabbit中没有此队列将自动创建。 //【方法入参: // 参数1:队列的名称; // 参数2:是否持久化,代表队列在服务器重启后是否还存在; // 参数3:是否独占此链接,是否是排他性队列。排他性队列只能在声明它的 Connection中使用(可以在同一个 Connection 的不同的 channel 中使用),连接断开时自动删除; // 参数4:队列不再使用时是否自动删除; // 参数5:队列的其他属性 Map<String, Object> arguments // 】 channel.queueDeclare(queueName, false, false, false, null); //4、发送消息: // 【方法入参: // 参数1: Exchange的名称,如果没有指定,则使用Default Exchange; // 参数2:routingKey,消息的路由Key,是用于Exchange(交换机)将消息转发到指定的消息队列; // 参数3:消息包含的属性; // 参数4:消息体 // 】 //这里没有指定交换机,消息将发送给默认交换机,每个队列也会绑定哪个默认的交换机,但是不能显示绑定或解除绑定认的交换机,routingKey(路由键)等于队列名称 for (String orderNo : orderNos) { channel.basicPublish("", queueName, null, orderNo.getBytes()); } System.out.println("message send body orderNos:" + orderNos); channel.close(); connection.close(); } catch (IOException e) { responseDTO = new ResponseDTO(ErrorCodeEnum.FAIL_501); responseDTO.setErrorMsg(e.toString()); } catch (TimeoutException e) { responseDTO = new ResponseDTO(ErrorCodeEnum.FAIL_501); responseDTO.setErrorMsg(e.toString()); } return responseDTO; } /** * @Method: * @Author: * @Description: 通过直连交换器(Direct Exchange)消费 队列queueName 消费消息 * param: * @Return: * @Exception: * @Date: 2020/9/23 20:43 */ public ResponseDTO consumeMessageByDirectExchange(String queueName) { ResponseDTO responseDTO = new ResponseDTO(ErrorCodeEnum.OK); //1、得到连接 Connection connection = RabbitMqConnectionUtils.getConnection(); Channel channel = null; try { //2、创建一个通道 channel = connection.createChannel(); //3、定义消费方法 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //得到交换机 String exchange = envelope.getExchange(); //路由key String routingKey = envelope.getRoutingKey(); //消息id long deliveryTag = envelope.getDeliveryTag(); //消息内容 String message = new String(body, "utf-8"); System.out.println("Consumer consumption news>>" + message); } }; //4、监听队列 // 【方法入参: // 参数1: 队列名称; // 参数2: 是否自动确认收到 // 设置为true表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息, // 设置为false则需要手动回复; // 参数3: 消费消息的方法,消费者接收到消息后调用此方法 // 】 channel.basicConsume(queueName, true, consumer); } catch (IOException e) { responseDTO = new ResponseDTO(ErrorCodeEnum.FAIL_501); responseDTO.setData(e.toString()); } return responseDTO; } /* * 目前消息转发机制是平均分配,这样就会出现俩个消费者,奇数的任务很耗时,偶数的任何工作量很小,造成的原因就是近当消息到达队列进行转发消息。并不在乎有多少任务消费者并未传递一个应答给RabbitMQ。仅仅盲目转发所有的奇数给一个消费者,偶数给另一个消费者。 为了解决这样的问题,我们可以使用basicQos方法,传递参数为prefetchCount= 1。这样告诉RabbitMQ不要在同一时间给一个消费者超过一条消息。 换句话说,只有在消费者空闲的时候会发送下一条信息。调度分发消息的方式,也就是告诉RabbitMQ每次只给消费者处理一条消息,也就是等待消费者处理完毕并自己对刚刚处理的消息进行确认之后,才发送下一条消息,防止消费者太过于忙碌,也防止它太过去清闲。通过 设置channel.basicQos(1); * */ public ResponseDTO productMessQueueOnFairForward(String queueName, List<String> orderNos) { ResponseDTO responseDTO = new ResponseDTO(ErrorCodeEnum.OK); //1、得到连接 Connection connection = RabbitMqConnectionUtils.getConnection(); //2、创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务 Channel channel = null; try { channel = connection.createChannel(); //3、声明队列 如果Rabbit中没有此队列将自动创建 channel.queueDeclare(queueName, false, false, false, null); channel.basicQos(1);// 保证一次只分发一次 限制发送给同一个消费者 不得超过一条消息 for (String orderNo : orderNos) { //4、发送消息【参数说明:参数一:交换机名称如果没有指定,则使用Default Exchange;参数二:队列名称,参数三:消息的其他属性-路由的headers信息;参数四:消息主体】 channel.basicPublish("", queueName, null, orderNo.getBytes()); } System.out.println("message send over :" + orderNos); channel.close(); connection.close(); } catch (IOException e) { responseDTO = new ResponseDTO(ErrorCodeEnum.FAIL_501); responseDTO.setData(e.toString()); } catch (TimeoutException e) { responseDTO = new ResponseDTO(ErrorCodeEnum.FAIL_501); responseDTO.setData(e.toString()); } return responseDTO; } /** * @Method: * @Author: * @Description: param: * @Return: * @Exception: * @Date: 2020/12/8 14:26 */ public ResponseDTO consumerMessageOnFariWay(String queueName) { ResponseDTO responseDTO = new ResponseDTO(ErrorCodeEnum.OK); //1、得到连接 Connection connection = RabbitMqConnectionUtils.getConnection(); //2、创建一个通道 Channel channel = null; try { channel = connection.createChannel(); //3、声明队列 channel.basicQos(1);// 保证一次只分发一次 限制发送给同一个消费者 不得超过一条消息 //4、定义消费方法 Channel finalChannel = channel; DefaultConsumer consumer = new DefaultConsumer(finalChannel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //得到交换机 String exchange = envelope.getExchange(); //路由key String routingKey = envelope.getRoutingKey(); //消息id long deliveryTag = envelope.getDeliveryTag(); //消息内容 String message = new String(body, "utf-8"); System.out.println("消费者消费:" + message); try { //睡眠1s Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 手动回执消息 finalChannel.basicAck(envelope.getDeliveryTag(), false); } } }; //5、监听队列(入参依次为:队列名称;设置为true表示消息接收到自动向mq回复接收到了,设置为false则需要手动回复;消费消息的方法,消费者接收到消息后调用此方法;) channel.basicConsume(queueName, false, consumer); } catch (IOException e) { responseDTO = new ResponseDTO(ErrorCodeEnum.FAIL_501); responseDTO.setData(e.toString()); } return responseDTO; } }
4)通过广播交换器生产和消费消息:FanoutExchangeServiceImpl
package com.zj.weblearn.serviceImpl.rabbitmq; import com.rabbitmq.client.*; import com.zj.weblearn.utils.RabbitMqConnectionUtils; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.concurrent.TimeoutException; /* * @Copyright (C), 2002-2020, * @ClassName: FanoutExchangeServiceImpl * @Author: * @Date: 2020/9/23 20:26 * @Description: * @History: * @Version:1.0 */ @Service public class FanoutExchangeServiceImpl { final String fanoutExchangeName = "fanoutExchange"; //交换器名称(广播交换器) /** * @Method: * @Author: * @Description: 通过广播交换器(Fanout Exchange)生产消息 * param: * @Return: * @Exception: * @Date: 2020/9/23 20:27 */ public boolean byFanoutExchangeProductMessage(String orderMsg, String queueName) { boolean productResult = false; //1、得到连接 Connection connection = RabbitMqConnectionUtils.getConnection(); Channel channel = null; try { //2、创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务 channel = connection.createChannel(); //3、声明交换器为广播交换器(是一种发布订阅交换器) channel.exchangeDeclare(fanoutExchangeName, "fanout"); //4、声明队列 如果Rabbit中没有此队列将自动创建 channel.queueDeclare(queueName, false, false, false, null); //5、发送消息 channel.basicPublish(fanoutExchangeName, queueName, null, orderMsg.getBytes()); System.out.println("message send body:" + orderMsg); try { channel.close(); } catch (TimeoutException e) { e.printStackTrace(); } connection.close(); productResult = true; } catch (IOException e) { e.printStackTrace(); } return productResult; } /** * @Method: * @Author: * @Description: 通过广播交换器(Fanout Exchange)消费消息 * param: * @Return: * @Exception: * @Date: 2020/9/23 20:30 */ public void byFanoutExchangeConsumeMessage() { //1、得到连接 Connection connection = RabbitMqConnectionUtils.getConnection(); Channel channel = null; try { //2、创建一个通道 channel = connection.createChannel(); //3、声明fanout交换器 channel.exchangeDeclare(fanoutExchangeName, "fanout"); //4、声明队列 String queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, fanoutExchangeName, ""); //5、定义消费方法 Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException { String message = new String(body, "UTF-8"); } }; //6、监听队列(入参依次为:队列名称;设置为true表示消息接收到自动向mq回复接收到了,设置为false则需要手动回复;消费消息的方法,消费者接收到消息后调用此方法;) channel.basicConsume(queueName, true, consumer);// } catch (IOException e) { e.printStackTrace(); } } }
5)通过主题交换器生产和消费消息:TopicExchangeServiceImpl
package com.zj.weblearn.serviceImpl.rabbitmq; import com.rabbitmq.client.*; import com.zj.weblearn.utils.RabbitMqConnectionUtils; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.concurrent.TimeoutException; /* * @Copyright (C), 2002-2020, * @ClassName: TopicExchangeServiceImpl * @Author: * @Date: 2020/9/23 20:33 * @Description: * @History: * @Version:1.0 */ @Service public class TopicExchangeServiceImpl { final String topicExchangeName = "topicExchange"; //主题交换器名称 /** * @Method: * @Author: * @Description: 通过主题交换器(Topic Exchange)生产消息 * param: * @Return: * @Exception: * @Date: 2020/9/23 20:27 */ public boolean byTopicExchangeProductMessage(String orderMsg, String queueName) { boolean productResult = false; //1、得到连接 Connection connection = RabbitMqConnectionUtils.getConnection(); Channel channel = null; try { //2、创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务 channel = connection.createChannel(); //3、声明交换器为主题交换器(是一种匹配订阅模式) channel.exchangeDeclare(topicExchangeName, "topic"); //4、声明队列 如果Rabbit中没有此队列将自动创建 channel.queueDeclare(queueName, false, false, false, null); //5、发送消息 channel.basicPublish(topicExchangeName, queueName, null, orderMsg.getBytes()); System.out.println("message send body:" + orderMsg); try { channel.close(); } catch (TimeoutException e) { e.printStackTrace(); } connection.close(); productResult = true; } catch (IOException e) { e.printStackTrace(); } return productResult; } /** * @Method: * @Author: * @Description: 通过主题交换器(Topic Exchange)消费消息 * param: * @Return: * @Exception: * @Date: 2020/9/23 20:27 */ public void byTopicExchangeConsumeMessage(){ //1、得到连接 Connection connection = RabbitMqConnectionUtils.getConnection(); Channel channel = null; try { //2、创建一个通道 channel = connection.createChannel(); //3、声明topic交换器 channel.exchangeDeclare(topicExchangeName, "topic"); //4、声明队列 String queueName = channel.queueDeclare().getQueue(); String routingKey = "#.error"; channel.queueBind(queueName, topicExchangeName, routingKey); //5、定义消费方法 Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body, "UTF-8"); System.out.println(routingKey + "|接收消息 => " + message); } }; //6、监听队列(入参依次为:队列名称;设置为true表示消息接收到自动向mq回复接收到了,设置为false则需要手动回复;消费消息的方法,消费者接收到消息后调用此方法;) channel.basicConsume(queueName, true, consumer);// } catch (IOException e) { e.printStackTrace(); } } }
6)相关的实体类:
import com.zj.weblearn.enums.BaseEnum; import com.zj.weblearn.enums.ErrorCodeEnum; import java.io.Serializable; public class ResponseDTO<T> implements Serializable { private boolean success; private String errorCode; /** * 原因 */ private String errorMsg; /** * 返回数据值 */ private T data; public ResponseDTO() { } public ResponseDTO(ErrorCodeEnum errorCode) { this.errorCode = errorCode.getErrorCode(); this.errorMsg = errorCode.getErrorMsg(); if(!"0".equals(errorCode.getErrorCode())){ success=false; }else{ success=true; } } public ResponseDTO(String errorCode, String errorMsg) { this.errorCode = errorCode; this.errorMsg = errorMsg; if(!"0".equals(errorCode)){ success=false; }else{ success=true; } } public void setErrorCodeEnum(BaseEnum errorCode) { this.errorCode = errorCode.getErrorCode(); this.errorMsg = errorCode.getErrorMsg(); if(!"0".equals(errorCode.getErrorCode())){ success=false; }else{ success=true; } } public String getErrorCode() { return errorCode; } public void setErrorCode(String errorCode) { this.errorCode = errorCode; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } @Override public String toString() { return "ResponseDTO{" + "errorCode='" + errorCode + '\'' + ", errorMsg='" + errorMsg + '\'' + ", data=" + data + '}'; } } public enum ErrorCodeEnum implements BaseEnum{ OK("0", "成功"), FAIL_500("500", "系统开小差了,请稍后再试!"), FAIL_501("501", "服务异常,请联系管理员处理!"), PARAM_ERROR("502", "入参异常,请检查后重试!"); private String errorCode; private String errorMsg; ErrorCodeEnum(String errorCode, String errorMsg) { this.errorCode = errorCode; this.errorMsg = errorMsg; } ErrorCodeEnum(BaseEnum errorCodeEnum) { this.errorCode = errorCodeEnum.getErrorCode(); this.errorMsg = errorCodeEnum.getErrorMsg(); } public String getErrorCode() { return errorCode; } public String getErrorMsg() { return errorMsg; } }
4、RabbitMQ的五种形式队列
1)简单队列(点对点):一个生产者P发送消息到队列Q,一个消费者C接收。
点对点模式(一对一模式):一个生产者投递消息给队列,只能允许有一个消费者进行消费。如果是消费集群的话,会进行均摊消费。
推(由队列指向消费者):消费者已经启动了,建立长连接,一旦生产者向队列投递消息会立马推送给消费者;
取(由消费者指向队列):生产者先投递消息到队列进行缓存,这时候消费者在启动的时候就向队列中获取消息
队列:以先进先出原则存放消息集合;
2)工作(公平性)队列模式
work queues与简单队列相比,多了一个消费端,两个消费端共同消费同一个队列中的消息。
应用场景:对于 任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
公平队列原理:队列服务器向消费者发送消息的时候,消费者采用手动应答模式,队列服务器必须要收到消费者发送ack结果通知,才会发送下一个消息。
默认消费者集群为均摊消费。假设生产者向队列发送10个消息,消息1和2都各自消费5个,保证消费唯一。
思考:均摊消费弊端-如果每个消费者处理消息的业务时间情况不相同,可能对消息处理比较慢的消费者不公平。应该能这多劳,谁消费快,就让其多消费消息。
3)发布/订阅模式(Publish/Subscribe):这个可能是消息队列中最重要的队列了,其他的都是在它的基础上进行了扩展。发布订阅模式说明:
(1)一个生产者,多个消费者
(2)每一个消费者都有自己的一个队列,并对其进行监听;
(3)生产者没有直接发消息到队列中,而是发送到交换机;
(4)每个消费者的队列都绑定到交换机上;
(5)消息通过交换机到达每个消费者的队列 该模式就是Fanout Exchange(广播交换机)将消息路由给绑定到它身上的所有队列 以用户发邮件案例讲解
注意:交换机没有存储消息功能,如果消息发送到没有绑定消费队列的交换机,消息则丢失。生产者将消息发给broker(消息队列服务),由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息;
4)路由模式Routing:
(1)每个消费者监听自己的队列,并且设置routingkey。
(2)生产者发送消息到交换机并指定一个路由key,消费者队列绑定到交换机时要指定路由key(key匹配就能接受消息,key不匹配就不能接受消息)
5)通配符模式Topics
(1)每个消费者监听自己的队列,并且设置带统配符的routingkey。
(2)生产者P发送消息到交换机X,type=topic,交换机根据绑定队列的routing key的值进行通配符匹配,由交换机根据routingkey来转发消息到指定的队列。
符号#:匹配一个或者多个词error.# 可以匹配error.order或者error.order.cancle
符号*:只能匹配一个词error.* 可以匹配error.order或者error.ceshi
参看文章:https://www.cnblogs.com/vipstone/p/9295625.html
细水长流,打磨濡染,渐趋极致,才是一个人最好的状态。