前言
所以在企业中我们要根据不同的业务需求,灵活选择同步远程调用和异步远程调用;
常见的角色有:Producer(生产者)、Consumer(消费者)、Broker(中介)。
1.
RabbitMQ | ActiveMQ | RocketMQ | Kafka | |
---|---|---|---|---|
公司/社区 | Rabbit | Apache | 阿里 | Apache |
开发语言 | Erlang | Java | Scala&Java | |
协议支持 | AMQP,XMPP,SMTP,STOMP | OpenWire,AMQP,STOMP,MQTT | 自定义协议 | 自定义协议 |
可用性 | 高 | 一般 | 高 | 高 |
单机吞吐量 | 一般 | 差 | 高 | 非常高 |
消息延迟 | 微秒级 | 毫秒级 | 毫秒级 | 毫秒以内 |
消息可靠性 | 高 | 一般 | 高 |
2.
- RocketMQ是阿里巴巴开发的MQ其特点是消息可靠性较高适用于电商项目;
- Kafka传输的是数据流,虽然消息可靠性较低,但是吞吐量非常高,适用于大数据项目;
- RabbitMQ的各方面性能适中适用于中小型项目;
官方教程:
#1.下载rabbitmq镜像 docker pull rabbitmq:3.8-management #2.运行容器 docker run -d -p 15672:15672 -p 5672:5672 --name mq -v mq-plugins:/plugins --hostname mq rabbitmq:3.8-management
设置5672端口提供API服务,15672端口提供用户管理界面;
2.
为了让各个用户可以互不干扰的工作,RabbitMQ添加了虚拟主机(Virtual Hosts)的概念;
虚拟主机其实就是1个独立的访问路径,每1个用户使用不同路径,每1个路径中包含多个队列、交换机;
虚拟主机和虚拟主机之间互相隔离不会影响;
将创建好的zhanggen虚拟主机交给用户zhanggen管理;
三、Java调用RabbitMQ
生产者负责想消息对象的某个队列中发送消息,生产者向消息队列发送消息成功之后程序退出;
消费者启动之后会一直监听消息队列的某个队列是否有消息,程序不会退出;
1.依赖引入
<dependencies> <!--AMQP依赖,包含RabbitMQ--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <!--单元测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
2.生产者
package com.itheima.test; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import org.junit.Test; import java.io.IOException; import java.util.concurrent.TimeoutException; public class PublisherTest { @Test public void testSendMessage() throws IOException, TimeoutException { // 1.建立连接 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("192.168.56.18"); factory.setPort(5672); factory.setVirtualHost("zhanggen"); factory.setUsername("zhanggen"); factory.setPassword("123.com"); Connection connection = factory.newConnection(); // 2.创建通道Channel Channel channel = connection.createChannel(); // 3.创建队列 String queueName = "p2p";//队列名称 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(); } }
3.消费者
package com.itheima.test; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; public class ConsumerTest { public static void main(String[] args) throws IOException, TimeoutException { // 1.建立连接 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("192.168.56.18"); factory.setPort(5672); factory.setVirtualHost("zhanggen"); factory.setUsername("zhanggen"); factory.setPassword("123.com"); Connection connection = factory.newConnection(); // 2.创建通道Channel Channel channel = connection.createChannel(); // 3.创建队列 String queueName = "p2p"; 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("消费者等待接收消息。。。。"); } }
调用RabbitMQ
Spring AMQP:基于AMQP协议定义的一套API,提供了模板来发送和接收消息,模板底层是基于RabbitMQ封装。
SpringAmqp的官方地址:
以下我们将借助SpringAmqp实现RabbitMQ的5中消息传输模型;
1.
1.1.生产者
1.1.1.application.yml配置
在application.yml中添加MQ配置
#RabbitMQ相关配置 spring: rabbitmq: host: 192.168.56.18 # 主机名 port: 5672 # 端口 virtual-host: zhanggen # 虚拟主机 username: zhanggen # 用户名 password: 123.com # 密码
1.1.2.测试类
编写测试类SpringAmqpTest,并利用RabbitTemplate实现消息发送
package com.zhanggen.test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @SpringBootTest @RunWith(SpringJUnit4ClassRunner.class) public class SpringAmqpTest { @Autowired private RabbitTemplate rabbitTemplate; //发送简单消息 @Test public void testSimpleQueue() { //参数一: 队列名称(次队列需要是提前创建好的) 参数二: 消息内容 rabbitTemplate.convertAndSend("p2p", "hello,spring amqp!"); } }
1.2.消费者
1.2.1.application.yml配置
#RabbitMQ相关配置
spring:
rabbitmq:
host: 192.168.56.18 # 主机名
port: 5672 # 端口
virtual-host: zhanggen # 虚拟主机
username: zhanggen # 用户名
password: 123.com # 密码
1.2.2.测试类
package com.zhanggen.listener; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class SpringRabbitListener { //简单类型消息 @RabbitListener(queues = "p2p")//声明队列名称 public void listenSimpleQueueMessage(String msg) { System.out.println("消费者接收到消息:【" + msg + "】"); } }
//批量发送消息 @Test public void testWorkQueue() { for (int i = 1; i <= 50; i++) { rabbitTemplate.convertAndSend("p2p", "message_" + i); } }
//使用下面两个方法来接收p2p队列中的消息 @RabbitListener(queues = "p2p") public void listenWorkQueue1(String msg) throws InterruptedException { System.out.println("消费者1接收到消息:【" + msg + "】"); Thread.sleep(20); } @RabbitListener(queues = "p2p") public void listenWorkQueue2(String msg) throws InterruptedException { System.err.println("消费者2........接收到消息:【" + msg + "】"); Thread.sleep(200); }
2.4.测试
可以看到消费者1很快完成了自己的25条消息,费者2却在缓慢的处理自己的25条消息。
2.5.消费者能者多劳
在spring中有一个简单的配置,可以解决以上问题。我们修改consumer服务的application.yml文件,添加配置:
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 消费者一次处理一条消息,处理完毕后再从MQ中获取
2.6.测试消费者能者多劳
2.7.小结
以上2中消息传输模型都属于点对点传输模型
Work模型的使用:
-
多个消费者绑定到同1个队列,同1条消息只会被1个消费者处理
-
在下面的发布/订阅消息传输模型中,需要使用Exchanges转发消息到多个Queue,不再局限于使用单个Queue传输消息;
3.3.声明交换机和队列
我们习惯在消费者一方来创建交换机和队列;
package com.zhanggen.config; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; //关于Fanout消息传输模型配置 @Configuration public class FanoutConfiguration { //1.配置1个交换机 @Bean public FanoutExchange fanoutExchange() { return new FanoutExchange("MyFanoutExchange"); } //2.配置2个队列myQueue1和myQueue2,注意方法名称就是就是bean的名称 @Bean public Queue myQueue1() { return new Queue("MyQueue1"); } @Bean public Queue myQueue2() { return new Queue("MyQueue2"); } //3.将2个队列(myQueue1和myQueue2)绑定到交换机(MyFanoutExchange) @Bean public Binding bindQueue1(FanoutExchange fanoutExchange, Queue myQueue1) { return BindingBuilder.bind(myQueue1).to(fanoutExchange); } @Bean public Binding bindQueue2(FanoutExchange fanoutExchange, Queue myQueue2) { return BindingBuilder.bind(myQueue2).to(fanoutExchange); } }
//FanOut消息传输模型 @RabbitListener(queues = "MyQueue1") public void listenFanOut1(String msg) throws InterruptedException { System.err.println("FanOut消息传输模型消费者1........接收到消息:【" + msg + "】"); Thread.sleep(200); } @RabbitListener(queues = "MyQueue2") public void listenFanOut2(String msg) throws InterruptedException { System.err.println("FanOut消息传输模型消费者2........接收到消息:【" + msg + "】"); Thread.sleep(200); }
3.5.生产者
//测试FanOut @Test public void testFanOut() { //参数一: 交换机名称 参数二:暂时没用 参数三: 消息内容 rabbitTemplate.convertAndSend("MyFanoutExchange", "", "交换机"); }
3.6.测试
------------------------------------- ------------------------------------- -------------------------------------
4.
这时就要用到Direct模型;
RoutingKey:生产者向Exchange发送消息时,一般会指定一个RoutingKey;
BindingKey:当绑定Exchange和Queue时,一般会指定一个BindingKey;
4.1.注解声明消费者
//测试Direct模型 @RabbitListener( bindings = @QueueBinding(//绑定 exchange = @Exchange(value = "direct.exchange", type = ExchangeTypes.DIRECT),//设置交换机的名字和类型 value = @Queue("direct.queue1"),//设置对列名字 key = "base"//设置bindingkey ) ) public void listenDirectQueue1(String message) { System.out.println("消费者1接收到了Direct消息:" + message); } @RabbitListener( bindings = @QueueBinding(//绑定 exchange = @Exchange(value = "direct.exchange", type = ExchangeTypes.DIRECT),//设置交换机的名字和类型 value = @Queue("direct.queue2"),//设置对列名字 key = {"base", "vip"}//设置bindingkey ) ) public void listenDirectQueue2(String message) { System.out.println("消费者2接收到了Direct消息:" + message); }
4.2.生产者
// 测试direct @Test public void testSendDirect() throws Exception { //参数一: 交换机名称 参数二:routingKey 参数三: 消息内容 rabbitTemplate.convertAndSend("direct.exchange", "vip", "hello,everyone!"); }
RoutingKey:一般由有1个或多个单词组成,多个单词之间以”.”分割,例如:china.news
BindingKey:使用通配符匹配RoutingKey匹配规则如下:
#
:代指匹配RoutingKey的0个或多个单词*
5.1.消费者
//测试topic模型 @RabbitListener(bindings = @QueueBinding( value = @Queue("topic.queue1"), exchange = @Exchange(value = "topic.exchange", type = ExchangeTypes.TOPIC), key = "china.#" )) public void listenTopicQueue1(String msg) { System.out.println("消费者1接收到Topic消息:【" + msg + "】"); } @RabbitListener(bindings = @QueueBinding( value = @Queue("topic.queue2"), exchange = @Exchange(value = "topic.exchange", type = ExchangeTypes.TOPIC), key = "#.weather" )) public void listenTopicQueue2(String msg) { System.out.println("消费者2接收到Topic消息:【" + msg + "】"); }
5.2.生产者
// 测试topic @Test public void testTopicExchange() throws Exception { //参数一: 交换机名称 参数二:routingKey 参数三: 消息内容 rabbitTemplate.convertAndSend("topic.exchange", "china.news", "喜报!孙悟空大战孙行者,胜!!!"); }
6.
-
数据体积过大
-
如果我们希望消息的体积更小、可读性更高,因此可以使用JSON方式来做序列化和反序列化。
6.1.1.在父工程中引入依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.10</version> </dependency>
6.1.2.在publisher和consumer两个服务启动类中添加json转换器
SpringBoot的启动类也是1个配置类;
需要注意的是在publisher和consumer配置完了Json转换器之后,双方经应该传输JSON数据类型的消息,否则消息反序列化是就会报错;
@Bean public MessageConverter jsonMessageConverter(){ return new Jackson2JsonMessageConverter(); }
6.1.3.测试