当前位置: 首页 > 后端技术 > Java

为什么数据库连接池不用IO多路复用?

时间:2023-04-02 00:57:40 Java

接下来,今天我们就来说说一道不常见的Java面试题:为什么数据库连接池不用IO多路复用?这个问题问得好。IO多路复用被认为是一个非常好的性能助推器。但是一般我们在使用DB的时候,往往会使用c3p0、tomcat连接池等技术来连接DB,即使整个程序已经变成了Netty为核心。这是什么?让我们从纠正一个常见的误解开始。IO多路复用听起来好像多个数据可以共享一个IO(套接字连接),其实不然。“IO多路复用并不意味着多个服务共享一个连接,而只是意味着多个连接的管理可以在同一个进程中。”在网络服务中,IO多路复用的作用是“一次通知业务代码处理多个连接事件”。至于这些事件如何处理,由业务代码决定是循环处理,丢入队列,还是交给线程池处理。对于使用DB的程序,无论多路复用还是连接池,都必须维护一组网络连接来支持并发查询。为什么并发查询要使用多个连接才能完成?因为DB一般都是以connection作为Session管理的基本单位。在一个连接中,SQL语句的执行必须是串行和同步的。这是因为对于每一个Session,DB都维护了一组状态来支持查询,比如事务隔离级别,当前Session的变量等等。只有在单个会话内串行执行才能保持查询的正确性(想象一下如果一组SQL变量不断增加或减少,然后这组SQL乱序执行会怎样)。维护这些状态会消耗内存以及CPU和磁盘IO。这样,限制DB的连接数就是限制了DB资源的消耗。所以对于DB来说,关键是限制连接数。DB连接池和NIO连接管理都可以满足这个需求。这样问题又回来了,为什么DB连接不能放到IO多路复用中一起执行呢?为什么你们都用连接池?答案是,您可以使用IO多路复用-但“您不能使用JDBC”。JDBC是一个已经存在了将近20年的标准。它的设计核心是BIO(因为199X时没有其他可用的IO):当调用者通过JDBC执行查询等API时,在执行完成之前,整个调用线程就卡住了。MysqlConnector/J等驱动程序完全实现了这组语义。当然,如果对DBClient协议的连接处理和分析稍微改动一下:调整IO模式为Non-Blocking,这样就可以连接到Non中的IO多路复用内核(select,epoll,kqueue...)-Blocking实现的基础上,可以实现对数据库协议的编码和解析,以IO多路复用的方式访问DB。其实很多其他语言/框架都是这样做的。例如Nodejs,参见https://github.com/sidorares/node-mysql2;或Vert.X的数据库客户端https://github.com/mauricio/postgresql-async,不用管名字,它实际上同时支持mysql和postgres)。只是对于IO多路复用,官方数据库好像没有提供这样的支持——它们只支持JDBC、ODBC等标准协议。那么为什么基于IO多路复用的实现不能成为默认的和官方的,而是成为旁门左道呢?对于数据库开发人员。这种使用量在整体用户群中非常少,因此可能不值得付出努力。你只需要把协议写清楚(比如https://dev.mysql.com/doc/internals/en/client-server-protocol.html),然后就可以实现了。那么社区里有兴趣的人自然就可以去做了。另一个原因是系统的支持。简单的说,如果没有大型的Reactive运行环境,IO多路复用的使用会非常有限。之所以能够建立IO多路复用,是因为“整个程序必须有一个IO多路复用驱动代码”——也就是select的调用——等待事件的到来,一个阻塞的API。整个程序必须以这个驱动程序代码为核心。这对整个代码的结构有很大的影响。这种效果不能用简单的接口抽象出来。JavaWeb容器之所以可以使用NIO,是因为NIO可以封装在容器内部。Web容器对外暴露的仍然是传统的多线程JavaEE接口。如果DB和web容器同时使用NIO,那么调用的DB连接库必须和容器有协议来描述“DB连接管理如何访问web容器的NIO驱动代码”。在Java这个大环境下,不同的人,不同的容器写不同的代码;或者,他们不使用任何通用容器,而是使用NIO来封装一个容器。这样就无法在代码上形成契约。那么多个独立的组件就不能很好的共享NIO驱动代码。上面的用法假定整个程序应该共享一个NIO驱动程序代码。那么Web和DB可以自己用吗?也是可以的,但是为了保证两个NIO驱动代码不会互相阻塞,最好把两个线程分开。这样一来,就会打破一般web服务用一个线程处理一个请求的普遍做法,让程序端变得更加复杂——你的业务代码和DB查询之间必须做跨线程的数据交换。相反,连接池的实现相对独立和简单。外界只要配置好DBURL、用户名和密码,以及连接池的容量参数,就可以自己管理连接了。而Nodejs和Vert.X是完全不同的。它们的本质是Reactive。他们的NIO驱动方法是他们runtime的基础——所有在此基础上开发的代码都必须遵守相同的NIO+异步开发规范,使用相同的NIO驱动。这样DB和NIO的配合就不成问题了。最后,“有大量场景需要BIO的DB查询支持”。批量数据分析代码就是这样一个场景。写NIO这样的程序会得不偿失——代码不容易理解,效率上也没有优势。在这种场景下,像Nodejs这样的运行时需要使用async或等价的语法来让代码看起来是同步的,这很容易编写。综上所述。DB访问普遍使用连接池的现象是生态造成的。经过多年的发展,历史上BIO+连接池的方式解决了主要问题。在Java环境下,这个方案是非常可靠和成熟的。基于IO多路复用的方法虽然在性能上可能有优势,但是对整个程序的代码结构要求太高,过于复杂。当然,如果你有特定的需求,使用IO多路复用来管理DB连接是完全可行的。