# rabbit-decoupling-v2 **Repository Path**: wzk9261/rabbit-decoupling-v2 ## Basic Information - **Project Name**: rabbit-decoupling-v2 - **Description**: RabbitMQ 代替 HttpClient,解耦系统 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2020-03-16 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### RabbitMQ 的应用场景 - 应用解耦 在高并发分布式环境下,由于来不及同步处理,通过使用消息队列,可以异步处理请求,从而缓解系统的压力。 举一个订单系统的例子:用户点击下订单,会触发以下业务逻辑流程: - 扣减库存 - 生成相应的订单 - 发短信通知等等 在业务发展初期这些逻辑可能放在一起同步执行,随着业务订单量增长,需要提升系统服务的性能,这时候可以将一些不需要立即生效的操作拆分出来异步执行,比如发短信通知等,这种场景就可以使用消息队列MQ。 本质还是通过异步来解决同步的系统压力,所以我们在做架构设计的时候有一个原则:能异步的就尽量不要同步。 - 流量削峰 要对流量进行削峰,最容易想到的解决方案就是用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送,中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去。 消息队列中间件主要解决应用耦合,异步消息, 流量削锋等问题。常用消息队列系统:目前在生产环境,使用较多的消息队列有 ActiveMQ、RabbitMQ、 ZeroMQ、Kafka、MetaMQ、RocketMQ 等。 在这里,消息队列就像“水库”一样,拦蓄上游的洪水,削减进入下游河道的洪峰流量,从而达到减免洪水灾害的目的。 ### RabbitMQ 参数作用 - ready 预备状态 如果此状态的队列较多,可以认为 - 消费者的处理能力不足,只能取走prefetch_count数量之内的消息队列,超出此范围的队列都会处于ready状态。可以通过增加消费者来解决。 - 消费者挂掉,重启消费者后会继续消费掉这些ready的消息队列。 - unacked 未确认的,未答复的 如果此状态的队列较多,可以认为: - 消费者取走消息后没有及时做消息确认,对于开启手动确认机制的,不进行ack则消息会一直以`unacked`状态留在队列中。 - 消费者处理能力不足。生产者投放消息的速度较快,当消费者按照`prefetch_count`设置的值取走相应数量的消息时,这些消息都会暂时处于`unacked`状态。 - 用一个例子来说明 两个系统,生产者系统P,消费者系统C,C通过监听消息队列,消费P发送过去的消息。C突然宕机,此时查看rabbitmq后台管理页面,会发现C监听的队列上ready一栏的数量激增。过一段时间,C重新启动,恢复监听,会发现ready一栏数量下降,很快递减为0;与此同时unacked一栏的数量随之增加,然后也逐渐递减为0. ### RabbitMQ的优势 为什么使用RabbitMQ而不是使用异步+HttpClient通知的方法? - RabbitMQ只需统一消息队列的名称和消息的类型(String Map List等),而不需要了解接收方的URL。 - RabbitMQ可以采用广播的形式,一个生产者对应多个消费者。比如用户注册成功后,可以将用户信息通过队列广播通知短信发送系统和邮件发送系统,一次发送多次消费。而HttpClient需要发送两次请求才能通知到两个系统。 - 通过ACK机制保证消息投递的可靠性。即使消费者宕机,没有被消费的消息也可以被暂时存储在RabbitMQ中,等到消费者重启后,再消费这些堆积的队列。这一点是HttpClient做不到的。 - ACK机制确实显著提高了消息消费的保障性,是不是只要这样就万无一失了?当然不是,设想如果队列服务器宕机,那么存储在队列中的消息都会丢失。RabbitMQ通过持久化消息到磁盘来保障消息不会丢失,队列服务器宕机后,服务重启,会从磁盘上读取消息来恢复宕机之前的队列状态。为了达到持久化的目的,我们需要将队列和消息都设置为持久化。 在spring-amqp源码中,Queue的构造函数Queue(String name)默认是持久化的,第二个参数就是durable ```java /** * The queue is durable, non-exclusive and non auto-delete. * * @param name the name of the queue. */ public Queue(String name) { this(name, true, false, false); } ``` Exchange的实现类的构造函数AbstractExchange(String name)默认也是持久化的。 ### RabbitMQ的概念 #### 队列 由Queue的构造函数源码可以很清楚地了解队列的特性 ```java /** * Construct a new queue, given a name, durability flag, and auto-delete flag, and arguments. * @param name the name of the queue - must not be null; set to "" to have the broker generate the name. * @param durable true if we are declaring a durable queue (the queue will survive a server restart) * @param exclusive true if we are declaring an exclusive queue (the queue will only be used by the declarer's * connection) * @param autoDelete true if the server should delete the queue when it is no longer in use * @param arguments the arguments used to declare the queue */ public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map arguments) { super(arguments); Assert.notNull(name, "'name' cannot be null"); this.name = name; this.actualName = StringUtils.hasText(name) ? name : (Base64UrlNamingStrategy.DEFAULT.generateName() + "_awaiting_declaration"); this.durable = durable; this.exclusive = exclusive; this.autoDelete = autoDelete; } ``` 队列有以下几个属性: - 名称。不能为null,否则设置为"" - 持久化。如果设置为true,则消息会被存储到磁盘上,一直等到消费者重启。 - 排外性。如果设置为true,则该队列只能被声明它的connection使用;项目A与rabbitmq的连接为`rabbitConnectionFactory#7dc51783`,项目B与rabbitmq的连接为`rabbitConnectionFactory#bf71cec`,那么在A中声明的队列QueueA只能在`rabbitConnectionFactory#7dc51783`进行生产消费,在项目B中是无法使用QueueA的。 - 自动删除。如果设置为true,则消息会在长期不使用后被清除。 通过 `RabbitTemplate#convertAndSend(String routingKey, final Object object)`可以将消息发送到队列上。路由键会通知一个或多个队列消费消息。 #### 交换机