文章目录
- 1. 基础理论
- 1.1. 同步调用与异步调用
- 1.2. RabbitMQ 安装与运行
- 1.2.1. 常见消息模型
- 2. 基本消息队列的应用
- 2.1. 消息发送流程
- 2.2. 消息接收流程
- 3. SpringAMQP的基础理论与应用(想快速应用看这里)
- 3.1. 基础理论
- 3.2. 【案例一】实现HelloWorld中的基础消息队列功能
- 3.3. 【案例二】 Work Queue 工作队列
- 3.4. 发布订阅模式
- 3.4.1. 【案例三】广播(Fanout)
- 3.4.2. 【案例四】路由(Direct)
- 3.4.3. 【案例五】主题(Topic)
- 3.5 消息转换器
1. 基础理论
1.1. 同步调用与异步调用
同步调用
- 优点:时效性强,可以立即得到结果
- 问题:
- 耦合度高:每次加入新的需求,都要修改原来的代码
- 性能和吞吐能力下降:调用者需要等待服务提供者响应,如果调用链过长则响应时间等于每次调用时间之和
- 资源浪费,有额外的资源消耗:调用链中的每个服务在等待响应过程中,不能释放请求占用的资源,在高并发场景下会极度浪费系统资源
- 级联失败:如果服务提供者出现问题,所有调用方都会跟着出问题,迅速导致整个微服务群故障
异步调用
常见实现是事件驱动模式
- 优势
- 服务解耦
- 性能提升,吞吐量提高
- 服务没有强依赖,不担心级联失败问题
- 流量削峰
- 缺点
- 依赖于Broker的可靠性、安全性、吞吐能力
- 架构复杂了,业务没有明显的流程线,不好追踪管理
1.2. RabbitMQ 安装与运行
MQ(MessageQueue):消息队列,存放消息的队列,事件驱动框架中的Broker
RabbitMQ是基于Erlang语言开发的开源消息通信中间件,官网地址:https://www.rabbitmq.com
安装MQ(docker形式)
docker pull rabbitmq:3-management
运行MQ(docker形式)
docker run \
-e RABBITMQ_DEFAULT_USER=用户名 \
-e RABBITMQ_DEFAULT_PASS=密码 \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
概念:
channel:操作MQ的工具
exchange:路由消息到队列中
queue:缓存消息
virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组
1.2.1. 常见消息模型
① 基本消息队列(BasicQueue)
publisher:消息发布者,将消息发送到队列queue
queue:消息队列,负责接受并缓存消息
consumer:消息订阅者
② 工作消息队列(WorkQueue)
③ 发布订阅(Publish/Subscribe),有根据交换机类型不同分为三种:
Fanout Exchange:广播
Direct Exchange:路由
Topic Exchange:主题
2. 基本消息队列的应用
依赖
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.1. 消息发送流程
1.简历connection
2.创建channel
3.利用channel声明队列
4.利用channel向队列发送消息
代码
@Test
public void testSendMessage() throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("");//ip
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("");//账号
factory.setPassword("");//密码
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
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();
}
2.2. 消息接收流程
1.简历connection
2.创建channel
3.利用channel声明队列
4.定义consumer的消费行为handleDelivery
5.利用channel将消费者与队列绑定
代码
public static void main(String[] args) throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("");//ip
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("");//账号
factory.setPassword("");//密码
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
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("等待接收消息。。。。");
}
3. SpringAMQP的基础理论与应用(想快速应用看这里)
官方地址:https://spring.io/projects/spring-amqp
3.1. 基础理论
AMQP(Advanced Message Queuing Protocol):高级消息队列协议,是用于在应用程序或之间传递业务消息的开放标准。该协议与语言和平台无关,更服务微服务中独立型的要求。
Spring AMQP:是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。包含两个部分,其中spring-amqp是抽象基础,spring-rabbit是底层的默认实现。
Spring AMQP 特点:
① 侦听器容器,用于异步处理入站消息
② 用于发送和接收消息的RabbitTemplate
③ RabbitAdmin用于自动声明队列,交换和绑定
3.2. 【案例一】实现HelloWorld中的基础消息队列功能
流程:
① 在父工程中引入spring-amqp的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
② 在application.yml中配置rabbitmq的信息
spring:
rabbitmq:
host: ip
port: 5672
virtual-host: /
username: 账号
password: 密码
③ 在publisher服务中利用RabbitTemplate发送消息到simple.queue队列
@RunWith(SpringRunner.class)
@SpringBootTest
public class PublisherTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSimpleQueue(){
String queueName = "simple.queue";
String message = "hello, spring amqp";
rabbitTemplate.convertAndSend(queueName,message);
}
}
④ 在consumer服务中编写消费逻辑,绑定simple.queue队列
@Component
public class SpringRabbitListener {
//@RabbitListener(queues = "simple.queue")
@RabbitListener(queuesToDeclare = { @Queue("simple.queue")})//如果队列不存在就创建队列
public void listenSimpleQueueMessage(String msg)throws InterruptedException{
System.out.println("spring 消费者接收到消息:【"+msg+"】");
}
}
3.3. 【案例二】 Work Queue 工作队列
Work Queue,工作队列,可以提高消息处理速度,避免消息堆积
流程:
① 在 publisher 服务中定义测试方法,每秒产生50条消息,发送至simple.queue
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testWorkQueue() throws Exception{
String queueName = "simple.queue";
String message = "hello, spring amqp";
for (int i=0;i<50;i++){
rabbitTemplate.convertAndSend(queueName,message+i);
Thread.sleep(20);
}
}
② 在 consumer 服务中定义两个消息监听者,都监听simple.queue
③ 消费者1每秒处理50条数据,消费者2每秒处理5条数据
@Component
public class SpringRabbitListener {
@RabbitListener(queuesToDeclare = { @Queue("simple.queue")})
public void listenWorkQueueMessage(String msg)throws InterruptedException{
System.out.println("spring 消费者【1】接收到消息:【"+msg+"】" + LocalTime.now());
Thread.sleep(20);
}
@RabbitListener(queuesToDeclare = { @Queue("simple.queue")})
public void listenWorkQueueMessage2(String msg)throws InterruptedException{
System.out.println("spring 消费者【2】接收到消息:【"+msg+"】" + LocalTime.now());
Thread.sleep(200);
}
}
④ 因为存在消息预取机制,队列中的消息会平均分配给两个队列,修改配置文件,空值预取消息的上限制
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 每次只能取一条消息,处理完才能获取下一条消息
3.4. 发布订阅模式
发布订阅模式与之前案例的区别是,允许将同一消息发送给多个消费者,实现方式是加入了exchange(交换机)。
常见交换机类型包括:广播(Fanout)、路由(Direct)、话题(Topic)
3.4.1. 【案例三】广播(Fanout)
Fanout Exchange 会将接收到的消息路由到每一个跟其绑定的queue
流程:
① 在 consumer 服务中,创建配置类,声明队列、交换机,并将两者绑定(创建绑定关系)
@Configuration
public class FanoutConfig {
//声明Fanout交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("itcast.fanout");
}
//声明第一个队列
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
//绑定队列1和交换机
@Bean
public Binding bingingQueue1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
//声明第二个队列
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue1");
}
//绑定队列2和交换机
@Bean
public Binding bingingQueue2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
② 在 consumer 服务中,编写两个消费者方法,分别监听 fanout.queue1 和 fanout.queue2
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1Message(String msg){
System.out.println("spring 消费者 1 接收到消息:【"+msg+"】");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2Message(String msg){
System.out.println("spring 消费者 2 接收到消息:【"+msg+"】");
}
}
③ 在 publisher 中编写测试方法,向 itcast.fanout交换机发送消息
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendFanoutExchange(){
//交换机名称
String exchangeName = "itcast.fanout";
//消息
String msg = "hello,every one";
//发送消息
rabbitTemplate.convertAndSend(exchangeName,"",msg);
}
交换机的作用:
① 接收publisher发送的消息
② 将消息按照规则路由到与之绑定的队列
③ 不能缓存消息,路由失败,消息丢失
④ FanoutExchange会将消息路由到每个绑定的队列
3.4.2. 【案例四】路由(Direct)
Direct Exchange会将接收到的消息根据规则路由到指定的Queue。
每一个Queue都与Exchange设置一个BindingKey
发布者发送消息时,指定消息的RoutingKey
Exchange将消息路由到BindingKey与消息RoutingKey一致的队列
流程:
① 在consumer 服务中 利用@RabbitListener声明Exchange、Queue、RoutingKey
@Component
public class SpringRabbitListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
key = {"red","blue"}
))
public void listenDirectQueue1(String msg){
System.out.println("spring 消费者 1 接收到 direct.queue1 的消息:【"+msg+"】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
key = {"red","yellow"}
))
public void listenDirectQueue2(String msg){
System.out.println("spring 消费者 2 接收到 direct.queue2 的消息:【"+msg+"】");
}
}
② 在 publish 服务发送消息到DirectExchange
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendDirectExchange(){
//交换机名称
String exchangeName = "itcast.direct";
//消息
String msg = "hello,blue";
//routingKey
String routingKey = "blue";
//发送消息
rabbitTemplate.convertAndSend(exchangeName,routingKey,msg);
}
Direct交换机与Fanout交换机的差异:
Fanout减缓及将消息路由给每一个与之绑定的队列,Direct交换机根据RoutingKey判断路由给哪个队列。如果多个队列具有相同的routingKey,则与Fanout功能类似。
3.4.3. 【案例五】主题(Topic)
Topic Exchange 与 Direct Exchange类似,区别在于routingKey必须是多个单词的列表,并且以.
分割。
Queue与Exchange指定BingingKey时可以使用通配符:#
代表0或多个单词,*
代表一个单词
流程:
① 利用 @RabbitListener 声明 Echange、Queue、RoutingKey
② 在 consumer 服务中,编写两个消费者方法,分别监听 topic.queue1 和 topic.queue2
@Component
public class SpringRabbitListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),
key = "china.#"
))
public void listenTopicQueue1(String msg){
System.out.println("spring 消费者 1 接收到 topic.queue1 的消息:【"+msg+"】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),
key = "#.news"
))
public void listenTopicQueue2(String msg){
System.out.println("spring 消费者 2 接收到 topic.queue2 的消息:【"+msg+"】");
}
③ 在 publisher 中编写测试方法,向 itcast.topic 发送消息
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendTopicExchange(){
//交换机名称
String exchangeName = "itcast.topic";
//消息
String msg = "天气还行";
//发送消息
rabbitTemplate.convertAndSend(exchangeName,"china.weather",msg);
}
3.5 消息转换器
在 SpringAMQP 的发送方法中,接收消息的类型是Object,也就是说我们可以发送任意对象类型的消息,SpringAMQP会帮我们序列化为字节后发送。
Spring的对消息的处理是由org.springframework.amqp.support.converter.MessageConverter来处理的。而默认实现是SimpleMessageConverter类型的Bean即可。推荐使用JSON方式序列化。
步骤(两端需要):
① 引入依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.11.4</version>
</dependency>
② 声明 MessageConverter
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}