在此前的博客中,博主参考 eShopOnContainers 实现了一个基于RabbitMQ的事件总线(EventBus)。在这个项目中,它提供了一个持久化连接的类,主要解决了RabbitMQ在连接断开后自动重连的问题,可实际上我们都知道,RabbitMQ提供的连接数是有一个上限的,如果频繁地使用短连接的方式,即通过的方法来创建一个连接,从本质上讲,一个对象就是一个TCP连接,而则是每个对象下有限的虚拟连接,注意“有限”这个限定词,这意味着和一样,都不能毫无节制的创建下去。此时,官方推荐的做法有两种:(1):一个对应多个同时保证每个线程独占;(2):创建一个池同时定期清除无效连接。这里的第二种做法,显然就是我们今天要说的对象池(Object Pool)啦,我们将从这里拉开这篇博客的帷幕。
什么是对象池
首先,我们来回答第一个问题,什么是对象池单来说,它就是一种为对象提供可复用性能力的软件设计思路。俗话说**“有借有还,再借不难”**,而对象池就是通过“借”和“还”这样两个动作来保证对象可以被重复使用,进而节省频繁创建对象的性能开销。对象池在游戏设计中使用的更普遍一点,因为游戏中大量存在着像子弹、怪物等等这类可复用的对象,你在玩第一人称射击游戏(FPS)时,总是有源源不断的子弹或者丧尸出现,可事实上这不过是数字世界的循环再生,因为玩家的电脑内存始终都有一个上限。而在数据库的世界里,则存在着一个被称为“连接池”的东西,每当出现数据库无法连接的情况时,经验丰富的开发人员往往会先检查“连接池”是否满了,这其实就是对象池模式在特定领域的具体实现啦,所以,对象池本质上就是负责一组对象创建和销毁的容器,下面是一个基本的对象池示意图:
.NET Core中的对象池
在.NET Core中,微软已经为我们提供了对象池的实现,即。它主要提供了三个核心的组件,分别是、和,关于这三者间的关系,我绘制了下面的UML图来作为说明:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HkXcnaxi-1598347290864)(https://i.loli.net/2020/08/22/M6ojLtqgKc5pfCA.png)]
可以注意到,是一个抽象类,它对外提供了Get()和Return()两个方法,所谓的“有借有还”,这一点没什么可说的。接下来,同样是一个抽象类,它的职责就是创建,所以,它提供了两个方法,两者的区别是,无参数版本本质上使用的是。顾名思义,它同、一样,都是微软提供的默认实现,其中可以为不同的对象池定义不同的策略,来决定对象如何“借”、是否可以“还”。默认的对象池内部使用这个数组来管理对象,数组的大小等于maximumRetained – 1,因为它单独指定了首项,默认情况下,这个maximumRetained等于,这里主要用到了方法:
这里主要用到这个方法,对于方法而言,它将和进行交换,相当于将指定元素设为null并返回原始值;而对于方法而言,如果将和交换后的值不为null,则表示指定元素已经“归还”,因为这个方法只有在第一个参数和第三个参数相等时才会发生交换。好了,了解了.NET Core中对象池的实现以后,我们来一起看看具体的使用:
其中,和是两个非常典型的“工具类”,类似我们所说的“工具人”:
当你需要控制对象池内的对象如何被创建的时候,你可以考虑实现自定义的,否则,这个默认实现完全可以满足你的使用,而这就是.NET Core中对象池的所有用法,一个实现起来并不复杂但是在某些场景下非常有用的软件设计模式。
回到起点
好了,回到我们一开始的问题,即:如何解决RabbitMQ在多次重连后提示连接数不足的问题。由于Channel对象本质上是Connection对象上的TCP连接的软连接,所以,每当创建一个新的Channel的时候,实际上会独占一个TCP连接。考虑到在使用RabbitMQ的时候,发布消息/消费消息每次都是创建一个Channel,在高并发场景下可能会导致TCP连接数被用完,进而出现无法连接或者响应过慢等一系列问题。既然TCP连接数是有限的,为什么不考虑复用这些TCP连接呢这个角度上来看,数据库连接池承担了相同的角色,增加连接数说到底是一种“治标不治本”的做法。在具体实现上,可以考虑Connection“池”和Channel“池”,我们我们像官方推荐的做法一样,一个Connection对应多个Channel,实际上只需要实现Channel“池”。除非在多个Connection对应多个Channel的情况下,我们需要考虑同时实现Connection“池”和Channel“池”。坦白说,我这里一直没能找到实现Connection“池”的相关资料,高冷的 Catcher 大神只是让我去认真读官方文档,搞清楚Connection和Channel的关系。而这个Channel“池”的实现,结合这篇博客里的内容,实现起来是非常简单的:
第一步是实现,注意到,这里通过构造函数注入了,所以,除了常规的注入项以外,这里还需要注入:
然后,我们只需要在EventBus里注入即可,此时,我们调用Channel的画风是下面这样子的:
关于Connection“池”的实现,我认为我的想法还不太成熟,暂时列入未来的思考计划中,所以,这篇博客就先写到这里。
对象池(ObjectPool)是一种通过复用对象来减少资源开销进而实现提高系统性能的软件设计模式,其核心是控制容器内对象的生命周期来规避系统的主动回收,从对象池中(ObjectPool)“借”出的对象必须要及时“归还”,否则会造成对象池(ObjectPool)中没有可用资源。实现对象池可以考虑、、以及等多种数据结构,而微软在.NET Core中已经为我们实现了一个简单的对象池,大多数情况下,我们只需要定义自己的去决定对象应该怎么样“借”、怎么样“还”。因为此前实现基于RabbitMQ的EventBus的时候,我们是每次创建一个Channel,即官方所谓的“短连接”的方式,因为Channel本质上是Connection在TCP连接上的一个虚拟连接,所以,每次创建Channel都会占用一个TCP连接,当我们系统中的TCP连接被用完的时候,就会出现无法连接、连接过慢的问题,为了解决这个问题,我们最终引入了对象池,实际上这里是实现了一个Channel“池”,关于是否应该实现Connection“池”,这一点我还没有想好,总而言之,游戏世界里可以复用的GameObject、各种数据库里的连接池,都是对象池模式在各自领域中的具体实现,这就是这篇博客的内容啦,欢迎大家在评论中留言,谢谢大家!
每次创建Channel都会占用一个TCP连接,当我们系统中的TCP连接被用完的时候,就会出现无法连接、连接过慢的问题,为了解决这个问题,我们最终引入了对象池,实际上这里是实现了一个Channel“池”,关于是否应该实现Connection“池”,这一点我还没有想好,总而言之,游戏世界里可以复用的GameObject、各种数据库里的连接池,都是对象池模式在各自领域中的具体实现,这就是这篇博客的内容啦,欢迎大家在评论中留言,谢谢大家!
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!