优点:时效性较强,可以立刻得到结果
存在的问题:
异步调用常见实现就是事件驱动模式。
优势:
缺陷:
MQ(MessageQueue),中文是消息队列, 字面看起来是存放消息的队列,也就是事件驱动架构中的Broker
安装并运行RabbitMQ
docker pull rabbitmq:3-management
docker run \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq \
--hostname mq1 \
-p 15672:15672 \ # RabbitMQ控制台端口
-p 5672:5672 \ # RabbitMQ通信端口
-d \
rabbitmq:3-management
官方的HelloWorld是基于最基础的消息队列模型来实现的,只包括三个角色:
所需依赖
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
Publisher代码
public class PublisherTest {
@Test
public void testSendMessage() throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("xxx.xxx.xxx.xx");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("xxx");
factory.setPassword("xxx");
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.发送消息
String message = "hello, rabbitmq!";
channel.basicPublish("", queueName, null, message.getBytes());
System.out.println("发送消息成功:【" + message + "】");
// 5.关闭通道和连接
channel.close();
connection.close();
}
}
Consumer代码
public class ConsumerTest {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("xxx.xxx.xxx.xx");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("xxx");
factory.setPassword("xxx");
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.订阅消息
channel.basicConsume(queueName, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
// 5.处理消息
String message = new String(body);
System.out.println("接收到消息:【" + message + "】");
}
});
System.out.println("等待接收消息。。。。");
}
}
基本消息队列的消息发送流程:
基本消息队列的消息接收流程:
AMQP:Advanced Message Queuing Protocol,是用于在应用程序或之间传递业务消息的开放标准。该协议与语言和平台无关,更符合微服务中独立性的要求
Spring AMQP:是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层默认实现
案例:利用SpringAMQP实现HelloWorld中的基础消息队列功能
在父工程中引入spring-amqp依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
在publisher服务中编写yml配置文件
spring:
rabbitmq:
host: xxx.xxx.xxx.xx # 主机名
port: 5672 # 端口号
virtual-host: / # 虚拟主机
username: xxx # 用户名
password: xxx # 密码
新建测试类,利用RabbitTemplate的convertAndSend方法发送消息到simple.queue这个队列
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage2SimpleQueue() {
String queueName = "simple.queue";
String message = "hello springamqp";
rabbitTemplate.convertAndSend(queueName, message);
}
}
在consumer服务中配置yml文件,设置spring.rabbitmq.listener.simple.prefetch的值,可以控制预取消息的上限
spring:
rabbitmq:
host: xxx.xxx.xxx.xx # 主机名
port: 5672 # 端口号
virtual-host: / # 虚拟主机
username: xxx # 用户名
password: xxx # 密码
listener:
simple:
prefetch: 1
在consumer服务中新建类,编写消费逻辑
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue") //queues的值为消息队列的名称,可以写多个
public void listenSimpleQueue(String msg){
System.out.println("msg = " + msg);
}
}
启动ConsumerApplication,运行查看结果
消息一旦消费就会从队列删除,RabbitMQ没有消息回溯功能
Work Queue工作队列,可以提高消息处理速度,避免队列消息堆积
案例:模拟WorkQueue,实现一个队列绑定多个消费者
在publisher服务中定义测试方法,每秒产生50条消息,发送到simple.queue
@Test
public void testSendMessage2WorkQueue() throws InterruptedException {
String queueName = "simple.queue";
String message = "hello message";
for (int i = 1; i <= 50; i++) {
rabbitTemplate.convertAndSend(queueName, message + i);
Thread.sleep(20);
}
}
在consumer服务中定义两个消息监听者,都监听simple.queue队列
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {
System.out.println("消费者1接收到的消息========>" + msg + "---" + LocalTime.now());
Thread.sleep(20);
}
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue2(String msg) throws InterruptedException {
System.err.println("消费者2接收到的消息--------》" + msg + "---" + LocalTime.now());
Thread.sleep(200);
}
}
消费者1每秒处理50条消息,消费者2每秒处理10条消息
work模型的使用:
发布订阅模式与之前案例的区别就是允许将同意消息发送给多个消费者。实现方式是加入了exchange(交换机)。
exchange负责消息路由,而不是存储,路由失败则消息丢失
Fanout Exchange会将接收到的消息路由到每一个跟其绑定的queue
案例:利用SpringAMQP演示FanoutExchange的使用
在consumer服务中,利用代码声明队列、交换机,并将两者绑定
@Configuration
public class FanoutConfig {
/**
* 创建fanout交换机
* @return 名为my.fanout的交换机
*/
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("my.fanout"); //创建名为my.fanout的交换机
}
/**
* 创建队列
* @return 名为fanout.queue1的队列
*/
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue2");
}
/**
* 绑定队列1到交换机
* @param fanoutQueue1
* @param fanoutExchange
* @return
*/
@Bean
public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
@Bean
public Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
在consumer服务中,编写两个消费者方法,分别监听fanout.queue1和fanout.queue2
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg){
System.out.println("消费者接收到fanout.queue1的消息:【" + msg + "】");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg){
System.out.println("消费者接收到fanout.queue2的消息:【" + msg + "】");
}
}
在publisher中编写测试方法,向my.fanout发送消息
@Test
public void testSendFanoutExchange() {
String exchangeName = "my.fanout";
String message = "hello fanout";
//发送消息,参数分别是交换机名称、routingKey(暂时为空)、消息
rabbitTemplate.convertAndSend(exchangeName, "", message);
}
交换机的作用是什么?
相关Bean:
Direct Exchange会将接收到的消息根据规则路由到指定的queue,因此称为路由模式(routes)
案例:利用SpringAMQP演示DirectExchange的使用
利用@RabbitListener声明Exchange、Queue、RoutingKey
在consumer服务中,编写两个消费者方法,分别监听direct.queue1、direct.queue2
@Component
public class SpringRabbitListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "my.direct", type = ExchangeTypes.DIRECT),
key = {"red", "blue"}
))
public void listenDirectQueue1(String msg){
System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "my.direct", type = ExchangeTypes.DIRECT),
key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg){
System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】");
}
}
ExchangeTypes是一个枚举类型,为交换机的类型
在publisher中编写测试方法,向my.direct发送消息
@Test
public void testSendDirectExchange() {
String exchangeName = "my.direct";
String message = "hello red";
rabbitTemplate.convertAndSend(exchangeName, "red", message); //发送消息,参数分别是交换机名称、routingKey为哪条规则,与之对应的队列就可以收到消息、消息
}
Direct交换机与Fanout交换机的区别:
基于@RabbitListener注解声明队列和交换机有哪些常见注解?
TopicExchange与DirectExchange类似,区别在于routingKey必须是多个单词的列表,并且以.
分割。例如:
Queue与Exchange指定BindingKey时可以使用通配符:
#:代指0个或多个单词
*:代指一个单词
案例:利用SpringAMQP演示TopicExchange的使用
利用@RabbitListener声明Exchange、Queue、RoutingKey
在consumer服务中,编写两个消费者方法,分别监听topic.queue1、topic.queue2
@Component
public class SpringRabbitListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "my.topic", type = ExchangeTypes.TOPIC),
key = "shanghai.#"
))
public void listenTopicQueue1(String msg){
System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "my.topic", type = ExchangeTypes.TOPIC),
key = "#.news"
))
public void listenTopicQueue2(String msg){
System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
}
}
在publisher中编写测试方法,向my.topic发送消息
@Test
public void testSendTopicExchange() {
String exchangeName = "my.topic";
String message = "hello shanghai news";
rabbitTemplate.convertAndSend(exchangeName, "shanghai.news", message); //发送消息,参数分别是交换机名称、routingKey(暂时为空)、消息
}
在SpringAMQP的发送方法中,接收消息的类型是Object,我们可以发送任意对象类型的消息,SpringAMQP会帮我们序列化为字节后发送
Spring的对消息对象的处理是由org.springframework.amqp.support.convert.MessageConvert来处理的,其默认实现是SimpleMessageConvert,基于JDK的ObjectOutputStream完成序列化。
如果需要修改只需要定义一个MessageConvert类型的Bean即可,推荐用JSON方式序列化
在父工程引入依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformatgroupId>
<artifactId>jackson-dataformat-xmlartifactId>
dependency>
在服务配置类中定义MessageConverter
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
SpringAMQP中消息的序列化和反序列化实现