一、消息服务概述
1.为什么要使用消息服务?
在多数应用尤其是分布式系统中,消息服务是不可或缺的重要部分,它使用起来比较简单,同时解决了不少难题,例如异步处理、应用解耦、流量削锋、分布式事务管理等,使用消息服务可以实现一个高性能、高可用、高扩展的系统。
(1)异步处理:
串行处理:耗时,用户体验不好
并行处理:较为耗时的业务处理,不够完善
使用消息服务:快 速
(2)应用解耦:
传统处理方式:库存系统出现异常,订单服务会失败导致订单丢失
使用消息服务:订单服务的下订单消息会快速写入消息队列,库存服务会监听并读取到订单,从而修改库存,高效可靠
(3)流量削峰:
对请求进行分层过滤,从而过滤掉一些请求
(4)分布式事务管理:
订单支付成功后,写入消息表;
定时扫描消息表消息写入到消息队列中;
库存系统会立即读取到消息队列中的消息进行库存更新,同时添加消息处理状态;
库存系统向消息队列中写入库存处理结果;
订单系统会立即读取到消息队列中的库存处理状态。直接删除消息表数据,并写入到历史消息表。
2.常用消息中间件介绍
(1)ActiveMQ (性能相对较弱)
(2)RabbitMQ
(3)Kafka
(4)RocketMQ
二、RabbitMQ消息中间件
1.RabbitMQ简介
RabbitMQ是基于AMQP协议的轻量级、可靠、可伸缩和可移植的消息代理,Spring使用RabbitMQ通过AMQP协议进行通信,在Spirng Boot中对RabbitMQ进行了集成管理。
消息代理过程:
2.RabbitMQ 工作模式介绍
(1)Work queues(工作队列模式)
使用场景:适用于那些较为繁重,并且可以进行拆分处理的业务,这种情况下可以分派给多个消费者轮流处理业务。
需要指定唯一的消息队列进行消息传递,并且可以有多个消息消费者。在这种模式下,多个消息消费者通过轮询的方式依次接收消息队列中存储的消息,一旦消息被某一个消费者接收,消息队列会将消息移除,而接收并处理消息的消费者必须在消费完一条消息后再准备接收下一条消息。
(2)Publish/Subscribe(发布订阅模式)
使用场景:适用于进行相同业务功能处理的场合
在Publish/Subscribe工作模式中,必须先配置一个fanout类型的交换器,不需要指定对应的路由键(Routing key),同时会将消息路由到每一个消息队列上,然后每个消息队列都可以对相同的消息进行接收存储,进而由各自消息队列关联的消费者进行消费。
(3)Routing(路由模式)
使用场景:适用于进行不同类型消息分类处理的场合
在Routing工作模式中,必须先配置一个direct类型的交换器,并指定不同的路由键值(Routing key)将对应的消息从交换器路由到不同的消息队列进行存储,由消费者进行各自消费。
(4)Topics(通配符模式)
使用场景:适用于根据不同需求动态传递处理业务的场合
在Topics工作模式中,必须先配置一个topic类型的交换器,并指定不同的路由键值(Routing key)将对应的消息从交换器路由到不同的消息队列进行存储,然后由消费者进行各自消费。
(5)RPC
使用场景:适用于远程服务调用的业务处理场合
RPC模式是一个回环结构,主要针对分布式架构的消息传递业务,客户端Client先发送消息到消息队列,远程服务端Server获取消息,然后再写入另一个消息队列,向原始客户端Client响应消息处理结果。
(6)Headers(使用较少,不进行详细介绍)
三、RabbitMQ安装以及整合环境搭建
1.安装RabbitMQ
(1)在RabbitMQ官网上http://www.rabbitmq.com/install-windows.html
下载,如果是在Windows环境下安装RabbitMQ消息中间件还需要64位的Erlang语言包支持。
(2) RabbitMQ安装包依赖于Erlang语言包的支持,所以要先安装Erlang语言包,再安装RabbitMQ安装包。
(3)RabbitMQ可视化效果展示, RabbitMQ默认提供了两个端口号5672和15672,其中5672用作服务端口号,15672用作可视化管理端口号。在浏览器上访问http://localhost:15672(用户名和密码均为guest)
(4)登录成功后,界面如图
2.Spring Boot整合RabbitMQ环境搭建
(1)创建Spring Boot项目,添加Web依赖以及RabbitMQ依赖。
(2)在application.properties配置文件中编写需要设置的配置属性。
# 配置RabbitMQ消息中间件连接配置
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#配置RabbitMQ虚拟主机路径/,默认可以省略
spring.rabbitmq.virtual-host=/
四、Spring Boot与RabbitMQ整合实现
1.Publish/Subscribe(发布订阅模式)
基于API的方式/基于配置类的方式/基于注解的方式
基于API的方式及测试的搭建步骤:
(1)使用AmqpAdmin定制消息发送组件:
在测试类中先引入AmqpAdmin管理类定制Publish/Subscribe工作模式所需的消息组件
@Test
public void amqpAdmin() {
amqpAdmin.declareExchange(new FanoutExchange("fanout_exchange"));
amqpAdmin.declareQueue(new Queue("fanout_queue_email"));
amqpAdmin.declareQueue(new Queue("fanout_queue_sms"));
amqpAdmin.declareBinding(new Binding("fanout_queue_email",
Binding.DestinationType.QUEUE,"fanout_exchange","",null));
amqpAdmin.declareBinding(new Binding("fanout_queue_sms",
Binding.DestinationType.QUEUE,"fanout_exchange","",null));}
执行上述单元测试方法amqpAdmin(),验证RabbitMQ消息组件的定制效果,通过RabbitMQ可视化管理页面的Exchanges面板查看效果。
在RabbitMQ可视化管理页面的Exchanges面板中新出现了一个名称为fanout_exchange的交换器,且其类型是设置的fanout类型。单击fanout_exchange交换器进入查看。
切换到Queues面板页面,查看定制生成的消息队列信息
(2)消息发送者发送消息:
消息发送者发送消息,创建一个实体类User(省略setXX()、getXX()方法和toString()方法)
public class User {
private Integer id;
private String username;
}
在项目测试类中使用Spring框架提供的RabbitTemplate模板类实现消息发送。
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void psubPublisher() {
User user=new User();
user.setId(1);
user.setUsername("石头");
rabbitTemplate.convertAndSend("fanout_exchange","",user);
}
执行上述消息发送的测试方法psubPublisher(),控制执行效果如图
异常的两种解决方式:
将实体类实现JDK自带的Serializable序列化接口/定制其他类型的消息转换器
定制其他类型的消息转换器:
创建一个RabbitMQ消息配置类RabbitMQConfig,并在该配置类中通过@Bean注解自定义了一个Jackson2JsonMessageConverter类型的消息转换器组件,该组件的返回值必须为MessageConverter类型
@Configuration
public class RabbitMQConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
执行上述测试方法,执行成功,查看RabbitMQ可视化管理页面Queues面板信息,两个消息队列中各自拥有一条待接收的消息。
(3)消息消费者接收消息:
消息消费者接受消息,创建一个针对RabbitMQ消息中间件进行消息接收和处理的业务类RabbitMQService,在该类中编写如下方法
@RabbitListener(queues = "fanout_queue_email")
public void psubConsumerEmail(Message message) {
byte[] body = message.getBody();
String s = new String(body);
System.out.println("邮件业务接收到消息: "+s);
}
@RabbitListener(queues = "fanout_queue_sms")
public void psubConsumerSms(Message message) {
byte[] body = message.getBody();
String s = new String(body);
System.out.println("短信业务接收到消息: "+s);
}
启动项目来监听并接收消息队列中的消息。程序启动成功后,立即查看控制台打印结果
基于配置类的方式:
@Bean
public Binding bindingEmail(){
return
BindingBuilder.bind(fanout_queue_email()).to(fanout_exchange()).with("").noargs();
}
@Bean
public Binding bindingSms(){
return
BindingBuilder.bind(fanout_queue_sms()).to(fanout_exchange()).with("").noargs();
}
打开业务类RabbitMQService,使用@RabbitListener注解及其相关属性定制消息发送组件,在该类中编写处理邮件以及短信任务的方法
@RabbitListener(bindings =@QueueBinding(value =
@Queue("fanout_queue_email"), exchange =
@Exchange(value = "fanout_exchange",type = "fanout")))
public void psubConsumerEmailAno(User user) {
System.out.println("邮件业务接收到消息: "+user);
}
@RabbitListener(bindings =@QueueBinding(value =
@Queue("fanout_queue_sms"),exchange =
@Exchange(value = "fanout_exchange",type = "fanout")))
public void psubConsumerSmsAno(User user) {
System.out.println("短信业务接收到消息: "+user);
}
2.Routing(路由模式)
(1)使用基于注解的方式定制消息组件和消息消费者
打开业务类RabbitMQService,在该类中使用@RabbitListener注解及其相关属性定制Routing路由模式的消息组件,并模拟编写消息消费者接收的方法。
@RabbitListener(bindings =@QueueBinding(value =
@Queue("routing_queue_error"),exchange =
@Exchange(value = "routing_exchange",type = "direct"),
key = "error_routing_key"))
public void routingConsumerError(String message) {
System.out.println("接收到error级别日志消息: "+message);
}
@RabbitListener(bindings =@QueueBinding(value =
@Queue("routing_queue_all"),exchange =
@Exchange(value = "routing_exchange",type = "direct"),
key = {"error_routing_key","info_routing_key","warning_routing_key"}))
public void routingConsumerAll(String message) {
System.out.println("接收到info、error、warning等级别日志消息: "+message);
}
(2)消息发送者发送消息
在测试类中使用RabbitTemplate模板类实现Routing路由模式下的消息发送
@Test
public void routingPublisher() {
rabbitTemplate.convertAndSend("routing_exchange",
"error_routing_key",
"routing send error message");
}
直接执行上述消息发送的测试方法routingPublisher(),控制台效果如图
将测试方法routingPublisher()中进行消息发送的参数进行修改,调整发送info级别的日志信息(注意同时修改info_routing_key路由键),再次启动该测试方法,查看控制台执行效果,如图
通过RabbitMQ可视化管理页面查看自动定制的Routing路由模式的消息组件,如图所示
3.Topics(通配符模式)
(1)使用基于注解的方式定制消息组件和消息消费者
打开业务类RabbitMQService,在该类中使用@RabbitListener注解及其相关属性定制Topics通配符模式的消息组件,并模拟编写消息消费者接收的方法。
@RabbitListener(bindings =@QueueBinding(value =
@Queue("topic_queue_email"),exchange =
@Exchange(value = "topic_exchange",type = "topic"),
key = "info.#.email.#"))
public void topicConsumerEmail(String message) {
System.out.println("接收到邮件订阅需求处理消息: "+message);
}
@RabbitListener(bindings =@QueueBinding(value =
@Queue("topic_queue_sms"),exchange =
@Exchange(value = "topic_exchange",type = "topic"),
key = "info.#.sms.#"))
public void topicConsumerSms(String message) {
System.out.println("接收到短信订阅需求处理消息: "+message);
}
(2)消息发送者发送消息
在项目测试类Chapter08ApplicationTests中使用RabbitTemplate模板类实现Routing路由模式下的消息发送。
@Test
public void topicPublisher() {
rabbitTemplate.convertAndSend("topic_exchange","info.email",
"topics send email message");
rabbitTemplate.convertAndSend("topic_exchange","info.sms",
"topics send sms message");
rabbitTemplate.convertAndSend("topic_exchange",
"info.email.sms",
"topics send email and sms message");}
执行测试方法topicPublisher(),先进行邮件订阅用户的消息发送
进行短信订阅用户的消息发送
同时进行邮件和短信订阅用户的消息发送方法
通过RabbitMQ可视化管理页面查看自动定制的Topics通配符模式的消息组件,使用基于注解的方式自动生成了Topics通配符模式下的消息组件,并进行了自动绑定。