本文开始进入正文前,我们需要先弄清楚一些基础知识,须知而后用。
1.消息队列(MQ)是啥?
1)消息队列是一种跨进程的通信机制,具有单向传递、单向依赖和异步处理的特性,主要解决多个系统间的消息异步、业务解耦、流量削峰等问题。
2)消息队列从字面上意思是Message+Queue。Message是信息载体,特征是携带的消息通常具有“可消费”性质;Queue是一个单向通道,特征是先进先出。
举个“快递服务”的例子,理解一下消息队列的概念:
快递服务就是一种消息队列MQ,卖家是生产者,买家是消费者,快递包裹是Message,运输线路是Queue。
快递服务(消息队列)的好处:
1)异步处理。生产者制造的商品,快递不需要实时被签收,消费者可以空闲的时候再取件。
2)业务解耦。生产者只关心自己完成订单,不关心卖给了谁;消费者只关心拿到商品,不关系是谁生产的。
3)流量削峰。当促销季,消费者购买的商品太多,快递服务可以提供的仓储功能,可以起到缓冲效果。
快递服务(消息队列)的不足:
1)可用性降低。快递服务一旦瘫痪,商品消费业务就无法进行。
2)业务复杂化增加。商品消费业务,原本是生产者与消费者两者之间的关系,现在有增加了第三方快递服务公司,完成一个商品消费业务复杂化增加。
3)可能一致性问题。生产者通过快递服务发完货就完成了自身的业务,默认商品消费已经成功,但如果消费者对部分商品不满意,拒绝签收所有物品,那么实际上商品消费失败,生产者和消费者双方的数据就会不一致。
2.使用MQ实现异步调用
下文结合Spring Boot,使用RabbitMQ来实现AMQP异步调用:
RabbitMQ不是基于JMS规范,而是基于AMQP(advanced message queuing protocol)高级消息队列协议,采用性能更佳的Erlang语言进行底层技术实现。
第一步:RabbitMQ服务启动(下面为登录服务器的执行启停命令)
1)service rabbitmq-server start #启动
2)service rabbitmq-server stop #停止
3)service rabbitmq-server restart #重启
4)service rabbitmq-server status #查看状态
5)service rabbitmq-server etc #查看有哪些命令可以使用
6) 同时将rabbitmq-server加入到开机自启动服务中,运行以下命令:chkconfig rabbitmq-server on
第二步:开发服务端(消费者,接收端)
1)在pom.xml文件添加maven依赖:
org.springframework.boot
spring-boot-starter-parent
1.5.2.RELEASE
org.springframework.boot
spring-boot-starter-amqp
2)在application.properties配置基础信息
#rabbitmq配置
spring.rabbitmq.host=11.11.11.11 #配置服务地址 (按实际情况修改)
spring.rabbitmq.port=5672 #配置端口号
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtual-host=mytest
#重试3次
spring.rabbitmq.listener.retry.enabled=true
spring.rabbitmq.listener.retry.max-attempts=3
3)新建服务类
MyTestQueueHandler是消费端,监听到指定队列的消息后,执行指定业务。
1.写法一:
public class MyTestQueueHandler {
@RabbitListener(queues = "${spring.rabbitmq.queue.send.msg}")//监听指定的队列
public void receive(final String message) {
//写消费的业务
//执行message相关的业务,如发短信
}
@RabbitListener(queues = "${spring.rabbitmq.queue.send.email}")//监听指定的队列
public void receive(final String email) {
//写消费的业务
//执行email相关的业务,如发邮件
}
}
2.写法二:
@RabbitListener(queues = "${spring.rabbitmq.queue.send.msg}")//监听指定的队列
public class MyTestQueueHandler {
@RabbitHandler
public void receive1(final String message) {
//写消费的业务
//执行message相关的业务,如发短信
}
@RabbitHandler
public void receive2(final String message) {
//写消费的业务
//执行message相关的业务,如发短信2
}
}
RabbitMqConfig配置一下queue队列信息 :(未配置:spring.rabbitmq.queue.send.email,请自行对比增加)
@Configuration
public class RabbitMqConfig {
// 发送短信-queue队列
@Value("${spring.rabbitmq.queue.send.msg}")
private String sendMessageQueue;
// 发送短信-direct类型
@Value("${spring.rabbitmq.exchange.direct.send.msg}")
private String sendMessageDirect;
// 发送短信-bindingKey绑定
@Value("${spring.rabbitmq.binding.send.msg}")
private String sendMessageBinding;
//sendMessageDirect类型
@Bean(name = "sendMessageDirect")
public DirectExchange sendMessageDirect() {
return new DirectExchange(sendMessageDirect);
}
//sendMessageQueue队列
@Bean(name = "sendMessageQueue")
public Queue sendMessageQueue() {
return new Queue(sendMessageQueue, true);
}
//sendMessageBinding绑定
@Bean(name = "sendMessageBinding")
public Binding sendMessageBinding(@Qualifier("sendMessageDirect") final DirectExchange directExchange,
@Qualifier("sendMessageQueue") final Queue queue) {
return BindingBuilder.bind(queue).to(directExchange).with(sendMessageBinding);
}
}
第三步:开发客户端(生产者,发送端)
1)在pom.xml文件添加
同上(开发服务端)
2)在application.properties配置基础信息
同上(开发服务端)
3)新建消费类:
SendMqUtil先定义MQ消息发送公共方法
@Component
public class SendMqUtil {
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 发送消息到指定routingKey(使用默认exchange)-直接指定queue
*/
public void send(final String routingKey, final String content) {
try {
amqpTemplate.convertAndSend(routingKey, content);
} catch (final Exception e) {
log.error("【消息队列发送失败:routingKey=" + routingKey + ",content=" + content + "】");
log.error(e.getMessage(), e);
}
}
/**
* 发送消息到指定routingKey(使用默认exchange)-直接指定queue
*/
public boolean sendEx(final String routingKey, final String content) {
try {
amqpTemplate.convertAndSend(routingKey, content);
return true;
} catch (final Exception e) {
log.error("【消息队列发送失败:routingKey=" + routingKey + ",content=" + content + "】");
log.error(e.getMessage(), e);
return false;
}
}
/**
* 发送消息到指定的交换器和routingKey
*/
public void send(final String exchange, final String routingKey, final String content) {
try {
amqpTemplate.convertAndSend(exchange, routingKey, content);
} catch (final Exception e) {
log.error("【消息队列发送失败:exchange=" + exchange + ",routingKey=" + routingKey + ",content=" + content + "】");
log.error(e.getMessage(), e);
}
}
/**
* 发送消息到指定的交换器和routingKey
*/
public boolean sendEx(final String exchange, final String routingKey, final String content) {
try {
amqpTemplate.convertAndSend(exchange, routingKey, content);
return true;
} catch (final Exception e) {
log.error("【消息队列发送失败:exchange=" + exchange + ",routingKey=" + routingKey + ",content=" + content + "】");
log.error(e.getMessage(), e);
return false;
}
}
}
SendMqComponent:实现具体的消息推送
@Component
public class SendMqComponent {
@Autowired
private SendMqUtil sendMqUtil;
// 发送短信-direct类型
@Value("${spring.rabbitmq.exchange.direct.send.message}")
private String sendMessageDirect;
// 发送消息-routingKey
@Value("${spring.rabbitmq.routingKey.send.message}")
private String sendMessageRoutingKey;
/**
* 发送短信
*/
public void sendMessage(final String message) {
log.debug("sendMessage:" + message);
sendMqUtil.send(sendMessageDirect, sendMessageRoutingKey, message);
}
}