以前的文章说过,若是使用 RabbitMQ,尽量使用框架,而不要去使用 RabbitMQ 提供的 Java 版客户端。程序员
细提及来,其实仍是由于 RabbitMQ 客户端的使用有不少的注意事项,稍微不注意,就容易翻车。缓存
我是 2013 年就开始用起了 RabbitMQ,一路使用,一路和它一块儿成长。当时,因为用的早,市面上也没有特别成熟的 RabbitMQ 客户端框架。因此,不得已之下,只好本身作了一套客户端。安全
在这其中,正好也有了许多独特的经验也和你们分享一下,以避免后来者陷入“后人哀之而不鉴之,亦使后人而复哀后人也”的套娃中。服务器
1、那么,就先从网络链接开始吧
1. 应该长久生存的链接
在 RabbitMQ 中,因为须要客户端和服务器端进行握手,因此致使客户端和服务器端的链接若是要成功建立,须要很高的成本。网络
每个链接的建立至少须要 7 个 TCP 包,这还只是普通链接。若是须要 TLS 的参与,则 TCP 包会更多。框架
并且,RabbitMQ 中主要是以 Channel 方式通讯,因此,每次建立完 Connection 网络链接,还得建立 Channel,这又须要 2 个 TCP 包。异步
若是,每次用完,再把链接关闭,首先还要关闭已经建立的 Channel,这也须要 2 个 TCP 包。oop
而后,再关闭已经创建好的 Connection 链接,又须要 2 个 TCP 包。性能
我们算算,若是一个链接从建立到关闭,一共须要多少个 TCP 包?url
7 + 2 + 2 + 2 = 13
一共须要 13 个包。这个成本是很昂贵的。
因此,在 RabbitMQ 中,链接最好缓存起来,重复使用更好。
2. Channel 仍是独占好
在 RabbitMQ 本身的客户端中,Channel 出于性能缘由,并非线程安全的。
而若是我们为了线程共用,给 Channel 人为的在外部加上锁,自己就和 RabbitMQ 的 Channel 设计意图是冲突的。
因此,最好的办法就是一个线程一个 Channel。
3. Channel 最好也别关
就像链接应该缓存起来那样,Channel 的打开和关闭也须要时间成本,并且没有必要去从新建立 Channel,因此,Channel 也应该缓存起来重用。
4. 别把消费和发送的链接搞在一块儿
把消费和发送的链接搞在一块儿,这是个很容易犯的错误!
咱们用 RabbitMQ 的时候,咱们本身的系统自己大部分都是既要发消息也要收消息的。对于这种状况,有不少程序员走了极端:
他们以为 RabbitMQ 链接成本高,因此省着用。因而就把发消息和收消息的链接混在一块儿,使用同一个 TCP 链接。
这极可能会埋一个大雷。
由于,当咱们发消息很频繁的时候,咱们收消息也是走的同一个 TCP 通道,收完了消息,客户端还要给 RabbitMQ 服务器端一个 ACK。
RabbitMQ 服务器端,对于每一个 TCP 链接都会分配专门的进程,若是遇到这个进程繁忙,这个 ACK 极可能被丢弃,又或者等待处理的时间过长。而这种状况又会致使 RabbitMQ 中的未确认消息会被堆积的愈来愈多,影响到整套系统。
因此,消费和发送的链接必须分开,各干各的事情。
5. 别搞太多链接和 Channel,RabbitMQ 的 Web 受不了
RabbitMQ 的 Web 插件会收集不少链接,和其对应 Channel 的相关数据。
若是链接和 Channel 堆积太多了,整个 Web 打开会很是慢,几乎没法对 RabbitMQ 进行管理。因此,要注意限制链接和 Channel 的数量。
2、消息很宝贵,千万别乱抛弃哦
用来通讯的消息是很宝贵的。
由于每条消息均可能携带了关键的数据和信息。因此,保证消息不丢失,须要根据消息的重要性,采起不少的措施。
1. 当心,Queue 存在再发消息
一条消息,在 RabbitMQ 中会先发到 Exchange,再由 Exchange 交给对应的 Queue。
而当 Queue 不存在,或者没匹配到合适的 Queue 的时候,默认就会把消息发到系统中的 /dev/null 中。
并且还不会报错。
这个坑当年把我坑惨了!我猜这个坑无数人踩过吧。
因此,在发送消息的时候,最好经过 declare passive 这种方法去探测下队列是否存在,保证消息发送不会丢的莫名其妙。
2. 收到消息请告诉我
在使用 RabbitMQ 客户端的时候,发送消息,必定要考虑使用 confirm 机制。
这个机制就是当消息收到了,RabbitMQ 会往客户端发送一个通知,客户端收到这个通知后,若是存在一个 confirm 处理器,那么就会回调这个处理器处理。这时候,咱们就能确保消息是被中间件收到了。
因此,必定要考虑使用 confirm 处理器去确保消息被 RabbitMQ 服务器收到。
3. 有时候消息出了问题我也须要知道
在某些业务里,可能须要知道消息发送失败的场景,以便执行失败的处理逻辑。这时候,就要考虑 RabbitMQ 客户端的 return 机制。
这个机制就是当消息在服务器端路由的时候出现了错误,好比没有 Exchange、或者 RoutingKey 不存在,则 RabbitMQ 会返回一个响应给客户端。客户端收到后会回调 return 的处理器。这时候,客户端所在系统就能感知到这种错误了,从而进行对应的处理。
4. 为了必定不丢消息我也是拼了
还有的时候,消息须要处理强一致性这种事务性质的业务。这时候,就必须开启 RabbitMQ 的事务模式。可是,这个模式会致使总体 RabbitMQ 的性能降低 250 倍。
通常没有必要,不建议开启。
5. 把消息写到磁盘上
通常来讲,为了防止消息丢失,须要在 RabbitMQ 服务器收到消息的时候,先持久化消息到磁盘上,防止服务器状态出现问题,消息丢失。
可是,持久化消息,必须先持久化队列,持久化队列完还不行,还必须把消息的 delivery mode 设置为 2,这样才能把消息存到磁盘。可是,这种行为会让整个 RabbitMQ 的性能降低 60%。
这种能够根据实际状况进行抉择。
3、对于收消息这件事,别由着性子来
1. 能一次拿多个干吗要一次只拿一个
不少时候,一些 RabbitMQ 的新手,以为若是在一个 mainloop 相似的无限循环里,去主动获取消息,会更加及时的获取到消息,也会拥有更加出色的性能。因此,他们会使用 get 这种行为去取代 consume 这种行为。
这时候,他们其实已经踩进了大坑。
为了能主动 get 服务器消息,不少新手会去写一个无限循环,而后不断尝试去 RabbitMQ 服务器端获取消息。可是,get 方法,实际上是只去获取了队列中的第一条消息。
而采用 consume 方式呢,它的默认方式是只要有消息,就会批量的拿,直到拿光全部还没消费过的消息。
一个是一条条拿,一个是批量拿,哪一个效率更高一目了然。
因此,尽可能采用 consume 方式获取消息。
2. 拿消息也要讲方法论的
消费消息的时候,其实最难掌握的就是:
一次咱们到底要取多少条消息?
对于 RabbitMQ 来说,若是咱们不对消费行为作限制,他会有多少消息就获取多少消息。这就形成了一个问题:
若是消息过多,咱们一次性把消息读取到内存,极可能就会把应用的内存挤崩掉。
因此,咱们要对这种状况作一些限制。
这时候,须要限制一次获取消息的数量,通常来说,当咱们的业务是异步发送,异步消费,不须要实时给回响应的时候,经验数据是一次获取 1000 条。
固然,系统和系统不同,硬件条件也不同,你们能够根据实际的状况来设置一次性获取的消息数量。
重点要说说同步。
在不少时候,咱们须要经过 RabbitMQ 传送消息,并能经过临时队列等技巧去实时返回处理结果。这时候,就没办法一次抓多条数据进行处理了,由于,有发送端在等处理结果,依次处理,再依次返回,黄花菜都凉了。
并且大部分时候,这种同步等待响应的业务是有顺序要求的。因此,也不能并行同时抓出多条信息处理。那么,彼时,设置每次只消费一条消息就是理所应当的了。
最后
从上面的内容中,你也看到了,RabbitMQ 客户端若是要使用,对新手是多可恶的一件事情,各类坑,各类复杂性。
因此,若是你以为 Spring 之类的 AMQP 客户端框架合你心意,那么你就使用它。
可是,Spring 的东西有个毛病,若是你要用它,你的应用必须也都要用 Spring。有些时候,也没有这种必要。这时候,你就能够根据我说的这些注意事项和经验,本身开发一套 RabbitMQ 的封装框架,去下降 RabbitMQ 的使用门槛。
你好,我是四猿外,一家上市公司的技术总监,管理的技术团队一百余人。
我从一名非计算机专业的毕业生,转行到程序员,一路打拼,一路成长。
我会把本身的成长故事写成文章,把枯燥的技术文章写成故事。
最后给各位程序员分享一些技术资料: 这三本资料,或许能帮你进大厂拿高薪
欢迎关注个人公众号