rabbitmq入门(同步调用和异步调用,mq控制台,java代码使用rabbitmq入门)

同步调用与异步调用

什么是同步调用,异步调用?

  • 同步通讯:就如同打视频电话,双方的交互都是实时的。因此同一时刻你只能跟一个人打视频电话。

  • 异步通讯:就如同发微信聊天,双方的交互不是实时的,你不需要立刻给对方回应。因此你可以多线操作,同时跟多人聊天。

两种方式各有优劣,打电话可以立即得到响应,但是你却不能跟多个人同时通话。发微信可以同时与多个人收发微信,但是往往响应会有延迟。

所以,如果我们的业务需要实时得到服务提供方的响应,则应该选择同步通讯(同步调用)。而如果我们追求更高的效率,并且不需要实时响应,则应该选择异步通讯(异步调用)。

************************************************************************************************************

同步调用案例

以一个支付服务为例,原本的服务流程是这样的:

  • 支付服务需要先调用用户服务完成余额扣减

  • 然后支付服务自己要更新支付流水单的状态

  • 然后支付服务调用交易服务,更新业务订单状态为已支付

也就是图中的1,2,3步

存在的问题

1.拓展性差

我们目前的业务相对简单,但是随着业务规模扩大,产品的功能也在不断完善。后续可能要新增通知服务,积分服务.......

最终支付业务会越来越臃肿。也就是说每次有新的需求,现有支付逻辑都要跟着变化,代码经常变动,不符合开闭原则,拓展性不好。

2.性能下降

由于我们采用了同步调用,调用者需要等待服务提供者执行完返回结果后,才能继续向下执行,也就是说每次远程调用,调用者都是阻塞等待状态。

最终整个业务的响应时长就是每次远程调用的执行时长之和,由上图,支付服务需要阻塞300ms,如果有更多的需求,用户可能要对着系统发呆,体验是非常差的。

3.级联失败 

由于我们是基于OpenFeign调用交易服务、通知服务。当交易服务、通知服务出现故障时,整个事务都会回滚,交易失败。

但实际上交易服务,通知服务,积分服务关键吗?并不关键,失败了我们后面重新去做就好了,关键的是整个支付服务的1,2步。

而要解决这些问题,我们就必须用异步调用的方式来代替同步调用

用异步调用进行改进

异步调用方式其实就是基于消息通知的方式,一般包含三个角色:

  • 消息发送者:投递消息的人,就是原来的调用方

  • 消息Broker管理、暂存、转发消息,你可以把它理解成微信的服务器

  • 消息接收者:接收和处理消息的人,就是原来的服务提供方

对上述同步调用的案例进行改进 

可以看到:除了扣减余额、更新支付流水单状态以外,其它调用逻辑全部取消,而是改为发送一条消息到Broker。而相关的微服务都可以订阅消息通知,一旦消息到达Broker,则会分发给每一个订阅了的微服务,处理各自的业务。 

也就是说,

无论后续再增加多少个需求,我们的支付服务的逻辑也不用变,只需要让Broker分发给新增的微服务即可。由此解决了拓展性差的问题。

改进后,无论新增多少个需求,支付服务都只耗时100ms,因为更新订单状态,短信通知,更新积分...诸如此类的服务都不急着去做,这些都是后续的事情,用户也不会关心。我们只要告诉用户支付是否成功了就行。由此解决了性能下降的问题。

同样的,另外,不管是交易服务、通知服务,还是积分服务,。现在采用了异步调用,解除了耦合,他们即便执行过程中出现了故障,后面重新去做就行了,也不会影响到支付服务。由此解决了级联失败的问题。

总结

综上,异步调用的优势包括

  • 耦合度更低

  • 性能更好

  • 业务拓展性强

  • 故障隔离,避免级联失败

  • 缓存消息,削峰填谷

当然,异步通信也并非完美无缺,它存在下列缺点:

  • 完全依赖于Broker的可靠性、安全性和性能

  • 架构复杂,后期维护和调试麻烦

*******************************************************************************************************

MQ技术选型

RabbitMQActiveMQRocketMQKafka
公司/社区RabbitApache阿里Apache
开发语言ErlangJavaJavaScala&Java
协议支持AMQP,XMPP,SMTP,STOMPOpenWire,STOMP,REST,XMPP,AMQP自定义协议自定义协议
可用性一般
单机吞吐量一般非常高
消息延迟微秒级毫秒级毫秒级毫秒以内
消息可靠性一般一般

追求可用性:Kafka、 RocketMQ 、RabbitMQ

追求可靠性:RabbitMQ、RocketMQ

追求吞吐能力:RocketMQ、Kafka

追求消息低延迟:RabbitMQ、Kafka

安装rabbitmq

docker创建rabbitmq容器-CSDN博客

rabbitmq架构

  • publisher:生产者,也就是发送消息的一方

  • consumer:消费者,也就是消费消息的一方

  • queue:队列,存储消息。生产者投递的消息会暂存在消息队列中,等待消费者处理

  • exchange:交换机,负责消息路由。生产者发送的消息由交换机决定投递到哪个队列。

  • virtual host虚拟主机,起到数据隔离的作用。每个虚拟主机相互独立,有各自的exchange、queue

快速入门:rabbitmq控制台

访问配了rabbitmq的主机的15672端口(控制台端口)

************************************************************************************************************

在这里,新建两个队列(hello.queue1和hello.queue2)

指定名字就行,其他默认

 在Exchange里面选中amq.direct这个交换机,新增两条绑定关系,绑定hello.queue1和hello.queue2

填要绑定的队列名就行,其他默认 

 ***************************************************************************************

现在指定amq.direct发布一条消息,看看两个队列能否接收到

*****************************

可以发现,两个队列都收到这条消息了(图就不都帖了)

 

数据隔离

点开控制台的admin,新建一个用户

退出guest,登录我们新创建的用户

 为用户chen添加一个虚拟主机

切换到  /baoder这个虚拟主机

 点到Queues,发现没有队列,说明不同虚拟主机(virtual host) 之间的数据是隔离的

快速入门:rabbitmq的java客户端

 新建一个springboot项目,导入如下依赖

配置一下application.properties

 写一个测试方法来发送消息(生产者)

控制台创建一个队列 

 运行测试方法,去控制台查看结果

 符合预期

******************************************************************

现在写一个消费者来监听hello.queue01,被监听到的消息会被消费(即从队列中移除)

启动测试方法,查看控制台输出

符合预期

******************************************************************

如果尝试监听一个不存在的队列,会报如下错误

 可以写一个RabbitMqConfig,把要用的队列在项目启动时就自动创出来

package com.gmgx.config;

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMqConfig {
    @Bean
    public Queue queue01() {
        return new Queue("hello.queue01");
    }
}

先去控制台把hello.queue01给删了

 启动测试方法

 可以看到,项目跑起来了,队列也被自动创建出来了,符合预期

WorkQueue模型

Work queues,任务模型。简单来说就是多个消费者绑定到一个队列,共同消费队列中的消息

当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。

此时就可以使用work 模型,多个消费者共同处理消息处理,消息处理的速度就能大大提高了。

********************************************************

创建work.queue,采用自动注入的方式创建

@Configuration
public class RabbitMqConfig {
    @Bean
    public Queue workQueue() {
        return new Queue("work.queue");
    }
}

生产者

    @Test
    void testWorkQueue() throws InterruptedException {
        String queueName = "work.queue";
        String msg = "hello work";
        for (int i = 1; i <= 50; i++) {
            rabbitTemplate.convertAndSend(queueName, msg + i);
            Thread.sleep(20);//模拟1秒发送50条消息
        }
    }

消费者 

@Component
public class WorkQueueListener {
    @RabbitListener(queues = "work.queue")
    public void handle01(String msg) throws InterruptedException {
        System.out.println("消费者1消费消息" + msg);
        Thread.sleep(20);//每秒处理50条消息
    }

    @RabbitListener(queues = "work.queue")
    public void handle02(String msg) throws InterruptedException {
        System.err.println("消费者2消费消息" + msg);
        Thread.sleep(200);//每秒处理5条消息
    }

}
  • 消费者1 sleep了20毫秒,模拟每秒钟处理50个消息

  • 消费者2 sleep了200毫秒,模拟每秒处理5个消息

运行一下,结果如下

消费者2消费消息hello work2
消费者1消费消息hello work1
消费者1消费消息hello work3
消费者1消费消息hello work5
消费者1消费消息hello work7
消费者1消费消息hello work9
消费者2消费消息hello work4
消费者1消费消息hello work11
消费者1消费消息hello work13
消费者1消费消息hello work15
消费者2消费消息hello work6
消费者1消费消息hello work17
消费者1消费消息hello work19
消费者1消费消息hello work21
消费者2消费消息hello work8
消费者1消费消息hello work23
消费者1消费消息hello work25
消费者1消费消息hello work27
消费者1消费消息hello work29
消费者2消费消息hello work10
消费者1消费消息hello work31
消费者1消费消息hello work33
消费者1消费消息hello work35
消费者2消费消息hello work12
消费者1消费消息hello work37
消费者1消费消息hello work39
消费者1消费消息hello work41
消费者2消费消息hello work14
消费者1消费消息hello work43
消费者1消费消息hello work45
消费者1消费消息hello work47
消费者1消费消息hello work49
消费者2消费消息hello work16
消费者2消费消息hello work18
消费者2消费消息hello work20
消费者2消费消息hello work22
消费者2消费消息hello work24
消费者2消费消息hello work26
消费者2消费消息hello work28
消费者2消费消息hello work30
消费者2消费消息hello work32
消费者2消费消息hello work34
消费者2消费消息hello work36
消费者2消费消息hello work38
消费者2消费消息hello work40
消费者2消费消息hello work42
消费者2消费消息hello work44
消费者2消费消息hello work46
消费者2消费消息hello work48
消费者2消费消息hello work50

可以看到,消息全部被消费了,但是消费者1和消费者2各消费了25条数据。也就是说消息是平均分配给每个消费者,并没有考虑到消费者的处理能力。导致1个消费者空闲,另一个消费者忙的不可开交。没有充分利用每一个消费者的能力,最终消息处理的耗时远远超过了1秒。这样显然是有问题的。

在spring中有一个简单的配置,可以解决这个问题。我们修改consumer服务的application.yml文件,添加配置:

spring.rabbitmq.listener.simple.prefetch=1

通过设置prefetch来控制消费者预取的消息数量。这条配置告诉RabbitMQ的消费者一次只从队列中拉取一条消息进行处理。

重新运行

消费者2消费消息hello work2
消费者1消费消息hello work1
消费者1消费消息hello work3
消费者1消费消息hello work4
消费者1消费消息hello work5
消费者1消费消息hello work6
消费者1消费消息hello work7
消费者1消费消息hello work8
消费者2消费消息hello work9
消费者1消费消息hello work10
消费者1消费消息hello work11
消费者1消费消息hello work12
消费者1消费消息hello work13
消费者1消费消息hello work14
消费者1消费消息hello work15
消费者1消费消息hello work16
消费者2消费消息hello work17
消费者1消费消息hello work18
消费者1消费消息hello work19
消费者1消费消息hello work20
消费者1消费消息hello work21
消费者1消费消息hello work22
消费者1消费消息hello work23
消费者1消费消息hello work24
消费者2消费消息hello work25
消费者1消费消息hello work26
消费者1消费消息hello work27
消费者1消费消息hello work28
消费者1消费消息hello work29
消费者1消费消息hello work30
消费者1消费消息hello work31
消费者1消费消息hello work32
消费者2消费消息hello work33
消费者1消费消息hello work34
消费者1消费消息hello work35
消费者1消费消息hello work36
消费者1消费消息hello work37
消费者1消费消息hello work38
消费者1消费消息hello work39
消费者2消费消息hello work40
消费者1消费消息hello work41
消费者1消费消息hello work42
消费者1消费消息hello work43
消费者1消费消息hello work44
消费者1消费消息hello work45
消费者1消费消息hello work46
消费者1消费消息hello work47
消费者2消费消息hello work48
消费者1消费消息hello work49
消费者1消费消息hello work50

这下正常多了,消费者1处理了44条消息,消费者2处理了6条消息,实现了“能者多劳”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值