消息中间件概述
1. 含义
- 百科对消息中间件的解释:面向消息的系统。
- 消息中间件是在分布式系统中完成消息的发送和接收的基础软件。
- 消息中间件也可以称消息队列,是指用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。
2. 常用的消息中间件
- RabBitMQ
- ActiveMQ
- RockertMQ
- Kafka
3. 消息中间件的一般组成
- Broker: 消息服务器
- Producer: 生产者
- Consumer: 消费者
- Topic: 主题
- Queue: 队列
- Message: 消息体
4. 同步调用
- 优点:时效性强,等待到结果后才返回
- 缺点:拓展性差,性能下降(同步的功能太多,速度变慢),级联失败
例:妈妈直接打电话给儿子,告诉他中午回家吃饭
5. 异步调用
- 包含:
- 消息发送者:投递消息的人(即调用方)
- 消息代理:管理、暂存、转发消息
- 消息接收者:接收和处理消息的人(即服务提供方
- 优点:接触耦合,拓展性强;无需等待,性能好;故障隔离;缓存消息,流量削峰填谷
- 缺点:不能立即得到调用结果,时效性差;业务安全依赖于Broker的可靠性不确定下游业务执行是否成功(即上例中,不知道儿子最后知不知道中午要回家吃饭)—>需要确认机制
例:
- 发微信
- 妈妈要通知儿子中午回家吃饭,跟老师说,由老师再转告给儿子,就是异步消息。
- 外卖员将外卖送到取餐柜,拿外卖的人不需要跟外卖员接触(外卖员和拿外卖的人解耦)
RabbitMQ
1. 含义
- RabbitMQ是一款实现了高级消息队列协议(Advanced Message Queuing Protocol, AMQP)的开源消息代理软件(或称中间件)。
- 主要作用:存储分发消息、异步通信、解耦业务模块等
- 基本介绍
- virtual-host:虚拟主机,起到数据隔离的作用
- publisher:消息发送者
- consumer:消息消费者
- queue:队列,存储消息
- exchange:交换机,负责路由消息
2. AMQP
- 消息队列的一个协议
- 组成:交换器,队列,绑定
- 生产者把消息发布到交换器上;消息最终到达队列,并被消费者接收;绑定决定了消息如何从交换器到特定的队列。
3. RabiitMQ安装配置
(网上有很多教程咯就不细说啦)
4. SpringBoot整合RabbitMQ
- 配置RabbitMQ就不多说了
- 添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
- 在application.yml中配置RabbitMQ
rabbitmq: host: 127.0.0.1 port: 5672 username: guest password: guest virtualHost: /
- 编写RabbitMQConfig ,配置Bean
- CachingConnectionFactory
- SimpleRabbitListenerContainerFactory
- RabbitTemplate
package jmu.dmt.service.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.*; import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @Configuration public class RabbitMQConfig { private static final Logger log= LoggerFactory.getLogger(RabbitMQConfig.class); @Autowired(required = false) private CachingConnectionFactory cachingConnectionFactory; //MQ连接 @Autowired(required = false) private SimpleRabbitListenerContainerFactoryConfigurer factoryConfigurer; //监听器配置 /** * 单一消费者 * @return */ @Bean(name = "singleListenerContainer") public SimpleRabbitListenerContainerFactory listenerContainer(){ //客户端连接监听器(Connection, Channel....) SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(cachingConnectionFactory); //factory.setMessageConverter(new Jackson2JsonMessageConverter()); //TODO: JSON factory.setConcurrentConsumers(1); factory.setMaxConcurrentConsumers(1); factory.setPrefetchCount(1); return factory; } /** * RabbitMQ发送消息的操作组件实例 * @return */ @Bean public RabbitTemplate rabbitTemplate(){ // cachingConnectionFactory.setPublisherConfirms(true); cachingConnectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.SIMPLE); cachingConnectionFactory.setPublisherReturns(true); RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory); rabbitTemplate.setMandatory(true); rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { public void confirm(CorrelationData correlationData, boolean ack, String cause) { log.info("消息发送成功:correlationData({}),ack({}),cause({})",correlationData,ack,cause); } }); rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() { @Override public void returnedMessage(ReturnedMessage returnedMessage) { log.info("消息丢失:{}", returnedMessage); } // public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { // log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}",exchange,routingKey,replyCode,replyText,message); // } }); return rabbitTemplate; } }
5. SpringAMQP收发信息简单代码
5.1. 发送消息
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSimpleQueue() {
// 队列名称
String queueName = "simple.queue";
// 消息
String message = "hello, spring amqp!";
// 发送消息
rabbitTemplate.convertAndSend(queueName, message);
}
}
5.2. 接收消息
- 通过注解在方法上声明要监听的队列名称,将来一旦监听的队列中有了消息,就会推送给当前服务,调用当前方法,处理消息。
@Component
public class SpringRabbitListener {
// 利用RabbitListener来声明要监听的队列信息
// 将来一旦监听的队列中有了消息,就会推送给当前服务,调用当前方法,处理消息。
// 可以看到方法体中接收的msg就是消息体的内容
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueueMessage(String msg) throws InterruptedException {
System.out.println("spring 消费者接收到消息:【" + msg + "】");
}
}
WorkQueues模型
- Work queues任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息。
- 使用场景:当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时使用work 模型,多个消费者共同处理消息处理,消息处理的速度就能大大提高了。
模拟
- 首先,我们在控制台创建一个新的队列,命名为
work.queue
: - 消息发送
这次我们循环发送,模拟大量消息堆积现象。
在SpringAmqpTest类中添加一个测试方法:
/**
* workQueue
* 向队列中不停发送消息,模拟消息堆积。
*/
@Test
public void testWorkQueue() throws InterruptedException {
// 队列名称
String queueName = "simple.queue";
// 消息
String message = "hello, message_";
for (int i = 0; i < 50; i++) {
// 发送消息,每20毫秒发送一次,相当于每秒发送50条消息
rabbitTemplate.convertAndSend(queueName, message + i);
Thread.sleep(20);
}
}
- 消息接收
要模拟多个消费者绑定同一个队列,我们在SpringRabbitListener中添加2个新的方法:
@RabbitListener(queues = "work.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {
System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
Thread.sleep(20);
}
@RabbitListener(queues = "work.queue")
public void listenWorkQueue2(String msg) throws InterruptedException {
System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());
Thread.sleep(200);
}
注意到这两消费者,都设置了Thead.sleep
,模拟任务耗时:
- 消费者1 sleep了20毫秒,相当于每秒钟处理50个消息
- 消费者2 sleep了200毫秒,相当于每秒处理5个消息
- 测试
- 结果:从测试结果可以看到消费者1和消费者2平均每人消费了25条消息:
- 消费者1很快完成了自己的25条消息
- 消费者2却在缓慢的处理自己的25条消息。
- 分析:当多个消费者绑定到一个队列时,队列的消息会平均分配给每个消费者。消息平均分给消费者,并没有考虑到消费者的处理能力,不能充分利用每一个消费者的能力,最终消息处理的耗时远远超过了1秒。这就是 消费者信息推送限制 的问题。
消费者信息推送限制
- 含义:默认情况下,RabbitMQ会将消息依次轮询投递给绑定在队列上的每一个消费者,但并没有考虑到消费者是否已经处理完消息,可能会出现消息推挤。
- 解决方法:
修改application.yml文件,设置preFetch值为1,确保同一时刻最多投递给消费者一条消息spring: rabbitmq: listener: simple: prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
- 修改过application.yml文件后,再测试
- 可以发现,由于消费者1处理速度较快,所以处理了更多的消息;消费者2处理速度较慢,只处理了6条消息。而最终总的执行耗时也在1秒左右,大大提升。
- 这样充分利用了每一个消费者的处理能力,可以有效避免消息积压问题。
总结
Work模型的使用:
- 多个消费者绑定到一个队列,可以加快消息处理速度
- 同一条消息只会被一个消费者处理
- 通过设置prefetch来控制消费者预取的消息数量,实现能者多劳