【RabbitMQ-3】连接池的配置

java NIO是IO的多路复用,Channel连接是TCP的多路复用。那么他们有什么关系呢?
NIO是服务器开启一个线程,在内核中使用select()进行轮询管理一些socket,当socket数据准备好时,会通知应用程序进行读写请求。系统之间那点事-NIO(内附IO模型)-IO/NIO/AIO到底是什么服务器看起来就好像是一个socket在通信,实现了多路复用。
channel复用TCP连接,是为了避免TCP连接创建和销毁的性能损耗,而多个channel使用一个tcp连接。

1. rabbitmq的connection连接池

RabbitMQ.png

我们看到一个Connection里面可以包含多个channel。那么我们在连接broker时,connectionchannel的关系是什么?

1.1 问题提出

1.1.1 Connection对象管理以及性能

Connection连接本质上就是TCP连接,系统之间那点事-问题驱动-TCP的连接和关闭是比较耗费时间的。我们可以使用一个单例的Connection对象创建多个Channel来实现数据传输,但是在channel信息比较大的情况下,Connection带宽会限制消息的传输。那么需要设计Connection池,将流量分摊到不同的connection上。

1.1.2 Channel对象管理以及性能

Channel对象的创建和销毁也是非常耗时的,推荐共享使用Channel,而不是每次都创建和销毁Channel。那如何设计一个channel线程池呢?

1.2 官网解读

官网对于Connection的解读:

AMQP 0-9-1 connections are typically long-lived. AMQP 0-9-1 is an application level protocol that uses TCP for reliable delivery. Connections use authentication and can be protected using TLS. When an application no longer needs to be connected to the server, it should gracefully close its AMQP 0-9-1 connection instead of abruptly closing the underlying TCP connection.

大概意思就是:AMQP 0-9-1一般是一个TCP的长链接,当应用程序不再需要连接到服务器时,应该正常关闭AMQP 0-9-1连接而不是关闭TCP连接。

官网对于Channel的解读:

Some applications need multiple connections to the broker. However, it is undesirable to keep many TCP connections open at the same time because doing so consumes system resources and makes it more difficult to configure firewalls. AMQP 0-9-1 connections are multiplexed withchannels that can be thought of as "lightweight connections that share a single TCP connection".
Every protocol operation performed by a client happens on a channel. Communication on a particular channel is completely separate from communication on another channel, therefore every protocol method also carries a channel ID (a.k.a. channel number), an integer that both the broker and clients use to figure out which channel the method is for.
A channel only exists in the context of a connection and never on its own. When a connection is closed, so are all channels on it.

For applications that use multiple threads/processes for processing, it is very common to open a new channel per thread/process and not share channels between them.

大概的意思就是:一些应用需要同时创建多个连接到broker也就是RabbitMQ服务器上。然而因为防火墙的存在,很难同时创建多个连接。 AMQP 0-9-1连接使用多个channel连接实现对单一Connection的复用。
客户端的每一个协议操作都发送在channel上。每个协议方法携带者channel IDbrokerclient使用channel ID来确定方法对应的channel。因此实现channel之间的数据隔离。
channel不能单独存在,仅存在connection上下文中。当connection关闭时,channel也会关闭。
多线程/进程之间打开一个channel但不共享channels是很普遍的。

通道和并发注意事项(线程安全)

As a rule of thumb, sharing Channel instances between threads is something to be avoided. Applications should prefer using a Channel per thread instead of sharing the same Channel across multiple threads.

线程之间共享channel是无法避免的,应用程序跟喜欢每个线程使用一个channel而不是跨线程共享相同的channel

A classic anti-pattern to be avoided is opening a channel for each published message. Channels are supposed to be reasonably long-lived and opening a new one is a network round-trip which makes this pattern extremely inefficient.

要避免一个反例,为每一个发布的消息分配一个channel,开辟一个新的channel需要一个网络的往返,这种模式是很低效的。channel保持合理的存活时间。

It is possible to use channel pooling to avoid concurrent publishing on a shared channel: once a thread is done working with a channel, it returns it to the pool, making the channel available for another thread. Channel pooling can be thought of as a specific synchronization solution. It is recommended that an existing pooling library is used instead of a homegrown solution. For example, Spring AMQP which comes with a ready-to-use channel pooling feature.

(敲黑板,划重点)可以使用channel pool来避免共享channel上并发发布:一旦一个线程使用完了channel,那么它将返回到pool中。其他线程便可使用这个Channel。线程池是一个解决方案,可以使用 Spring AMQP线程池而不是自己开发。

总结:频繁建立TCP连接和channel连接是消耗性能的,于是我们希望可以共享connection或者channel。达到连接的复用。

1.3 Spring AMQP线程池配置

版本spring-rabbit:2.0.2.RELEASE

1.3.1 ConnectionFactory连接工厂

这个ConnectionFactory是Spring AMQP定义的连接工厂,负责创建连接。而CacheConnectionFactory实现支持对这些通道的缓存。

  private static ConnectionFactory newRabbitConnectionFactory() {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setAutomaticRecoveryEnabled(false);
        return connectionFactory;
    }

参数分析:

1. 开启confirm机制。
connectionFactory.setPublisherConfirms(true); connectionFactory.setPublisherReturns(true);
为了消息的不丢失,生产者可以设置事务或者confirm异步通知。但是事务性能并不是很好,所以一般使用confirm模式。
区别:(confirm保证达到交换机,return保证交换机到达队列)
如果消息没有到exchange,则confirm回调,ack=false
如果消息到达exchange,则confirm回调,ack=true
exchangequeue成功,则不回调return
exchangequeue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)

*注意:设置PublisherReturns状态为true,那么需要设置 rabbitTemplate.setMandatory(true);
具体如何保证消息不丢失,请参考RabbitMQ的消息不丢失机制

2. 配置模式
缓存模式一般两种:
connectionFactory.setCacheMode(CachingConnectionFactory.CacheMode.CONNECTION);

  public static enum CacheMode {
        CHANNEL,
        CONNECTION;

        private CacheMode() {
        }
    }

2.1 CHANNEL模式
程序运行期间ConnectionFactory只维护着一个connection,但是可以含有多个channel,操作rabbitmq之前必须先获取一个channel,否则将会阻塞。
相关参数配置:

connectionFactory.setChannelCacheSize(10);
设置每个Connection中的缓存Channel的数量。操作rabbitmq之前(send/receive message等)要先获取到一个Channel,获取Channel时会先从缓存中找闲置的Channel,如果没有则创建新的Channel,当Channel数量大于缓存数量时,多出来没法放进缓存的会被关闭。

connectionFactory.setChannelCheckoutTimeout(600);
单位毫秒,当这个值大于0时,ChannelCacheSize代表的是缓存的数量上限,当缓存获取不到可用的channel时,不会创建新的channel会等待指定的时间,若到时间后还获取不到可用的channel,直接抛出AmqpTimeoutException

注意:在CONNECTION模式,这个值也会影响获取Connection的等待时间,超时获取不到Connection也会抛出AmqpTimeoutException异常。

2.2 CONNECTION模式

CONNECTION模式。在这个模式下允许创建多个connection,会缓存一定数量的connection,每个connection中同样缓存着一些channel

相关参数配置:

connectionFactory.setConnectionCacheSize(3);
仅在CONNECTION模式下使用,指定connection缓存数量。

connectionFactory.setConnectionLimit(10);
仅在CONNECTION模式下使用,指定connection数量上限。

官网对于是否关闭channel解答:

Channels used within the framework (e.g. RabbitTemplate) will be reliably returned to the cache. If you create channels outside of the framework, (e.g. by accessing the connection(s) directly and invoking createChannel()), you must return them (by closing) reliably, perhaps in a finally block, to avoid running out of channels.

注意:若使用RabbitTemplate创建channel,那么无需关闭,但是自己新建connection创建channel,则需要手动关闭!避免channel溢出。

ConnectionFactory 代码:

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host, port);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        //设置virtualHost。
        connectionFactory.setVirtualHost("/");
        //消息的确认机制(confirm);
        connectionFactory.setPublisherConfirms(true);
        connectionFactory.setPublisherReturns(true);
        //setCacheMode:设置缓存模式,共有两种,CHANNEL和CONNECTION模式。
        //1、CONNECTION模式,这个模式下允许创建多个Connection,会缓存一定数量的Connection,每个Connection中同样会缓存一些Channel,
        // 除了可以有多个Connection,其它都跟CHANNEL模式一样。
        //2、CHANNEL模式,程序运行期间ConnectionFactory会维护着一个Connection,
        // 所有的操作都会使用这个Connection,但一个Connection中可以有多个Channel,
        // 操作rabbitmq之前都必须先获取到一个Channel,
        // 否则就会阻塞(可以通过setChannelCheckoutTimeout()设置等待时间),
        // 这些Channel会被缓存(缓存的数量可以通过setChannelCacheSize()设置);
        connectionFactory.setCacheMode(CachingConnectionFactory.CacheMode.CONNECTION);   //设置CONNECTION模式,可创建多个Connection连接
        //设置每个Connection中缓存Channel的数量,不是最大的。操作rabbitmq之前(send/receive message等)
        // 要先获取到一个Channel.获取Channel时会先从缓存中找闲置的Channel,如果没有则创建新的Channel,
        // 当Channel数量大于缓存数量时,多出来没法放进缓存的会被关闭。
        connectionFactory.setChannelCacheSize(10);
        //单位:毫秒;配合channelCacheSize不仅是缓存数量,而且是最大的数量。
        // 从缓存获取不到可用的Channel时,不会创建新的Channel,会等待这个值设置的毫秒数
        //同时,在CONNECTION模式,这个值也会影响获取Connection的等待时间,
        // 超时获取不到Connection也会抛出AmqpTimeoutException异常。
        connectionFactory.setChannelCheckoutTimeout(600);

        //仅在CONNECTION模式使用,设置Connection的缓存数量。
        connectionFactory.setConnectionCacheSize(3);
        //setConnectionLimit:仅在CONNECTION模式使用,设置Connection的数量上限。
        connectionFactory.setConnectionLimit(10);
        return connectionFactory;
    }

RabbitTemplate 代码:

  @Autowired
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        //客户端开启confirm模式
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause);
            }
        });
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message);
            }
        });
        return rabbitTemplate;
    }

2.2 消费发送和接收使用不同的Connection

当一个服务同时作为消息的发送端和接收端时,建议使用不同的Connection避免一方出现故障或者阻塞影响另一方。

只需要在RabbitTemplate中加入下面的配置,那么RabbitTemplate在创建Connection时,会根据这个boolean的值,选择使用ConnectionFactory本身或者ConnectionFactory中的publisherConnectionFactory(也即是一个ConnectionFactory)来创建。

rabbitTemplate.setUsePublisherConnection(true);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 142,461评论 1 300
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 61,089评论 1 256
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 94,078评论 0 212
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 40,938评论 0 175
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 48,698评论 1 255
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,733评论 1 175
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,340评论 2 268
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,138评论 0 165
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 28,949评论 6 229
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,493评论 0 213
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,269评论 2 214
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,591评论 1 229
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,216评论 0 31
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,107评论 2 213
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,485评论 3 204
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,615评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,006评论 0 167
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,496评论 2 230
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,572评论 2 230

推荐阅读更多精彩内容

  • http://liuxing.info/2017/06/30/Spring%20AMQP%E4%B8%AD%E6%...
    sherlock_6981阅读 15,397评论 2 11
  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,033评论 0 10
  • 本文章翻译自http://www.rabbitmq.com/api-guide.html,并没有及时更新。 术语对...
    joyenlee阅读 7,414评论 0 3
  • 昨天周四,早上起来也是七点多了,没出操也被班主任给抓了,于是说是下午四点半去操场跑步,起来以后就去上课去了,第一节...
    坚志阅读 181评论 0 0
  • 一 初春的无界村,山青水秀,阡陌塘池,柏杨垂柳,一幅世外桃源的田园风光。 萧宁走在村外的田埂上,眼睛环视着四周,目...
    摄文凡尘阅读 397评论 0 9