分布式消息中间件

RabbitMQ是用Erlang语言编写的分布式消息中间件,常常用在大型网站中作为消息队列来使用,主要目的是各个子系统之间的解耦和异步处理。消息中间件的基本模型是典型的生产者-消费者模型,生产者发送消息到消息队列,消费者监听消息队列,收到消息后消费处理。

在使用RabbitMQ做消息分发时,主要有三个概念要注意:Exchange,RoutingKey,Queue。

Exchange可以理解为交换器,RoutingKey可以理解为路由,Queue作为真实存储消息的队列和某个Exchange绑定,具体如何路由到感兴趣的Queue则由Exchange的三种模式决定:

  • fanout
  • topic
  • direct

Exchange为fanout时,生产者往此Exchange发送的消息会发给每个和其绑定的Queue,此时RoutingKey并不起作用;Exchange为topic时,生产者可以指定一个支持通配符的RoutingKey(如demo.*)发向此Exchange,凡是Exchange上RoutingKey满足此通配符的Queue就会收到消息;direct类型的Exchange是最直接最简单的,生产者指定Exchange和RoutingKey,然后往其发送消息,消息只能被绑定的满足RoutingKey的Queue接受消息。(通常如果不指定RoutingKey的具体名字,那么默认的名字其实是Queue的名字)

Concurrency与Prefetch

在通常的使用中(Java项目),我们一般会结合spring-amqp框架来使用RabbitMQ,spring-amqp底层调用RabbitMQ的java client来和Broker交互,比如我们会用如下配置来建立RabbitMQ的连接池、声明Queue以及指明监听者的监听行为:

<rabbit:connection-factory id="connectionFactory" />

<!-- template非必须,主要用于生产者发送消息-->
<rabbit:template id="template" connection-factory="connectionFactory" /> <rabbit:queue name="remoting.queue" />
<rabbit:listener-container connection-factory="connectionFactory" concurrency="2" prefetch="3">
<rabbit:listener ref="listener" queue-names="remoting.queue" />
</rabbit:listener-container>

listener-container可以设置消费者在监听Queue的时候的各种参数,其中concurrency和prefetch是本篇文章比较关心的两个参数,以下是spring-amqp文档的解释:

prefetchCount(prefetch)
The number of messages to accept from the broker in one socket frame. The higher this is the faster the messages can be delivered, but the higher the risk of non-sequential processing. Ignored if the acknowledgeMode
is NONE. This will be increased, if necessary, to match the txSize

concurrentConsumers(concurrency)

The number of concurrent consumers to initially start for each listener.

简单解释下就是concurrency设置的是对每个listener在初始化的时候设置的并发消费者的个数,prefetch是每次从一次性从broker里面取的待消费的消息的个数,上面的配置在监控后台看到的效果如下:

 
 

图中可以看出有两个消费者同时监听Queue,但是注意这里的消息只有被一个消费者消费掉就会自动ack,另外一个消费者就不会再获取到此消息,Prefetch Count为配置设置的值3,意味着每个消费者每次会预取3个消息准备消费。每个消费者对应的listener有个Exclusive参数,默认为false, 如果设置为true,concurrency就必须设置为1,即只能单个消费者消费队列里的消息,适用于必须严格执行消息队列的消费顺序(先进先出)。

源码剖析

这里concurrency的实现方式不看源码也能猜到,肯定是用多线程的方式来实现的,此时同一进程下打开的本地端口都是56278.下面看看listener-contaner对应的org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer的源码:

protected int initializeConsumers() {
int count = 0;
synchronized (this.consumersMonitor) {
if (this.consumers == null) {
this.cancellationLock.reset();
this.consumers = new HashMap<BlockingQueueConsumer, Boolean>(this.concurrentConsumers);
for (int i = 0; i < this.concurrentConsumers; i++) {
BlockingQueueConsumer consumer = createBlockingQueueConsumer();
this.consumers.put(consumer, true);
count++;
}
}
}
return count;
}

container启动的时候会根据设置的concurrency的值(同时不超过最大值)创建n个BlockingQueueConsumer。

protected void doStart() throws Exception {
//some code
synchronized (this.consumersMonitor) {
int newConsumers = initializeConsumers(); //some code
Set<AsyncMessageProcessingConsumer> processors = new HashSet<AsyncMessageProcessingConsumer>();
for (BlockingQueueConsumer consumer : this.consumers.keySet()) {
AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer);
processors.add(processor);
this.taskExecutor.execute(processor);
}
//some code
}
}

在doStart()方法中调用initializeConsumers来初始化所有的消费者,AsyncMessageProcessingConsumer作为真实的处理器包装了BlockingQueueConsumer,而AsyncMessageProcessingConsumer其实实现了Runnable接口,由this.taskExecutor.execute(processor)来启动消费者线程。

private final class AsyncMessageProcessingConsumer implements Runnable {
private final BlockingQueueConsumer consumer;
private final CountDownLatch start;
private volatile FatalListenerStartupException startupException; private AsyncMessageProcessingConsumer(BlockingQueueConsumer consumer) {
this.consumer = consumer;
this.start = new CountDownLatch(1);
} //some code
@Override
public void run() {
//some code
}
}

那么prefetch的值意味着什么呢?其实从名字上大致能看出,BlockingQueueConsumer内部应该维护了一个阻塞队列BlockingQueue,prefetch应该是这个阻塞队列的长度,看下BlockingQueueConsumer内部有个queue,这个queue不是对应RabbitMQ的队列,而是Consumer自己维护的内存级别的队列,用来暂时存储从RabbitMQ中取出来的消息:

private final BlockingQueue<Delivery> queue;

public BlockingQueueConsumer(ConnectionFactory connectionFactory,
MessagePropertiesConverter messagePropertiesConverter,
ActiveObjectCounter<BlockingQueueConsumer> activeObjectCounter, AcknowledgeMode acknowledgeMode,
boolean transactional, int prefetchCount, boolean defaultRequeueRejected,
Map<String, Object> consumerArgs, boolean exclusive, String... queues) {
//some code
this.queue = new LinkedBlockingQueue<Delivery>(prefetchCount);
}

BlockingQueueConsumer的构造函数清楚说明了每个消费者内部的队列大小就是prefetch的大小。

业务问题

前面说过,设置并发的时候,要考虑具体的业务场景,对那种对消息的顺序有苛刻要求的场景不适合并发消费,而对于其他场景,比如用户注册后给用户发个提示短信,是不太在意哪个消息先被消费,哪个消息后被消费,因为每个消息是相对独立的,后注册的用户先收到短信也并没有太大影响。

设置并发消费除了能提高消费的速度,还有另外一个好处:当某个消费者长期阻塞,此时在当前消费者内部的BlockingQueue的消息也会被一直阻塞,但是新来的消息仍然可以投递给其他消费者消费,这种情况顶多会导致prefetch个数目的消息消费有问题,而不至于单消费者情况下整个RabbitMQ的队列会因为一个消息有问题而全部堵死。所有在合适的业务场景下,需要合理设置concurrency和prefetch值。

转自:https://www.jianshu.com/p/04a1d36f52ba

【RabbitMQ】Concurrency、Prefetch、exclusive的更多相关文章

  1. 【Android】【录音】Android录音--AudioRecord、MediaRecorder

    [Android][录音]Android录音--AudioRecord.MediaRecorder Android提供了两个API用于实现录音功能:android.media.AudioRecord. ...

  2. riot.js教程【三】访问DOM元素、使用jquery、mount输入参数、riotjs标签的生命周期

    前文回顾 riot.js教程[二]组件撰写准则.预处理器.标签样式和装配方法 riot.js教程[一]简介 访问DOM元素 你可以通过this.refs对象访问dom元素 而且还有大量的属性简写方式可 ...

  3. 【Bootstrap】一个兼容IE8、谷歌等主流浏览器的受众门户式风格页面

    上一次写的<[Bootstrap]一个兼容IE8.谷歌等主流浏览器的受众巨幕式风格页面>(点击打开链接) 部分老一辈的需求可能对这样的后现代的风格并不惬意, 没关系,我们全然能够改变布局 ...

  4. 【转】C#中的==、Equal、ReferenceEqual

    [转]C#中的==.Equal.ReferenceEqual 转载自: http://www.cnblogs.com/zagelover/articles/2741409.html 1. Refere ...

  5. 【转】php 之 array_filter、array_walk、array_map的区别

    [转]php 之 array_filter.array_walk.array_map的区别 原文:https://blog.csdn.net/csdnzhangyiwei/article/detail ...

  6. 【Android】自己定义View、画家(画布)Canvas与画笔Paint的应用——绘图、涂鸦板app的实现

    利用一个简单的绘图app来说明安卓的图形处理类与自己定义View的应用. 例如以下图,有一个供用户自己随意绘图.涂鸦的app. 这里不做那么花俏了,仅提供黑白两色.但能够改变笔尖的粗细. 实质上这里的 ...

  7. 【入门】广电行业DNS、DHCP解决方案详解(三)——DNS部署架构及案

    [入门]广电行业DNS.DHCP解决方案详解(三)——DNS部署架构及案 DNS系统部署架构 宽带业务DNS架构 互动业务DNS架构 案例介绍 案例一 案例二 本篇我们将先介绍DNS系统部署架构体系, ...

  8. 【jvm】07-偏向锁、轻量锁、重量锁到底是啥?

    [jvm]07-偏向锁.轻量锁.重量锁到底是啥? 欢迎关注b站账号/公众号[六边形战士夏宁],一个要把各项指标拉满的男人.该文章已在github目录收录. 屏幕前的大帅比和大漂亮如果有帮助到你的话请顺 ...

  9. 【rabbitmq】rabbitmq集群环境搭建

    安装rabbitmq-server 总共有3台虚拟机,都安装有rabbitmq服务,安装过程可参考: [rabbitmq]Centos7 下安装rabbitmq 创建用户和vhost 说明: 此步骤不 ...

随机推荐

  1. mac的jvm调优工具

    安装好JDK之后调优工具所在位置为: /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/jvisualvm j ...

  2. .NET Core:.Net Core 百科

    ylbtech-.NET Core:.Net Core 百科 .NET Core是适用于 windows.linux 和 macos 操作系统的免费.开源托管的计算机软件框架,是微软开发的第一个官方版 ...

  3. linux使用apache发布静态html网页

    环境 centOS7+httpd 安装httpd 安装 #检查是否安装和httpd rpm -qa | grep httpd #如果没安装 yum -y install httpd 启动httpd并验 ...

  4. idhttpserver 下载文件

    procedure TForm30.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AR ...

  5. 牛顿法求极值及其Python实现

    最初对于牛顿法,我本人是一脸懵的.其基本原理来源于高中知识.在如下图所示的曲线,我们需要求的是f(x)的极值: 对于懵的原因,是忘记了高中所学的点斜式,直接贴一张高中数学讲义: 因为我们一路沿着x轴去 ...

  6. python 按二维数组的某行或列排序 (numpy lexsort)

    lexsort支持对数组按指定行或列的顺序排序:是间接排序,lexsort不修改原数组,返回索引. (对应lexsort 一维数组的是argsort a.argsort()这么使用就可以:argsor ...

  7. 普通ACL访问控制列表

    配置OSPF R1: R2: R3: R4: 在R1上查看OSPF的学习 测试R1与R4环回接口连通性 配置普通ACL访问控制列表: 先在R4配置密码用R1与R4建立telnet建立 密码huawei ...

  8. 深信达加密下禁用IME

    1.控制面板 2.管理工具 3.计算机管理 4.系统工具 5.任务计划程序 6.任务计划程序库 7.Microsoft 8.Windows 9.TextServicesFramework 10.MsC ...

  9. U盘修复教程--使用量产工具和芯片检测工具

    U盘修复教程(金士顿DT101) 本教程使用量产工具(量产可理解为强制恢复出厂设置),量产有风险(U盘彻底报废),根据教程所造成所有损失均与本文作者无关. ·第一步:下载ChipGenius_v4_0 ...

  10. word中迅速将表格一分为二 拆分表格快捷键ctrl+shift+enter 重复上一个命令快捷键f4

    这里说的是将一个表格拆分为两个表格 选择要拆分的行,快捷键ctrl+shift+enter,就拆分为两个表格了,是不是很快! 在多个表格需要拆分的时候,做一次这样的操作,然后不停的移动.F4,就可以了 ...