可以异步调用jdbc吗?


158

我想知道是否有一种方法可以对数据库进行异步调用吗?

例如,假设我有一个很大的请求,需要很长时间来处理,我想发送请求并在请求将返回值时(通过传递侦听器/回调等)接收通知。我不想阻止等待数据库答复。

我不认为使用线程池是一种解决方案,因为它无法扩展,在大量并发请求的情况下,这会产生大量线程。

我们正面临着网络服务器的此类问题,并且已经找到解决方案,方法是使用select / poll / epoll系统调用来避免每个连接只有一个线程。我只是想知道如何在数据库请求中具有类似的功能?

注意:我知道使用FixedThreadPool可能是一个不错的解决方法,但令我惊讶的是,没有人开发出真正异步的系统(无需使用额外的线程)。

**更新**
由于缺少实际可行的解决方案,我决定自己创建一个库(finagle的一部分):finagle-mysql。它基本上解码/解码mysql请求/响应,并在后台使用Finagle / Netty。即使有大量连接,它的扩展性也非常好。




问题是查询完成后数据库如何通知客户端。一种是(例如)让Oracle使用“数据库查询结果更改通知”功能,并在db数据更改时得到通知。这适用于修改db数据的SQL查询。对于只读查询,它将不起作用。另一方面,我不确定使连接异步是否是一个好主意,因为建立连接很昂贵。当然,这不是一个非常通用的解决方案。只是
值得

finagle-mysql是否使用JDBC?
赛义德·扎林法姆

Answers:


164

我不明白在Actor,executor或其他任何东西中包装JDBC调用的提议方法如何能在这里提供帮助-有人可以澄清一下。

当然,基本问题是JDBC操作在套接字IO上阻塞。执行此操作时,它将阻止线程在故事结尾运行。无论选择哪种包装框架,使用它最终都会导致每个并发请求使一个线程处于繁忙/阻塞状态。

如果基础数据库驱动程序(MySql?)提供了一种拦截套接字创建的方法(请参阅SocketFactory),那么我想可以在JDBC api之上构建异步事件驱动的数据库层,但是我们必须封装整个JDBC在事件驱动的外观后面,并且该外观看起来不像JDBC(在事件驱动之后)。数据库处理将在与调用方不同的线程上异步发生,并且您必须弄清楚如何构建不依赖线程亲和力的事务管理器。

就像我提到的方法一样,即使是单个后台线程也可以处理并发JDBC执行程序的负载。实际上,您可能会运行一个线程池来利用多个内核。

(当然,我不是在评论原始问题的逻辑,只是回答那些暗示在没有选择器模式的情况下,在阻塞套接字IO的情况下并发是可能的-简单地算出典型的JDBC并发并放入在正确大小的连接池中)。


看起来MySql可能按照我的建议做了一些事情--- http://code.google.com/p/async-mysql-connector/wiki/UsageExample


1
使用Akka不会使对关系数据库的调用异步。它使您可以在一系列专用线程上运行它们,以轻松访问数据库。这样,当站点无响应时,您不会关闭整个站点,因为您一直在使用诺言在服务层向DAO层进行异步调用,并且Web服务器线程与应用程序的其余部分分开。
2014年

Actor并不是唯一的解决方法(例如,微服务和异步http,我们每秒可扩展到数千个),从客户端的角度来看,我不会很快将它们视为非异步的。如果1k UI线程流量进入您的系统,并且DB上只有10个线程被阻塞,而990个“消息”(或类似消息)在内存中排队而没有阻塞任何 1k UI线程(可能会释放)。 ..这不是必需的吗?我希望看到真正的异步JDBC,但这并不意味着在此期间没有非常可行的解决方法。
格雷格·彭德伯里

42

不可能通过JDBC 对数据库进行异步调用,但是您可以使用Actors 对JDBC进行异步调用(例如,actor通过JDBC对DB进行调用,并在调用结束后将消息发送给第三方),或者,如果您喜欢CPS,则可以使用流水线式的期货(承诺)(一个很好的实现是Scalaz Promises

我不认为使用线程池是一种解决方案,因为它无法扩展,在大量并发请求的情况下,这会产生大量线程。

默认情况下,Scala actor是基于事件的(不是基于线程的)-连续调度允许在标准JVM设置上创建数百万个actor。

如果您以Java 为目标,则Akka Framework是一种Actor模型实现,对于Java和Scala都具有良好的API。


除此之外,JDBC的同步特性对我来说非常有意义。数据库会话的成本远远高于阻塞Java线程(在前台或后台)并等待响应的成本。如果您的查询运行了很长时间,以至于执行程序服务(或包装Actor / fork-join / promise并发框架)的功能不足以满足您的需求(并且您消耗了太多的线程),则首先应考虑您的数据库负载。通常,来自数据库的响应很快返回,并且以固定线程池为后盾的执行程序服务是一个很好的解决方案。如果您有太多长时间运行的查询,则应考虑进行前期(预处理​​)处理,例如每晚重新计算数据等。


2
@Victor,每个在阻塞操作(JDBC)上并行工作的actor都将在Steve试图避免的单独线程上运行
Vasil Remeniuk 2010年

36
当事务正在进行时,参与者方法仍然需要为每个活动的数据库事务提供一个线程,因此,这并不是解决OP问题的真正方法,除非您愿意限制并行数据库事务的数量并愿意进行一些“异步”数据库操作一些已经执行的线程完成并释放线程。不过,这并不是一个坏主意-如果打开过多的连接,数据库可能会过载-因此,将数据库事务放入队列中进行处理,而不是阻塞http请求处理线程,将会有所帮助。
Dobes Vandermeer 2012年

8
基于Actor的解决方案仍在阻塞线程。不要说无法执行异步jdbc调用,有一些尝试实现异步jdbc的实验性开源库。

6
+1 “数据库会话的成本远远高于被阻塞的Java线程的成本”
Paul Draper 2014年

1
对于昂贵的数据库调用,通常不会有太大的问题。只是在琐碎的通话中,网络开销才成为问题。如果要进行100个查询,每个查询在DB上花费1毫秒,但网络开销为200毫秒,那么它将同步花费20秒钟以上,而异步花费300毫秒。
morten

12

也许您可以使用可扩展性很好的JMS异步消息传递系统,恕我直言:

  • 将消息发送到队列,订阅者将在该队列中接受消息并运行SQL进程。您的主要流程将继续运行并接受或发送新请求。

  • 当SQL进程结束时,您可以以相反的方式运行:将消息和进程的结果发送到ResponseQueue,客户端的侦听器接受它并执行回调代码。


7

JDBC没有直接支持,但是您有多个选项,例如MDB,Java 5中的Executors。

“我不认为使用线程池是一种解决方案,因为它无法扩展,在大量并发请求的情况下,这将产生大量线程。”

我很好奇为什么有限的线程池不会扩展?它不是每个请求线程的池,而是为每个请求生成一个线程。我已经在很重的Web应用程序上使用了一段时间,到目前为止,我们还没有发现任何问题。


我认为反对线程的主要论点是,您基本上不在任何标准Java容器约束之内,因此尽管可以自己滚动或使用Terracotta之类的方法,但您失去了容器管理的集群并无法进行故障转移。
mezmo

3
我们可以使用工作管理器来进行应用服务器托管的线程民意测验。websphere,weblogic和glassfish支持它
Aravind Yarram 2010年



3

Ajdbc项目似乎可以解决此问题http://code.google.com/p/adbcj/

当前有2个针对mysql和postgresql的实验性本地异步驱动程序。


我想准备好这种方法。JDBC从一开始就发展了很多(迭代器,模板,准备好的过程),但是这种异步方法从未实现。对于写操作(Insert,Update,Delete),尤其是我们都面临的大量TX,这将特别有趣。我认为,任何基于客户端的方法(Pooling,Actor,Scheduling,Messaging ...)都不会在资源使用方面带来很少的回报(可能会增加吞吐量或延迟)。
Jaime Casero 2015年

既旧又废弃,仅支持两种数据类型,甚至还没有准备好投入生产。不幸的是:(
亚伦·辛曼

该库的第1期是有关该网站不可用的信息。已经有一年多了。我怀疑这个图书馆已经死了。
卢卡斯·埃德

3

一个古老的问题,但有更多信息。除非供应商提供JDBC扩展和用于处理JDBC的包装器,否则JDBC不可能向数据库本身发出异步请求。也就是说,可以用处理队列包装JDBC本身,并实现可以在一个或多个独立连接上处理队列的逻辑。对于某些类型的调用,此方法的优点之一是,如果逻辑在足够大的负载下,则可以将这些调用转换为JDBC批处理以进行处理,从而可以显着提高逻辑速度。这对于插入数据的呼叫最有用,并且仅在出现错误时才记录实际结果。一个很好的例子是是否执行插入操作来记录用户活动。该应用程序将'

附带说明一下,市场上的一种产品提供了一种策略驱动的方法,以允许像我描述的那样异步调用(http://www.heimdalldata.com/)。免责声明:我是这家公司的联合创始人。它允许将正则表达式应用于数据转换请求,例如对任何JDBC数据源的插入/更新/删除,并将自动将它们一起批处理。与MySQL和rewriteBatchedStatements选项(带有rewriteBatchedStatements = true的MySQL和JDBC)一起使用时,这可以显着降低数据库的总体负载。


但这仍然意味着JDBC应该至少具有一个单独的线程。单线程但仍基于回调的框架和堆栈又如何呢?您知道他们如何管理JDBC调用吗?
yuranos

3

我认为您有三种选择:

  1. 使用并发队列将消息分布在固定数量的线程上。因此,如果您有1000个连接,则将有4个线程,而不是1000个线程。
  2. 在另一个节点(即另一个进程或机器)上进行数据库访问,并让数据库客户端对该节点进行异步网络调用
  3. 通过异步消息实现真正的分布式系统。为此,您将需要一个消息队列,例如CoralMQ或Tibco。

免责声明:我是CoralMQ的开发人员之一。


3

正在开发一种解决方案,以使与标准关系数据库的反应性连接成为可能。

由于基于阻塞I / O的现有标准,想要在保持关系数据库使用的同时进行扩展的人们被从反应式编程中分离出来。R2DBC指定了一个新的API,该API允许可与关系数据库一起有效工作的反应式代码。

R2DBC是从头开始设计的规范,用于使用SQL数据库进行反应式编程,从而为数据库驱动程序实现者和客户端库作者定义了非阻塞SPI。R2DBC驱动程序在非阻塞I / O层之上完全实现了数据库有线协议。

R2DBC的网站

R2DBC的GitHub

特征矩阵

在此处输入图片说明


2

Java 5.0中执行人可能来得心应手。

您可以有固定数量的线程来处理长时间运行的操作。而不是Runnable可以使用Callable,它返回结果。结果封装在一个Future<ReturnType>对象中,因此可以在返回时获取它。



2

只是一个疯狂的主意:您可以对某些Future / Promise中的JBDC resultSet使用Iteratee模式

Hammersmith为MongoDB做到了这一点。


1

我只是在这里思考想法。为什么没有一个数据库连接池,每个数据库连接都有一个线程。每个线程都可以访问队列。当您要执行需要很长时间的查询时,可以将其放在队列中,然后一个线程将其拾取并处理。您将永远不会有太多的线程,因为您的线程数是有界的。

编辑:或者更好,只是多个线程。当线程看到队列中的内容时,它会请求池中的连接并进行处理。


1

commons-dbutils库支持AsyncQueryRunner您提供的ExecutorService,并返回Future。值得检查,因为它使用简单,并确保您不会泄漏资源。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.