1.引言
最近在优化后端接口的时候,因为不同接口都需要去操作数据库,这就产生了一个疑惑,我是应该一直连着还是连接并操作完数据库之后断开连接呢,然后每次需要操作数据库连接,然后关闭,这样有点尴尬,一个接口对应一次连接和断开,是不是有点憨憨的,但是如果我不断开,会不会导致连接池达到上限呢?最近又想了想,觉得自己有点想偏了,一定好记得看第7部分(狗头保命)。
结论:建议操作数据库之后不断开连接,也就是最常理解的那种。
2.问题分析
一般数据库操作都是追求安全的,也就意味着大概率使用TCP连接,一搜,果然是TCP。那么问题就来了TCP是长连接,你不断开他就一直连着。如果我们只看一次连接需要消耗的资源,不设置连接池的话,那么数据库不断开,一直连一直连,那么迟早会耗尽服务端的资源,为什么这么说呢,应为客户端远比服务端多的多(一般服务端就是一个内存还不如客户端的Linux系统)。所以连接池机制还是很有必要的
。
3.佐证
还是有点半信半疑,于是我去搜了搜后端数据库操作数据之后是否需要断开与数据库的连接?
。好巧不巧,遇到了Java老大哥里面的jdbc
,网上说jdbc
不需要断开连接,因为里面的finally会主动帮助断开连接,设置有些大佬还提到一些框架是能够主动帮助我们断开与数据库的连接的。这也就解释了为什么,很多时候都没有这个断开数据库连接的习惯。这些本质是框架自己完成的,即需要断开连接。
4.分析目标mongoDB
我这里的项目是使用koa框架,里面用的是mongoDB第三方依赖,官网中给出的实例代码如下:
可以看出需要主动断开连接,另外还有人用复杂一点的mongoose
连接数据库,嘿嘿,一搜发现mongoose
自带连接池,那就是也需要断开连接的,和jdbc
一样,它帮我们完成了。
5.实际测试
如果真的有连接限制,那么就如同大佬说的那样,连接过多会变慢,就像是很多人挤进操场和一个人去操场一样。那我直接连接多次mongoDB,看看会发生什么吧,程序代码如下:
const dbUserName = "用户名";
const dbPassWord = "用户密码";
const targetDataBase = "连接的数据库";
const url = `mongodb://${dbUserName}:${dbPassWord}@远程ipAddress/${targetDataBase}`;
const { MongoClient } = require("mongodb");
module.exports = async (ctx) => {
let isSuccess = true;
let success = 0;
let fail = 0;
let timer = setInterval(() => {
const client = new MongoClient(url);
client
.connect()
.then((res) => {
console.log("连接成功:", res);
success++;
})
.catch((err) => {
console.log("连接失败:", err);
isSuccess = false;
fail++;
clearInterval(timer);
})
.finally(() => {
console.log("完成" + success + "次测试", "失败" + fail + "次测试");
});
}, 100);
};
当然运行的时候由于连接失败程序直接崩了,promise里面的数据没有打印,也就是具体能连多少还是看不出来:
第一次测试是在重启了mongoDB的情况下进行的,然后我测一下接口,发现还是能连接,然后没有重启又测了一次:
可以明显看出timeout
,也就是真的“挤”不进去了。
为了确认我又去看了下服务器的日志:
好家伙,服务器把锅给了客户端,说这是客户端主动断开连接
。那现在连接池很卡了,我接着连会不会直接报错呢?看来这个连接池是相对卡顿的。补充这里的setInterval
,这个停下来会比预想的慢,因为发出去的请求是否成功,setInterval
管不了,只是在失败的那一次停止,之前已经发出去的的可能也还有失败的)。
好接下来反向论证一下,将finally里面的关闭连接(client.close()
),得到如下结果:
这个是我主动关闭连接,因为是函数里面调用,也不存在内存不够的情况,理论上无穷次连接。
6.得出结论
使用mongoDB数据库的时候,如果使用mongoose依赖,内部具有默认连接池,不需要关闭连接,如果使用mongoDB依赖,需要主动关闭连接,不关闭连接,最大连接大概在700左右,在连接峰期结束之后还能恢复正常。如果主动关闭连接,那么连接次数就是无穷次。
7.狗头保命
在实际开发中,最终想到的解决方法如下:
简单说下思路:思路比较简单,就是每次用户请求接口的时候连接数据库,到最后由于用户发送数据完毕,无论成功还是失败都需要封装数据体,也就是下面这个:
所以只需要再最后的函数里面关闭数据库连接,最终只需要在封装数据体的函数里面关闭连接。
好接下来说一说,面临的选择问题,搜集相关信息的时候,发现mongoose的连接池里面之后10个不到,我顿时想起来是自己想错了,把服务端像客户端一样看待了。其实用户每次访问应用,数据库的连接不是由用户发起的,而是服务启动的时候开启的。也就是说重启服务对应数据库的一次连接并且这个连接将一直保持。如果设置成使用接口打开连接,并在发送完数据关闭连接就会导致出现不必要的延时。最终的话还是建议不关闭连接,毕竟连接数据库的用户和客户端相比简直没有
。