消息队列mq,是作为消息的中间件存在的,它的优势在于
1.解耦合
2.异步提速
3.削峰填谷
解耦:在消息队列没有出现前我们的系统传递消息是由A系统直接传递给系统B,系统A和B直接存在紧密的耦合度,若是其中一个系统发生故障导致整体系统瘫痪,可维护性和容错性较低,不利于程序的拓展.
然而出现了mq,之后系统相互之间的耦合就变成了和消息中间件的耦合了,降低了系统之间的耦合度,独立出来的消息中间件也可以被其他系统所利用,提高了系统的拓展性和可维护性,容错性
异步提速:系统A的消息都存放在消息中间件中,不用去将请求发送给其他系统,减少了系统之间的相互通信,大大提升了速度
削峰填谷:如果系统处理消息的能力在每秒1000,现在每秒出现了5000的请求系统无法处理,使用消息中间件之后,先将消息存放在消息中间件里面,限制消费消息每秒拉取1000个请求给系统处理,(在消费者端为了防止系统处理请求时候,请求数超过了系统的最大的请求处理,我们这里使用了消费端限流)这样提高了系统的稳定性.
**劣势 **
1.系统可用性降低:
系统引入的外部依赖越多,系统稳定性越差。一旦 MQ 宕机,就会对业务造成影响。
解决办法:搭建集群
2.系统复杂度提高:
MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过MQ 进行异步调用。
解决办法:根据业务需求决定
3.一致性问题:
A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理失败。
保证消息的一致性
解决办法:
为了确保消息的可靠性,我们在生产者端和消费者端都进行了消息的可靠性的投递机制
- 在生产者端我们使用了消息的confirm和return
- 在消费者端我们则使用了Ack机制.
确保消息的一致性
生产者端
rabbitmq.properties
rabbitmq.host=192.168.88.133
rabbitmq.port=5672
rabbitmq.username=guest
rabbitmq.password=guest
rabbitmq.virtual-host=/
spring-rabbitmq-producer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
publisher-confirms="true"
publisher-returns="true"
/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
<!-- 消息的可靠性-->
<rabbit:queue id="test_queue_confirm" name="test_queue_confirm"></rabbit:queue>
<rabbit:direct-exchange name="test_exchange_confirm" id="test_exchange_confirm" durable="true">
<rabbit:bindings>
<rabbit:binding queue="test_queue_confirm" key="confirm"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
</beans>
在测试方法中,用于生产者发送消息
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class TestSpringConfirm {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 确认模式开启步骤
* 1.spring的配置文件中connectionFactory的 publisher-confirms="true"
* 2.在rabbitTemplate中定义回调函数
*/
@Test
public void testConfirm() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* confirm(CorrelationData correlationData, boolean ack, @Nullable String cause)
* @param correlationData 配置参数
* @param ack 是否开启ack的机制
* @param cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
byte[] body = correlationData.getReturnedMessage().getBody();
System.out.println(new String(body));
if (ack) {
System.out.println("确认方法执行成功了...");
} else {
System.out.println("确认方法执行失败了...");
}
}
});
CorrelationData correlationData = new CorrelationData();
Message message = new Message("用于测试confirm-".getBytes(), new MessageProperties());
correlationData.setReturnedMessage(message);
rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "用于测试confirm", correlationData);
}
/**
* 回退模式 : 当消息发送给Exchange后,Exchange路由到Queues失败是才会执行ReturnCallBack
* 步骤:
* 1.开启回退模式 publisher-returns="true"
* 2.设置ReturnCallBack
* 3.设置Exchange处理消息的模式,
* 如果消息没有到路由的queue,则丢弃消息(默认)
* 如果消息没有到queue,返回给消息发送方ReturnCallBack
*/
@Test
public void testReturnCallBack() {
//设置交换机处理失败消息的方式
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("回退模式执行了");
System.out.println(message);
System.out.println(replyCode);
System.out.println(replyText);
System.out.println(exchange);
System.out.println(routingKey);
}
});
rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "用于测试confirm");
}
}
publisher confirm
接受成功的情形:
1、消息被路由到所有匹配的队列,如果消息是持久化的,还需要等待消息持久化到磁盘;
2、消息发送到交换机,但未路由到任何队列。
接受失败情形:主要是由于rabbitmq内部错误导致的消息接受失败。
1、生产者发送的交换机不存在;
2、消息由交换机路由到队列出错;
3、持久化消息在进行持久化,写入磁盘是出错;
4、其他rabbitmq的未知错误等等。
解决方法:做一些处理,让消息再次发送。
publisher return
当消息发送给Exchange后,Exchange路由到Queues失败是才会执行ReturnCallBack
消费者端
- comsumer ACK机制
- 1.设置手动的签收,acknowledge=“manual” (配置文件中设置)
- 2.让监听器实现ChannelAwareMessageListener接口
- 3.如果消息成功处理,则调用channel的basicACK签收
- 4.如果消息处理失败,则调用channel的basicNack拒绝签收,broker重新发送给consumer
这里,acknowledge=“manual”
有三种情况
1.none:
自动确认是指,当消息一旦被Consumer接收到,则自动给broker返回确认信息,broker收到确认信息后将相应 message从Broker中移除。
但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失!!!
2.auto:
会根据方法的执行情况来决定是否确认还是拒绝(是否重新入queue)
1.如果消息成功被消费(成功的意思是在消费的过程中没有抛出异常),则自动确认
2.当抛出 AmqpRejectAndDontRequeueException 异常的时候,则消息会被拒绝,且 requeue = false(不重新入队列)
3.当抛出 ImmediateAcknowledgeAmqpException 异常,则消费者会被确认
4.其他的异常,则消息会被拒绝,且 requeue = true(如果此时只有一个消费者监听该队列,则有发生死循环的风险,多消费端也会造成资源的极大浪费,这个在开发过程中一定要避免的)。可以通过 setDefaultRequeueRejected(默认是true)去设置
3.manuel:
手动模式下,broker未正确收到消息的确认或拒绝信息时,会处于unacked状态,
以下情形会进行重新发送:
1、当前消费者重启;
2、有新的消费者加入
确认消息
channel.basicAck(deliveryTag,true);
// deliveryTag:当前需要确认消息的标识; true:是否批量确认
int batch=100;
if(deliveryTag%batch==0){
channel.basicAck(deliveryTag,true);
}
拒绝消息
channel.basicNack(deliveryTag,true,false);
参数1:当前拒绝消息的标识;
参数2:是否批量拒绝;
参数3:requeue是否重入队列 true:重入队列会再次发送;false:作为死信消息(默认删除)
rabbitmq.properties
rabbitmq.host=192.168.88.133
rabbitmq.port=5672
rabbitmq.username=guest
rabbitmq.password=guest
rabbitmq.virtual-host=/
spring-rabbitmq-consumer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
/>
<context:component-scan base-package="com.itheima.rabbitmq.listener"/>
<!--设置手动的签收,acknowledge="manual"-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1" auto-declare="true">
<rabbit:listener ref="qosListener" queue-names="test_queue_confirm"/>
</rabbit:listener-container>
</beans>
/**
* comsumer ACK机制
* 1.设置手动的签收,acknowledge="manual" (配置文件中设置)
* 2.让监听器实现ChannelAwareMessageListener接口
* 3.如果消息成功处理,则调用channel的basicACK签收
* 4.如果消息处理失败,则调用channel的basicNack拒绝签收,broker重新发送给consumer
*/
@Component
public class SpringAckListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
//basicAck(long deliveryTag, boolean multiple)
try {
System.out.println(new String(message.getBody()));
System.out.println("处理业务逻辑");
//int i = 1 / 0;
//一次性设置100数据的传输
if (deliveryTag % 1 == 0) {
channel.basicAck(deliveryTag, true);
}
} catch (Exception e) {
channel.basicNack(deliveryTag, true, true);
}
}
}
限流机制
/**
* Consumer 限流机制
* 1. 确保ack机制为手动确认。
* 2. listener-container配置属性
* perfetch = 1,表示消费端每次从mq拉去一条消息来消费,直到手动确认消费完毕后,才会继续拉去下一条消息。
*/
@Component
public class QosListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
Thread.sleep(1000);
//1.获取消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
//3. 签收
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
消费者端的测试,保证消费者一直处于监听的状态使用死循环让程序不要停止
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class TestConsumer {
@Test
public void test1(){
boolean flag = true;
while (true){
}
}
}