使用 RabbitMQ 实现异步调用
引言
除了上篇文章所讲的 ActiveMQ,还有一种流行的开源消息中间件叫 RabbitMQ。和 ActiveMQ 相比,它具有更高的性能。
RabbitMQ 不再基于 JMS 规范,也没有选择 Java 作为底层实现语言。 它基于另一种消息通信协议,名为 AMQP,并采用 Erlang 语言作为技术实现。 RabbitMQ 提供了众多语言客户端,能够与 Spring 框架整合,Spring Boot 也提供了对 RabbitMQ 的支持。
RabbitMQ 官网: http://www.rabbitmq.com
启动 RabbitMQ 服务器
运行 rabbitmq 容器
RabbitMQ 官方已经提供了自己的 Docker 容器,先下载 rabbitmq:3-management 镜像来启动 RabbitMQ 容器, 之所以选择这个镜像是因为它拥有一个 web 控制台,可以通过浏览器来访问。
docker pull rabbitmq:3-management
RabbitMQ 除了控制台,还提供了 HTTP API 方式,可方便应用程序使用。
下面使用如下 Docker 命令启动 RabbitMQ
docker run -d -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin --name rabbitmq rabbitmq:3-management
在启动 RabbitMQ 容器时,它对宿主机暴露了两个端口号
- 15672: 表示RabbitMQ 控制台端口号,可在浏览器中通过控制台来执行 RabbitMQ 的相关操作
- 5672 表示 RabbitMQ 监听的TCP 端口号,应用程序可以通过该端口号与 RabbitMQ 建立 TCP 连接,并完成后续的异步消息通信
此外,启动时还有两个环境变量
- RABBITMQ_DEFAULT_USER : 设置控制台默认用户名, 默认为 guest
- RABBITMQ_DEFAULT_PASS: 设置控制台默认密码,默认为 guest
RabbitMQ 控制台
RabbitMQ 容器启动完毕后,打开浏览器,并在地址栏中输入 http://localhost:15672/
,并且输入登录的用户名和密码,就可以看到控制台如下所示
在上面管理界面中,包含 6 个功能菜单
- Overview: 用于查看 RabbitMQ 基本信息
- Connections: 用于查看 RabbitMQ 客户端的连接信息
- Channels: 用于查看 RabbitMQ 的通道
- Exchanges:用于查看 RabbitMQ 的交换机
- Queues: 用于查看 RabbitMQ 的队列
- Admin: 用于管理 RabbitMQ 的用户,虚拟主机,策略等数据
Exchange 和 Queue
RabbitMQ 只有 Queue, 没有 Topic,因为可通过 Exchange 与 Queue 的组合来实现 Topic 所具备的功能。RabbitMQ 的消息模型如下图所示
在 Exchange 和 Queue 间有一个 Binding 关系,当消息从 Producer 发送到 Exchange 中时,会根据 Binding 来路由消息的去向。
- 如果 Binding 各不相同,那么该消息将路由到其中一个 Queue 中,随后将被一个 Consumer 所消费,此时实现了 "点对点"的消息通信模型。
- 如果 Binding 完全相同,那么该消息就会路由到每个 Queue 中,随后将会被每个 Consumer 消费,这样就实现了 “发布与订阅” 的消息通信模型
因此可将 Binding 理解为 Exchange 到 Queue 的路由规则,这些规则可通过 RabbitMQ 所提供的客户端 API 来控制,也可通过 RabbitMQ 提供的控制台来管理。
RabbitMQ 提供了一个默认的 Exchange(AMQP default),在控制台的 Exchange 菜单中就可以看到它,简单情况下,只需要使用默认的 Exchange 即可,当需要提供发布与订阅功能时才会使用自定义的 Exchange。
开发服务端和客户端
下面我们就将 Spring Boot 与 RabbitMQ 进行整合,先开发一个服务端作为消息的消费者,再开发一个客户端作为消息的生产者,随后运行客户端,并查看服务端中接收到的消息。
开发服务端
创建一个名为 rabbitmq-hello-server 的 maven 项目或者 Spring Starter Project, 在 pom.xml 文件中添加下面 Maven 依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.19.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
Spring Boot 框架中已经添加了对 RabbitMQ 的支持,只需要依赖 spring-boot-starter-amqp
就可以启动 RabbitMQ,此时还需要在 application.properties
配置文件中添加 RabbitMQ 的相关配置项
spring.rabbitmq.addresses=127.0.0.1:5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
接下来创建 HelloServer 类,封装服务端代码
package demo.msa.rabbitmq;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class HelloServer {
@RabbitListener(queues = "hello-queue")
public void receive(String message) {
System.out.println(message);
}
}
只需要在 receive()
方法上定义 @RabbitListener
,并且设置 queues
参数来指定消费者需要监听的的队列名称。
最后,编写一个 Spring Boot 应用程序启动类来启动服务器
package demo.msa.rabbitmq;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class RabbitmqHelloServerApplication {
@Bean
public Queue helloQueue() {
return new Queue("hello-queue");
}
public static void main(String[] args) {
SpringApplication.run(RabbitmqHelloServerApplication.class, args);
}
}
在 RabbitMQ 中,必须通过程序来显式创建队列。服务端启动完毕后,将持续监听 RabbitMQ 的 hello-queue 队列中即将到来的消息,该消息由客户端来发送。
开发客户端
创建一个名为 rabbitmq-hello-client 的 maven 项目或者 Spring Starter Project, pom 中的依赖与服务端一致。客户端的 application.properties 文件与服务端一致。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.19.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
接下来创建一个名为 HelloClient 的类,将其作为客户端
package demo.msa.rabbitmq;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class HelloClient {
@Autowired
private RabbitTemplate rabbitmqTemplate;
public void send(String message) {
rabbitmqTemplate.convertAndSend("hello-queue", message);
}
}
最后编写 Spring Boot 应用程序启动类来启动客户端
package demo.msa.rabbitmq;
import javax.annotation.PostConstruct;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class RabbitmqHelloClientApplication {
@Autowired
private HelloClient helloClient;
@Bean
public Queue helloQueue() {
return new Queue("hello-queue");
}
@PostConstruct
public void init() {
helloClient.send("hello world!");
}
public static void main(String[] args) {
SpringApplication.run(RabbitmqHelloClientApplication.class, args).close();
}
}
与服务端一样,此处使用 @Bean
注解的 helloQueue()
方法创建一个名为 hello-queue
的队列,这样可以保证当客户端在服务端之前启动时,也能创建所需的队列。而且 RabbitMQ 可以确保不会创建同名的队列,因此可分别在服务端与客户端创建同名的队列。
运行 main 方法可以启动客户端应用程序,此时将在服务端看到客户端发送的消息,也可以在 RabbitMQ 控制台中看到消息队列当前的状态。
Java Bean 类型传输
上面发送和接收的消息只是 String 类型,如果发送的消息是一个普通的 Java Bean 类型,应该如何调用呢?
Java Bean 类型则必须实现 Serializable
序列化接口才能正常调用,这是因为 RabbitMQ 所传送的消息是 byte[]
类型,当客户端发送消息需要进行序列化(也就是讲 Java 类型转换为 byte[] 类型),当服务端接收消息前需要先反序列化,因此发送和接收的消息对象必须实现 JDK 的序列化接口。
除了这种序列化方式外,我们也可以使用 Jackson 来实现,而且 RabbitMQ 已经为我们提供了 jackson 序列化的方式,这种方式更加高效。所需要做的是定义一个 Jackson2JsonMessageConverter
的 Spring Bean。
@Bean
public Jackson2JsonMessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
结语
RabbitMQ 的性能非常高效和稳定,也能非常方便的与 Spring Boot 应用程序集成,还拥有非常丰富的官方文档和控制台,因此选择 RabbitMQ 作为服务之间的异步消息调用平台,将成为整个微服务架构中的 "消息中心"。
参考
- 《架构探险—轻量级微服务架构》
使用 RabbitMQ 实现异步调用的更多相关文章
- 006-优化web请求二-应用缓存、异步调用【Future、ListenableFuture、CompletableFuture】、ETag、WebSocket【SockJS、Stomp】
四.应用缓存 使用spring应用缓存.使用方式:使用@EnableCache注解激活Spring的缓存功能,需要创建一个CacheManager来处理缓存.如使用一个内存缓存示例 package c ...
- 使用rabbit mq.模拟dubbo,使MQ异步调用代码写起来像是同步方法.
最近在改造老系统,遇到了需要使用rabbitMq的场景.在以前使用的过程中需要在发送端和消费端各种配置,感觉比较麻烦,然后突然想到了dubbo中@Reference注解的形式,可不可以做一个类似的架子 ...
- 7.RabbitMQ RFC同步调用
RabbitMQ RFC同步调用是使用了两个异步调用完成的,生产者调用消费者的同时,自己也作为消费者等待某一队列的返回消息,消费者接受到生产者的消息同时,也作为消息发送者发送一消息给生产者.参考下图: ...
- RabbitMQ|异步
目录 RabbitMQ|异步 1 概念|异步 1.1 同步与异步 1.2 比喻 2 生产者消费者设计模式 3 RabbitMQ介绍 3.1 主流消息队列比较: 3.2 RabbitMQ安装(mac电脑 ...
- SpringBoot异步调用--@Async详解
1. 概述 在日常开发中,为了提高主线程的效率,往往需要采用异步调用处理,例如系统日志等.在实际业务场景中,可以使用消息中间件如RabbitMQ.RocketMQ.Kafka等来解决.假如对高可用 ...
- C#委托异步调用
参考页面: http://www.yuanjiaocheng.net/webapi/mvc-consume-webapi-get.html http://www.yuanjiaocheng.net/w ...
- Direct3D Draw函数 异步调用原理解析
概述 在D3D10中,一个基本的渲染流程可分为以下步骤: 清理帧缓存: 执行若干次的绘制: 通过Device API创建所需Buffer: 通过Map/Unmap填充数据到Buffer中: 将Buff ...
- 一个简单的webservice的demo(下)winform异步调用webservice
绕了一大圈,又开始接触winform的项目来了,虽然很小吧.写一个winform的异步调用webservice的demo,还是简单的. 一个简单的Webservice的demo,简单模拟服务 一个简单 ...
- 浅析jquery ajax异步调用方法中不能给全局变量赋值的原因及解决方法(转载)
在调用一个jquery的ajax方法时我们有时会需要该方法返回一个值或者给某个全局变量赋值,可是我们发现程序执行完后并没有获取到我们想要的值,这时很有可能是因为你用的是ajax的异步调用async:t ...
随机推荐
- Flask系列08--Flask中flask_session, redis插件
一.安装 1.flask_session 不想将Session的信息存放在Cookie 将Session存放在Redis Cookie中保存Session的ID flask中的session是直接将数 ...
- <转>PHP中正则表达式函数
PHP中的正则表达式函数 在PHP中有两套正则表达式函数库.一套是由PCRE(Perl Compatible Regular Expression)库提供的,基于传统型NFA.PCRE库使用和Perl ...
- 获取请求地址的IP地址
public static String getIpAddr(HttpServletRequest request) throws Exception { String ip = request.ge ...
- mybatis使用中的记录
一: 常用sql语句: sql顺序:select [distinct] * from 表名 [where group by having order by limit]; 查询某段时间内的数据: ...
- [原创]K8一句话密码爆破工具{秒破10万} 支持ASP/PHP/ASPX/JSP/CFM/DIY
工具: K8_FuckOneShell 20161224编译: VS2012 C# (.NET Framework v4.0)组织: K8搞基大队[K8team]作者: K8拉登哥哥博客: http ...
- 【sping揭秘】11、Java 平台上的AOP实现机制
动态代理 Jdk1.3只有引入的动态代理机制,可以再运行期间,为相应的接口(必须得有接口)动态生成对应的代理对象 基于以上问题,我们可以将横切关注点逻辑封装到动态代理的invocationhandle ...
- POJ 2575
#include<iostream> #include<set> #include<stdio.h> using namespace std; int my_abs ...
- shell 终端terminfo命令 tput
tput命令 tput 可以更改终端功能,如移动或更改光标,更改文本属性,清除终端屏幕的特定区域等. 光标属性 在shell脚本或命令行中,可以利用tput命令改变光标属性. tput clear # ...
- (转)Python3.5 queue模块详解
原文:https://www.cnblogs.com/CongZhang/p/5274486.html queue介绍 queue是python中的标准库,俗称队列,可以直接import 引用,在py ...
- c3p0配置文件
配置文件 名称必须为c3p0-config.xml,否则找不到: 标签名称 <c3p0-config> <default-config > 具体配置内容 </defaul ...