几年前,为什么我撸了一套RabbitMQ客户端?
之前文章说过,如果使用 RabbitMQ,尽可能使用框架,而不要去使用 RabbitMQ 提供的 Java 版客户端。
细说起来,其实还是因为 RabbitMQ 客户端的使用有很多的注意事项,稍微不注意,就容易翻车。
我是 2013 年就开始用起了 RabbitMQ,一路使用,一路和它一起成长。当时,由于用的早,市面上也没有特别成熟的 RabbitMQ 客户端框架。所以,不得已之下,只好自己做了一套客户端。
在这其中,正好也有了许多独特的经验也和大家分享一下,以免后来者陷入“后人哀之而不鉴之,亦使后人而复哀后人也”的套娃中。
一、那么,就先从网络连接开始吧
1. 应该长久生存的连接
在 RabbitMQ 中,由于需要客户端和服务器端进行握手,所以导致客户端和服务器端的连接如果要成功创建,需要很高的成本。
每一个连接的创建至少需要 7 个 TCP 包,这还只是普通连接。如果需要 TLS 的参与,则 TCP 包会更多。
而且,RabbitMQ 中主要是以 Channel 方式通信,所以,每次创建完 Connection 网络连接,还得创建 Channel,这又需要 2 个 TCP 包。
如果,每次用完,再把连接关闭,首先还要关闭已经创建的 Channel,这也需要 2 个 TCP 包。
然后,再关闭已经建立好的 Connection 连接,又需要 2 个 TCP 包。
咱们算算,如果一个连接从创建到关闭,一共需要多少个 TCP 包?
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 的数量。
二、消息很宝贵,千万别乱抛弃哦
用来通信的消息是很宝贵的。
因为每条消息都可能携带了关键的数据和信息。所以,保证消息不丢失,需要根据消息的重要性,采取很多的措施。
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%。
这种可以根据实际情况进行抉择。
三、对于收消息这件事,别由着性子来
1. 能一次拿多个干嘛要一次只拿一个
很多时候,一些 RabbitMQ 的新手,觉得如果在一个 mainloop 类似的无限循环里,去主动获取消息,会更加及时的获取到消息,也会拥有更加出色的性能。所以,他们会使用 get 这种行为去取代 consume 这种行为。
这时候,他们其实已经踩进了大坑。
为了能主动 get 服务器消息,很多新手会去写一个无限循环,然后不断尝试去 RabbitMQ 服务器端获取消息。但是,get 方法,其实是只去获取了队列中的第一条消息。
而采用 consume 方式呢,它的默认方式是只要有消息,就会批量的拿,直到拿光所有还没消费过的消息。
一个是一条条拿,一个是批量拿,哪个效率更高一目了然。
所以,尽量采用 consume 方式获取消息。
2. 拿消息也要讲方法论的
消费消息的时候,其实最难掌握的就是:
一次我们到底要取多少条消息?
对于 RabbitMQ 来讲,如果我们不对消费行为做限制,他会有多少消息就获取多少消息。这就造成了一个问题:
如果消息过多,我们一次性把消息读取到内存,很可能就会把应用的内存挤崩掉。
所以,我们要对这种情况做一些限制。
这时候,需要限制一次获取消息的数量,一般来讲,当我们的业务是异步发送,异步消费,不需要实时给回响应的时候,经验数据是一次获取 1000 条。
当然,系统和系统不一样,硬件条件也不一样,大家可以根据实际的情况来设置一次性获取的消息数量。
重点要说说同步。
在很多时候,我们需要通过 RabbitMQ 传送消息,并能通过临时队列等技巧去实时返回处理结果。这时候,就没办法一次抓多条数据进行处理了,因为,有发送端在等处理结果,依次处理,再依次返回,黄花菜都凉了。
而且大部分时候,这种同步等待响应的业务是有顺序要求的。所以,也不能并行同时抓出多条信息处理。那么,彼时,设置每次只消费一条消息就是理所应当的了。
最后
从上面的内容中,你也看到了,RabbitMQ 客户端如果要使用,对新手是多可恶的一件事情,各种坑,各种复杂性。
所以,如果你觉得 Spring 之类的 AMQP 客户端框架合你心意,那么你就使用它。
但是,Spring 的东西有个毛病,如果你要用它,你的应用必须也都要用 Spring。有些时候,也没有这种必要。这时候,你就可以根据我说的这些注意事项和经验,自己开发一套 RabbitMQ 的封装框架,去降低 RabbitMQ 的使用门槛。
你好,我是四猿外。
一家上市公司的技术总监,管理的技术团队一百余人。
我从一名非计算机专业的毕业生,转行到程序员,一路打拼,一路成长。
我会把自己的成长故事写成文章,把枯燥的技术文章写成故事。
欢迎关注我的公众号,关注之后还可以获取算法、高并发等干货学习资料。
我建了一个读者交流群,里面大部分是程序员,一起聊技术、工作、八卦。欢迎加我微信,拉你入群。
几年前,为什么我撸了一套RabbitMQ客户端?的更多相关文章
- Xamarin+Prism小试牛刀:定制跨平台Outlook邮箱应用
通过本文你将学会如下内容: 1,如何使用Xamarin开发跨平台(Windows,Android,iOS)应用. 2,如何使用微软的登录界面登入Microsoft账号. 3,如何使用Outlook邮箱 ...
- 【WebMisCentral WMC】基于Extjs 4.2x的企业级用户授权认证中心系统(SSO+AM+SM),多租户SAAS应用
http://saas.chinacloudtech.com 题记 三年磨一剑,在企业信息化的道路上已经走了3年之久了,3年多时间里做了很多,突破了很多:有无奈和辛酸,也有收货与喜悦:自我价值也在不断 ...
- Nginx介绍
原文:http://www.aosabook.org/en/nginx.html 作者: Andrew Alexeev nginx(发音"engine x")是俄罗斯软件工程师Ig ...
- iOS网络模块优化(失败重发、缓存请求有网发送)
iOS开发中,一般都是通过AFN搭建一个简易的网络模块来进行与服务器的通信,这一模块要优化好没那么简单,需要花费很多时间与精力,仅仅根据这几年来的填坑经验,总结下这一块的需要注意的地方,也是给自己梳理 ...
- XX-NET史上最详细完整教程
转 https://www.cnblogs.com/phperkang/p/8780123.html 前言 XX-NET,系GAE类代理,即通过可用Google ip连接Google App Eng ...
- utf-8和Unicode的区别
链接 utf-8和Unicode到底有什么区别?是存储方式不同?编码方式不同?它们看起来似乎很相似,但是实际上他们并不是同一个层次的概念 要想先讲清楚他们的区别,首先应该讲讲Unicode的来由. 众 ...
- nginx架构与基础概念
1 Nginx架构 Nginx 高性能,与其架构有关. Nginx架构: nginx运行时,在unix系统中以daemon形式在后台运行,后台进程包含一个master进程和多个worker ...
- ascII、iso、utf-8、Unicode的区别
utf-8和Unicode到底有什么区别?是存储方式不同?编码方式不同?它们看起来似乎很相似,但是实际上他们并不是同一个层次的概念,utf-8是unicode的实现方式. 要想先讲清楚他们的区别,首先 ...
- ACM/OI 出题用
之前出题,很苦恼出数据和检查程序,因为很多繁琐的工作,还很可能小手一抖出问题. 最近又在出题...想起之前的对拍脚本,感觉不能更方便,于是撸了一套出题用的小工具,也学习了一点点的DOS命令 首先是输入 ...
随机推荐
- 【Spring】循环依赖
@ 目录 循环依赖 是什么? Spring是如何解决的? 源码分析 细节 循环依赖 是什么? 简单的来说就是对象a的属性中引用了对象b,对象b的属性中引用了对象c......最后引用到a. < ...
- 前端数据渲染及mustache模板引擎的简单实现
早期数据渲染的几种方式 在模板引擎没有诞生之前,为了用JS把数据渲染到页面上,诞生了一系列数据渲染的方式. 最最基础的,莫过于直接使用DOM接口创建所有节点. <div id="roo ...
- 记一次 .NET 某教育系统API 异常崩溃分析
一:背景 1. 讲故事 这篇文章起源于 搬砖队大佬 的精彩文章 WinDBg定位asp.net mvc项目异常崩溃源码位置 ,写的非常好,不过美中不足的是通览全文之后,总觉得有那么一点不过瘾,就是没有 ...
- Thinkphp树形菜单相关方法
1 <?php 2 3 /** 4 * Menu(菜单管理) 5 */ 6 namespace Admin\Controller; 7 use Common\Controller\Adminba ...
- 【网络协议】 RPC vs Http
- VRRP中的上层回的路由
如图,PC1和PC2处于不同网段,他们到AR3都有两条路可以选,现在要让他们到达AR3负载均衡,并且当AR1和 AR2的其中一个路由器挂了之后,他们仍然可以正常到达AR3. 他们去往AR3的VRRP的 ...
- WindowsPE 第五章 导出表
导出表 PE中的导出表存在于动态链接库文件里.导出表的主要作用是将PE中存在的函数导出到外部,以便其他人可以使用这些函数,实现代码重用. 5.1导出表的作用 代码重用机制提供了重用代码的动态链接库,它 ...
- Win64 驱动内核编程-17. MINIFILTER(文件保护)
MINIFILTER(文件保护) 使用 HOOK 来监控文件操作的方法有很多,可以在 SSDT 上 HOOK 一堆和 FILE 有关的函数,也可以对 FSD 进行 IRP HOOK,不过这些方法既不 ...
- docker文件系统分层存储原理
一,前言 众所周知,docker镜像技术的基础是联合文件系统(UnionFS),其文件系统是分层的,那它的分层机制是什么样的呢?共分为几种层呢?又是怎么工作的呢? 目前docker支持的联合文件系统有 ...
- 推荐一个不得不知道的 Visual Studio 快捷键
不得不说,Visual Studio 内置了很多非常棒的快捷键,借助于这些快捷键我们甚至不需要再使用鼠标,就可以快速高效的编写代码,因此学习和熟悉这些快捷键是值得的. 其中有一个快捷键是我非常喜欢,也 ...