我们很高兴在Manuel Bernhardt的jOOQ博客上宣布一系列客座帖子。 在本博客系列中,Manuel将解释所谓的反应技术背后的动机,并在介绍期货和参与者的概念后使用Actor来与jOOQ一起访问关系数据库。
Manuel Bernhardt是一位独立软件顾问,热衷于构建基于Web的后端和前端系统。 他是“响应式Web应用程序” (Manning)的作者,在Java花费了很长时间之后,他于2010年开始与Scala,Akka和Play Framework合作。 他住在维也纳,他是当地Scala用户组的联合组织者。 他对基于Scala的技术和充满活力的社区充满热情,并正在寻找在行业中推广其使用的方法。 自6岁起,他就开始潜水,不能完全适应奥地利缺少海的情况。
本系列分为三个部分,我们将在下个月发布:
- 第1部分:期货简介,“为什么要异步”,连接池配置
- 第2部分:演员简介
- 第3部分:将jOOQ与Scala,期货和参与者一起使用
有反应吗?
如今, 响应式应用程序的概念变得越来越流行,并且很有可能您已经在Internet上的某个地方听说过它。 如果没有,您可以阅读反应式宣言,或者我们也可以同意其以下简单摘要:简而言之,反应式应用程序是满足以下条件的应用程序:
- 通过利用异步编程技术,最佳地利用计算资源(就CPU和内存使用而言)
- 知道如何处理故障,优雅地降级,而不是仅仅崩溃并使其用户无法使用
- 可以适应繁重的工作负载,随着负载的增加而在多台机器/节点上进行横向扩展(并向后扩展)
反应性应用程序不仅仅存在于原始的绿色野外。 在某些时候,他们将需要存储和访问数据以便做一些有意义的事情,并且数据很可能存在于关系数据库中。
延迟和数据库访问
当应用程序与数据库进行对话的频率更高时,数据库服务器将不会与该应用程序在同一服务器上运行。 如果您不走运,甚至可能是托管应用程序的服务器(或一组服务器)位于与数据库服务器不同的数据中心中。 这就是延迟的含义 :
假设您有一个在其首页上运行简单SELECT
查询的应用程序(这里不要争论这是否是个好主意)。 如果您的应用程序服务器和数据库服务器位于同一个数据中心中,则等待时间约为500 µs(取决于返回的数据量)。 现在,将其与您的CPU在此期间可以做的所有事情(上图中的所有绿色和黑色方块)进行比较,并牢记这一点–我们将在一分钟内再次讨论。
线程成本
假设您以同步方式(这就是JDBC的方式)运行欢迎页面查询,并等待结果从数据库返回。 在所有这些时间中,您将独占一个线程,等待结果返回。 刚刚存在的Java线程(根本不做任何事情)最多可能占用1 MB的堆内存,因此,如果您使用的线程服务器将为每个用户分配一个线程(我在看Tomcat),那么它就是为了您的最大利益,请为您的应用程序留出足够的内存,以使其在Hacker News(每位并发用户1 MB)上显示时仍能正常工作。
诸如使用Play Framework构建的应用程序之类的反应性应用程序使用遵循事件服务器模型的服务器:它将遵循一系列事件,而不是遵循“一个用户,一个线程”的口头禅(将访问请求视为一组事件)。这些事件之一)并通过事件循环运行它:
这样的服务器不会使用很多线程。 例如,Play Framework的默认配置是每个CPU内核创建一个线程,池中最多可以创建24个线程。 但是,与给定相同硬件的线程模型相比,这种服务器模型可以处理更多的并发请求。 事实证明,诀窍是在任务需要等待一些时间时换句话说将线程移交给其他事件,或者换句话说:以异步方式进行编程。
痛苦的异步编程
异步编程并不是真正的新事物,并且处理它的编程范例自70年代以来就出现了,并且从那时开始悄然发展。 但是,异步编程并不一定会使大多数开发人员重新获得美好的回忆。 让我们看一些典型的工具及其缺点。
回呼
直到最近(ECMAScript 6引入了Promises),某些语言(我正在使用Javascript向您介绍)一直停留在70年代,而回调是它们作为异步编程的唯一工具。 这也称为圣诞树编程 :
呵呵呵 。
线程数
作为Java开发人员,这个词异步未必有非常积极的含义,往往是相关联的,臭名昭著的synchronized
关键字:
95%的同步代码已损坏。 其余5%由Brian Goetz撰写。 – #s2gx的 Venkat Subramaniam
— RonnyLøvtangen(@rlovtangen) 2011年10月26日
在Java中使用线程很难,尤其是在使用可变状态时–让底层的应用程序服务器将所有异步内容抽象出来而不用担心就方便得多,对吗? 不幸的是,正如我们刚刚看到的那样,就性能而言,这付出了巨大的代价。
我的意思是,只看下面的堆栈跟踪:
从某种意义上说,线程服务器是异步编程,而Hibernate是SQL,这是一种渗漏的抽象,从长远来看,这将使您付出巨大的代价。 一旦意识到,通常为时已晚,您就陷入了抽象之中,为了提高性能必须采取各种措施。 尽管对于数据库访问而言,放开抽象是相对容易的(仅使用纯SQL,甚至更好,使用jOOQ ),但对于异步编程而言,更好的工具才刚刚开始流行。
让我们转到一个可以在函数式编程中找到根源的编程模型:期货。
期货:异步编程的SQL
在Scala中可以找到的期货利用了已经存在了数十年的功能编程技术,以使异步编程再次变得令人愉快。
未来的基本面
scala.concurrent.Future[T]
可以看作是一个框,如果成功,它将最终包含类型T
的值。 如果失败,将保留失败源处的Throwable
。 -未来是说,一旦它正在等待计算已取得的结果,或者出现在计算过程中出现错误未能 成功 。 在这两种情况下,一旦未来完成计算,据说完成 。
声明Future后,它将立即开始运行,这意味着它将尝试异步执行计算。 例如,我们可以使用Play Framework的WS库来针对Play Framework网站执行GET请求:
val response: Future[WSResponse] =
WS.url("http://www.playframework.com").get()
此调用将立即返回,并让我们继续执行其他操作。 在将来的某个时候,调用可能已执行,在这种情况下,我们可以访问结果以对其进行处理。 与Java的java.util.concurrent.Future<V>
可以通过get()
方法在检索Future时检查它是否完成还是阻塞不同,Scala的Future可以指定执行结果的方式。
改变期货
操纵盒子里面的东西也很容易,我们不需要等待结果可用就可以这样做:
val response: Future[WSResponse] =
WS.url("http://www.playframework.com").get()
val siteOnline: Future[Boolean] =
response.map { r =>
r.status == 200
}
siteOnline.foreach { isOnline =>
if(isOnline) {
println("The Play site is up")
} else {
println("The Play site is down")
}
}
在此示例中,我们通过检查响应的状态将Future[WSResponse]
转换为Future[Boolean]
。 重要的是要理解该代码在任何时候都不会阻塞:只有当响应可用时,才会使线程可用于响应的处理并在map
函数内部执行代码。
恢复失败的期货
故障恢复也非常方便:
val response: Future[WSResponse] =
WS.url("http://www.playframework.com").get()
val siteAvailable: Future[Option[Boolean]] =
response.map { r =>
Some(r.status == 200)
} recover {
case ce: java.net.ConnectException => None
}
在我们所说的未来的最末尾的recover
方法将处理特定类型的异常和限制dammage。 在此示例中,我们仅通过返回None
值来处理java.net.ConnectException
的不幸情况。
组成期货
期货的杀手feature是其可组合性。 构建异步编程工作流时,一个非常典型的用例是将多个并发操作的结果组合在一起。 期货(和Scala)使这变得很容易:
def siteAvailable(url: String): Future[Boolean] =
WS.url(url).get().map { r =>
r.status == 200
}
val playSiteAvailable =
siteAvailable("http://www.playframework.com")
val playGithubAvailable =
siteAvailable("https://github.com/playframework")
val allSitesAvailable: Future[Boolean] = for {
siteAvailable <- playSiteAvailable
githubAvailable <- playGithubAvailable
} yield (siteAvailable && githubAvailable)
allSitesAvailable
Future是使用for理解构建的,它将等待两个Future都完成。 一旦声明它们,两个Future的playSiteAvailable
和playGithubAvailable
就会开始运行,并且for comprehension会将它们组合在一起。 而且,如果其中的一个Future[Boolean]
失败了,那么生成的Future[Boolean]
就会直接失败(而无需等待另一个Future完成)。
这是本系列文章的第一部分。 在下一篇文章中,我们将介绍另一种用于反应式编程的工具,最后讨论如何组合使用这些工具,以便以反应式方式访问关系数据库。
继续阅读
请继续关注,作为本系列的一部分,我们将很快发布第2部分和第3部分:
- 第1部分:期货简介,“为什么要异步”,连接池配置
- 第2部分:演员简介
- 第3部分:将jOOQ与Scala,期货和参与者一起使用
翻译自: https://www.javacodegeeks.com/2015/12/reactive-database-access-part-1-async.html