开发工具:Intellij IDEA 2018.2.6
springboot: 2.0.6.RELEASE
jdk:1.8.0_192
maven: 3.6.0
rabbitmq:3.4.1
Java消息服务(Java Message Service,JMS)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。
Java消息服务的规范包括两种消息模式,点对点和发布者/订阅者。许多提供商支持这一通用框架因此,程序员可以在他们的分布式软件中实现面向消息的操作,这些操作将具有不同面向消息中间件产品的可移植性。
Java消息服务支持同步和异步的消息处理,在某些场景下,同步消息是必要的;在其他场景下,异步消息比同步消息操作更加便利。
Java消息服务支持面向事件的方法接收消息,事件驱动的程序设计现在被广泛认为是一种富有成效的程序设计范例,程序员们都相当熟悉。
在应用系统开发时,Java消息服务可以推迟选择面对消息中间件产品,也可以在不同的面对消息中间件切换。
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而群集和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。
Brocker:消息队列服务器实体。
Exchange:消息交换机,指定消息按什么规则,路由到哪个队列。
Queue:(队列)是RabbitMQ的内部对象,用于存储消息。
Binding:绑定,它的作用是把exchange和queue按照路由规则binding起来。
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
Vhost:虚拟主机,一个broker里可以开设多个vhost,用作不用用户的权限分离。
Producer:消息生产者,就是投递消息的程序。
Consumer:消息消费者,就是接受消息的程序。
Channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。
消息接收
客户端连接到消息队列服务器,打开一个channel。
客户端声明一个exchange,并设置相关属性。
客户端声明一个queue,并设置相关属性。
客户端使用routing key,在exchange和queue之间建立好绑定关系。
消息发布
客户端投递消息到exchange。
exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。
fanout:该类型路由规则非常简单,会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,相当于广播功能
direct:该类型路由规则会将消息路由到binding key与routing key完全匹配的Queue中
topic:与direct类型相似,只是规则没有那么严格,可以模糊匹配和多条件匹配
headers:该类型不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配
异步
当某个接口的业务逻辑需要很长的时间处理,由于都在一个线程,所以要等待每一步完成才能继续执行。由于每一步的操作时间响应时间不固定,所以这个线程的请求耗时可能会非常长,如果请求过多,会导致IIS站点巨慢,排队请求,甚至宕机,严重影响用户体验。如发送邮件和发送短信,会开启2个异步线程,扔进去并行执行,主线程不管,继续执行后续的操作,这种处理方式要远远好过第一种处理方式,极大的增强了请求响应速度,用户体验良好。缺点是,由于异步线程里的操作都是很耗时间的操作,
解耦
把消息队列作为中间件,以电商订单的业务场景为例子,当订单系统下完单后,把数据消息写入消息队列中,库存系统和发货系统同时订阅这个消息队列,思想上和纯API系统调用类似,但是,消息队列RabbitMq本身的强大功能,会帮我们做大量的出错善后处理,还是,假设下单成功,库存失败,发货成功,当我们修复库存的时候,不需要任何管数据的不一致性,因为库存队列未被处理的消息,会直接发送到库存系统,库存系统会进行处理。实现了应用的大幅度解耦。
削峰
流量削峰一般在秒杀活动中应用广泛,比如在秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列,这样所有的请求不会在一瞬间打到服务器上从而降低服务器的压力。
spring-boot-rabbitmq
com.andy
1.0.7.RELEASE
4.0.0
io.spring.platform
platform-bom
Cairo-SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-amqp
org.projectlombok
lombok
1.18.2
org.apache.maven.plugins
maven-compiler-plugin
3.7.0
1.8
UTF-8
org.springframework.boot
spring-boot-maven-plugin
2.0.3.RELEASE
ZIP
repackage
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Leone
* @since 2018-04-10
**/
@SpringBootApplication
public class RabbitmqApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitmqApplication.class, args);
}
}
server:
port: 8081
servlet:
context-path: /
spring:
application:
name: spring-rabbitmq
rabbitmq:
port: 5672
username: cloud
password: cloud
host: 127.0.0.1
publisher-confirms: true
virtual-host: /cloud
listener:
simple:
concurrency: 5
max-concurrency: 10
acknowledge-mode: manual
prefetch: 1
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* 把队列和交换机绑定
*
* @author Leone
* @since 2018-10-30
**/
@Configuration
public class BindingConfig {
// 消息队列
@Resource(name = RabbitMQConstant.QUEUE_A)
private Queue queueA;
@Resource(name = RabbitMQConstant.QUEUE_B)
private Queue queueB;
@Resource(name = RabbitMQConstant.QUEUE_C)
private Queue queueC;
@Resource(name = RabbitMQConstant.QUEUE_D)
private Queue queueD;
@Resource(name = RabbitMQConstant.QUEUE_E)
private Queue queueE;
@Resource(name = RabbitMQConstant.QUEUE_F)
private Queue queueF;
@Resource(name = RabbitMQConstant.QUEUE_G)
private Queue queueG;
@Resource(name = RabbitMQConstant.QUEUE_H)
private Queue queueH;
@Resource(name = RabbitMQConstant.QUEUE_I)
private Queue queueI;
// 交换机
@Resource
private TopicExchange topicExchange;
@Resource
private FanoutExchange fanoutExchange;
@Resource
private HeadersExchange headersExchange;
@Resource
private DirectExchange directExchange;
// ----------------------主题交换机:Topic exchange----------------------
// 把队列和交换机绑定 *表示一个词,#表示任意数量(零个或多个)单词。
@Bean
public Binding topicBindingA() {
return BindingBuilder.bind(queueB).to(topicExchange).with(RabbitMQConstant.KEY_A);
}
@Bean
public Binding topicBindingB() {
return BindingBuilder.bind(queueC).to(topicExchange).with(RabbitMQConstant.KEY_B);
}
// ----------------------扇形交换机(广播):Fanout ----------------------
@Bean
public Binding fanoutBindingA() {
return BindingBuilder.bind(queueG).to(fanoutExchange);
}
@Bean
public Binding fanoutBindingB() {
return BindingBuilder.bind(queueH).to(fanoutExchange);
}
// ----------------------首部交换机:Headers exchange----------------------
@Bean
public Binding headersBinding() {
Map map = new HashMap<>();
map.put("header-a", "value-a");
map.put("header-b", "value-b");
return BindingBuilder.bind(queueF).to(headersExchange).whereAll(map).match();
}
// ----------------------直连交换机:Direct exchange----------------------
@Bean
public Binding directBindingA() {
return BindingBuilder.bind(queueD).to(directExchange).with(RabbitMQConstant.KEY_C);
}
@Bean
public Binding directBindingB() {
return BindingBuilder.bind(queueE).to(directExchange).with(RabbitMQConstant.KEY_D);
}
}
package com.andy.rabbitmq.config;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.HeadersExchange;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* rabbitMQ交换机配置
*
* @author Leone
* @since 2018-05-05
**/
@Configuration
public class ExchangeConfig {
// ----------------------主题交换机:Topic exchange----------------------
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(RabbitMQConstant.TOPIC_EXCHANGE);
}
// ----------------------扇形交换机(广播):Fanout ----------------------
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange(RabbitMQConstant.FANOUT_EXCHANGE);
}
// ----------------------首部交换机:Headers exchange----------------------
@Bean
public HeadersExchange headersExchange() {
return new HeadersExchange(RabbitMQConstant.HEADERS_EXCHANGE);
}
// ----------------------直连交换机:Direct exchange----------------------
@Bean
public DirectExchange directExchange() {
return new DirectExchange(RabbitMQConstant.DIRECT_EXCHANGE);
}
}
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 消息队列配置
*
* @author Leone
* @since 2018-04-15
**/
@Configuration
public class QueueConfig {
@Bean
public Queue queue_a() {
return new Queue(RabbitMQConstant.QUEUE_A, true);
}
@Bean
public Queue queue_b() {
return new Queue(RabbitMQConstant.QUEUE_B, true);
}
@Bean
public Queue queue_c() {
return new Queue(RabbitMQConstant.QUEUE_C, true);
}
@Bean
public Queue queue_d() {
return new Queue(RabbitMQConstant.QUEUE_D, true);
}
@Bean
public Queue queue_e() {
return new Queue(RabbitMQConstant.QUEUE_E, true);
}
@Bean
public Queue queue_f() {
return new Queue(RabbitMQConstant.QUEUE_F, true);
}
@Bean
public Queue queue_g() {
return new Queue(RabbitMQConstant.QUEUE_G, true);
}
@Bean
public Queue queue_h() {
return new Queue(RabbitMQConstant.QUEUE_H, true);
}
@Bean
public Queue queue_i() {
return new Queue(RabbitMQConstant.QUEUE_I, true);
}
}
/**
* @author Leone
* @since 2018-05-01
**/
public interface RabbitMQConstant {
// 消息队列配置
String QUEUE_A = "queue_a";
String QUEUE_B = "queue_b";
String QUEUE_C = "queue_c";
String QUEUE_D = "queue_d";
String QUEUE_E = "queue_e";
String QUEUE_F = "queue_f";
String QUEUE_G = "queue_g";
String QUEUE_H = "queue_h";
String QUEUE_I = "queue_i";
// rabbitMQ有四种类型的交换机fanout、direct、topic、headers
String HEADERS_EXCHANGE = "headers_exchange";
String FANOUT_EXCHANGE = "fanout_exchange";
String DIRECT_EXCHANGE = "direct_exchange";
String TOPIC_EXCHANGE = "topic_exchange";
// 绑定匹配规则[*]表示一个单词,[#]表示任意数量(零个或多个)单词。
String KEY_A = "topic.#";
String KEY_B = "topic.*";
String KEY_C = "direct_a";
String KEY_D = "direct_b";
}
package com.andy.rabbitmq.controller;
import com.andy.rabbitmq.config.RabbitMQConstant;
import com.andy.rabbitmq.sender.MessageSender;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Leone
* @since 2018-05-15
**/
@Slf4j
@RestController
public class RabbitMqController {
@Autowired
private MessageSender messageSender;
@GetMapping("/send/{target}")
public String send(@PathVariable("target") String target, @RequestParam String message) {
switch (target) {
case "topic":
messageSender.sendTopic(RabbitMQConstant.TOPIC_EXCHANGE, RabbitMQConstant.KEY_A, message);
break;
case "fanout":
messageSender.sendFanout(RabbitMQConstant.FANOUT_EXCHANGE, "fanout类型交换机发送的内容!");
break;
case "direct":
messageSender.sendDirect(RabbitMQConstant.DIRECT_EXCHANGE, "direct类型交换机发送的内容!");
break;
case "headers":
messageSender.sendHeaders(RabbitMQConstant.HEADERS_EXCHANGE, "header类型交换机发送的内容!");
break;
default:
messageSender.sendQueue(RabbitMQConstant.QUEUE_A, message);
break;
}
return "send " + target + " success!";
}
}
package com.andy.rabbitmq.receive;
import com.andy.rabbitmq.config.RabbitMQConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author Leone
* @since 2018-05-15
**/
@Slf4j
@Component
public class MessageReceive {
//-------------------------普通队列模式-------------------------------
@RabbitListener(queues = RabbitMQConstant.QUEUE_A)
public void receiveQueue(Object msg) throws Exception {
Thread.sleep(3000);
log.info("receive:{} queue message:{} --- queue", RabbitMQConstant.QUEUE_A, msg);
}
//-------------------------topic类型的交换机(主题模式)-------------------------
@RabbitListener(queues = RabbitMQConstant.QUEUE_B)
public void receiveTopicA(Object msg) throws Exception {
Thread.sleep(3000);
log.info("receive:{} exchange message:{} --- receiveTopicA", RabbitMQConstant.TOPIC_EXCHANGE, msg);
}
@RabbitListener(queues = RabbitMQConstant.QUEUE_C)
public void receiveTopicB(Object msg) throws Exception {
Thread.sleep(3000);
log.info("receive:{} exchange message:{} --- receiveTopicB", RabbitMQConstant.TOPIC_EXCHANGE, msg);
}
//-------------------------headers类型的交换机(首部模式)------------------------
@RabbitListener(queues = RabbitMQConstant.QUEUE_F)
public void receiveHeaders(byte[] msg) throws Exception {
Thread.sleep(3000);
log.info("receive:{} exchange message:{} --- receiveHeaders", RabbitMQConstant.HEADERS_EXCHANGE, new String(msg));
}
//-------------------------direct类型的交换机(直连模式)------------------------
@RabbitListener(queues = RabbitMQConstant.QUEUE_D)
public void receiveDirectA(Object msg) throws Exception {
Thread.sleep(3000);
log.info("receive:{} exchange message:{} --- receiveDirectA", RabbitMQConstant.DIRECT_EXCHANGE, msg);
}
@RabbitListener(queues = RabbitMQConstant.QUEUE_E)
public void receiveDirectB(Object msg) throws Exception {
Thread.sleep(3000);
log.info("receive:{} exchange message:{} --- receiveDirectB", RabbitMQConstant.DIRECT_EXCHANGE, msg);
}
//------------------------- fanout类型的交换机(广播模式)-----------------------
@RabbitListener(queues = RabbitMQConstant.QUEUE_G)
public void receiveFanoutA(Object msg) throws Exception {
Thread.sleep(3000);
log.info("receive:{} exchange message:{} --- receiveFanoutA", RabbitMQConstant.FANOUT_EXCHANGE, msg);
}
@RabbitListener(queues = RabbitMQConstant.QUEUE_H)
public void receiveFanoutB(Object msg) throws Exception {
Thread.sleep(3000);
log.info("receive:{} exchange message:{} --- receiveFanoutB", RabbitMQConstant.FANOUT_EXCHANGE, msg);
}
}
import com.andy.rabbitmq.config.RabbitMQConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author Leone
* @since 2018-06-05
**/
@Slf4j
@Component
public class MessageSender {
@Resource
private AmqpTemplate template;
//-------------------------普通队列模式-------------------------------
public void sendQueue(String queue, Object msg) {
log.info("send:{},message:{}", queue, msg);
template.convertAndSend(queue, msg);
}
/**
* direct类型的交换机(直连模式)
* 直连交换机是一种带路由功能的交换机,一个队列会和一个交换机绑定,除此之外再绑定一个routing_key,当消息被发送的时候,需要指定一个binding_key,
* 这个消息被送达交换机的时候,就会被这个交换机送到指定的队列里面去。同样的一个binding_key也是支持应用到多个队列中的。这样当一个交换机绑定多个队列,就会被送到对应的队列去处理。
*
* @param exchange
* @param msg
*/
public void sendDirect(String exchange, Object msg) {
template.convertAndSend(exchange, RabbitMQConstant.KEY_C, msg);
template.convertAndSend(exchange, RabbitMQConstant.KEY_D, msg);
log.info("send:{}交换机,routing_key:{},message:{}", exchange, RabbitMQConstant.KEY_C, msg);
log.info("send:{}交换机,routing_key:{},message:{}", exchange, RabbitMQConstant.KEY_D, msg);
}
/**
* topic类型的交换机(主题模式)
*
* 直连交换机的routing_key方案非常简单,如果我们希望一条消息发送给多个队列,那么这个交换机需要绑定上非常多的routing_key,假设每个交换机上都绑定一堆的routing_key连接到各个队列上。
* 那么消息的管理就会异常地困难。所以RabbitMQ提供了一种主题交换机,发送到主题交换机上的消息需要携带指定规则的routing_key,主题交换机会根据这个规则将数据发送到对应的(多个)队列上。
* 主题交换机的routing_key需要有一定的规则,交换机和队列的binding_key需要采用*.#.*.....的格式,每个部分用.分开,其中:
* {*表示一个单词 #表示任意数量(零个或多个)单词}
*
* @param exchange
* @param key
* @param msg
*/
public void sendTopic(String exchange, String key, Object msg) {
template.convertAndSend(exchange, RabbitMQConstant.KEY_A, msg + "a");
template.convertAndSend(exchange, RabbitMQConstant.KEY_B, msg + "b");
log.info("send:{},交换机,绑定规则为{}message:{}", exchange, RabbitMQConstant.KEY_A, msg);
log.info("send:{},交换机,绑定规则为{}message:{}", exchange, RabbitMQConstant.KEY_B, msg);
}
/**
* fanout类型的交换机(广播模式)
*
* 扇形交换机是最基本的交换机类型,它所能做的事情非常简单———广播消息。扇形交换机会把能接收到的消息全部发送给绑定在自己身上的队列。因为广播不需要“思考”,所以扇形交换机处理消息的速度也是所有的交换机类型里面最快的。
*
* @param exchange
* @param msg
*/
public void sendFanout(String exchange, Object msg) {
template.convertAndSend(exchange, "some thing", msg);
log.info("send:{},交换机,message:{}", exchange, msg);
}
/**
* headers类型的交换机(首部模式)
*
* 首部交换机是忽略routing_key的一种路由方式。路由器和交换机路由的规则是通过Headers信息来交换的,这个有点像HTTP的Headers。将一个交换机声明成首部交换机,绑定一个队列的时候,定义一个Hash的数据结构,消息发送的时候,会携带一组hash数据结构的信息,当Hash的内容匹配上的时候,消息就会被写入队列。
* 绑定交换机和队列的时候,Hash结构中要求携带一个键“x-match”,这个键的Value可以是any或者all,这代表消息携带的Hash是需要全部匹配(all),还是仅匹配一个键(any)就可以了。相比直连交换机,首部交换机的优势是匹配的规则不被限定为字符串(string)。
*
* @param exchange
* @param msg
*/
public void sendHeaders(String exchange, String msg) {
MessageProperties properties = new MessageProperties();
properties.setHeader("header-a", "value-a");
properties.setHeader("header-b", "value-b");
Message message = new Message(msg.getBytes(), properties);
template.convertAndSend(exchange, "", message);
log.info("send:{},交换机,message:{}", exchange, msg.getBytes());
}
}
github