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;
}
}
View Code
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;
}
}
View Code
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;
}
}
View Code
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();
}
}
}
View Code
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();
}
}
}
View Code
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;
}
}
View Code
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
细水长流,打磨濡染,渐趋极致,才是一个人最好的状态。
本文章为转载内容,我们尊重原作者对文章享有的著作权。如有内容错误或侵权问题,欢迎原作者联系我们进行内容更正或删除文章。