共计 2488 个字符,预计需要花费 7 分钟才能阅读完成。
接着,明天咱们聊一个不常见的 Java 面试题:为什么数据库连接池不采纳 IO 多路复用?
这是一个十分好的问题。IO
多路复用被视为是十分好的性能助力器。然而个别咱们在应用 DB 时,还是经常性采纳 c3p0
,tomcat connection pool
等技术来与 DB 连贯,哪怕整个程序曾经变成以 Netty
为外围。这到底是为什么?
首先纠正一个常见的误会。IO 多路复用听下来如同是多个数据能够共享一个 IO(socket 连贯),实际上并非如此。「IO 多路复用不是指多个服务共享一个连贯,而仅仅是指多个连贯的治理能够在同一过程」。在网络服务中,IO 多路复用起的作用是「一次性把多个连贯的事件告诉业务代码解决」。至于这些事件的解决形式,到底是业务代码循环着解决、丢到队列里,还是交给线程池解决,由业务代码决定。
对于应用 DB 的程序来讲,不论应用多路复用,还是连接池,都要保护一组网络连接,反对并发的查问。
为什么并发查问肯定要应用多个连贯能力实现呢?因为 DB 个别是应用连贯作为 Session 治理的根本单元。在一个连贯中,SQL 语句的执行必须是串行、同步的。这是因为对于每一个 Session,DB 都要保护一组状态来反对查问,比方事务隔离级别,以后 Session 的变量等。只有单 Session 内串行执行,能力保护查问的正确性(试想一下一组 sql 在一直的增减变量,而后这组 sql 乱序执行会产生什么)。保护这些状态须要消耗内存,同时也会耗费 CPU 和磁盘 IO。这样,限度对 DB 的连接数,就是在限度对 DB 资源的耗费。
因而,对 DB 来说,要害是要限度连贯的数目。这个要求无论是 DB 连接池还是 NIO 的连贯治理都能做到。
这样问题就绕回来了,为什么 DB 连贯不能放到 IO 多路复用里一并执行吗?为啥大家都用连接池?
答案是,能够用 IO 多路复用——然而 「应用 JDBC 不行」。JDBC 是一个呈现了近 20 年的规范,它的设计外围是 BIO(因为 199X 年时还没有别的 IO 能够用):调用者在通过 JDBC 时执行比方query
这样的 API
,在没有执行实现之前,整个调用线程被卡住。而相似于Mysql Connector/J
这样的 driver
齐备的实现了这套语义。
当然如果 DB Client 的协定的连贯解决和解析略微改一下:
- 将 IO 模式调整为 Non-Blocking,这样就能够挂到 IO 多路复用的内核上(select、epoll、kqueue……)
- 在 Non-Blocking 实现的根底之上实现数据库协定的编码和解析
就能够实现用 IO 多路复用来拜访 DB。实际上很多其余语言 / 框架里都是这么干的。比方 Nodejs,see https://github.com/sidorares/node-mysql2
;或者 Vert.X 的 db 客户端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 那句调用——期待事件降临,一个 blocking 的 API。整个程序必须以这个驱动代码为外围。这样就对整个代码的构造产生重大的影响。这种影响是没法用简略的接口形象的。
Java Web 容器之所以能够应用 NIO 是因为 NIO 能够被封装到容器外部。Web 容器对外裸露的还是传统的多线程模式的 Java EE
接口。
如果 DB 和 Web 容器同时应用 NIO,那么调用的 DB 连贯库与必须与容器有一个约定形容「DB 的连贯治理如何接入 Web 容器的 NIO 的驱动代码」。在 Java 这个大环境下,不同人,不同的容器写的代码不同;又或者,不应用任何常见的容器,而是本人用 NIO 去封装一个。这样是无奈造成代码上的约定的。那么多个独立的组件就不能很好的共享 NIO 的驱动代码。
下面这个用法假如整个程序应该共享一个 NIO 驱动代码。那么 Web 和 DB 可不可以各用各的呢?也是能够的,然而为了保障这两个 NIO 驱动代码不会互相 block,最好要离开两个线程。这样一来就会突破个别 Web 服务一个申请解决用一个线程的个别做法,会让程序边的更简单——你的业务代码和 DB 查问之间必须做跨线程数据交换。
相同,连接池的实现就绝对独立的多,也简略的多。外界只有配好 DB URL,用户名明码和连接池的容量参数,就能够做到自行治理连贯。
而 Nodejs
和Vert.X
是齐全不同的。他们实质就是 Reactive
的。他们的 NIO
的驱动形式是其运行时的根底——所有要在这个根底上开发的代码都必须恪守同样的 NIO+
异步开发标准,应用同一个 NIO
的驱动。这样 DB
与NIO
的合作就不成问题了。
最初,「有大量场景是须要 BIO 的 DB 查问反对的」。批处理数据分析代码都是这样的场景。这样的程序写成 NIO 就会得失相当——代码不容易懂,也没有任何效率上的劣势。相似于 Nodejs
这样的运行时在此场景下,反而要利用 async
或等价的语法来让代码看起来是同步的,这样才容易写。
总结一下。DB 拜访个别采纳连接池这种景象是生态造成的。历史上的 BIO + 连接池的做法通过多年的倒退,曾经解决了次要的问题。在 Java 的大环境下,这个计划是十分靠谱的,成熟的。而基于 IO 多路复用的形式只管在性能上可能有劣势,然而其对整个程序的代码构造要求过多,过于简单。当然,如果有特定的须要,心愿应用 IO 多路复用治理 DB 连贯,是齐全可行的。