异步EJB只是一个Gi头吗?

在之前的文章( 此处和此处 )中,我展示了当服务器负载沉重时,创建非阻塞异步应用程序可以提高性能。 EJB 3.1引入了@Asynchronous批注,用于指定方法将在将来的某个时间返回其结果。 Javadocs声明必须返回voidFuture 。 以下清单显示了使用此注释的服务示例:

Service2.java

@Stateless
public class Service2 {@Asynchronouspublic Future<String> foo(String s) {// simulate some long running processThread.sleep(5000);s += "<br>Service2: threadId=" + Thread.currentThread().getId();return new AsyncResult<String>(s);}
}

注释位于第4行。该方法返回String类型的Future ,并在第10行通过将输出包装在AsyncResult 。 在客户端代码调用EJB方法时,容器将拦截该调用并创建一个任务,该任务将在另一个线程上运行,以便它可以立即返回Future 。 当容器然后使用另一个线程运行任务时,它将调用EJB的方法并使用AsyncResult来完成给定调用者的Future 。 即使看起来与Internet上所有示例中的代码完全一样,此代码也存在一些问题。 例如, Future类仅包含用于获取Future结果的阻塞方法,而不包含用于在回调完成时注册回调的任何方法。 这将导致如下所示的代码,当容器处于加载状态时,这是很糟糕的:

客户端程序

//type 1
Future<String> f = service.foo(s);
String s = f.get(); //blocks the thread, but at least others can run
//... do something useful with the string...//type 2
Future<String> f = service.foo(s);
while(!f.isDone()){try {Thread.sleep(100);} catch (InterruptedException e) {...}
}
String s = f.get();
//... do something useful with the string...

这种代码是不好的,因为它导致线程阻塞,这意味着它们在这段时间内无法做任何有用的事情。 当其他线程可以运行时,需要进行上下文切换,这会浪费时间和精力(有关成本或我以前的文章的结果,请参见这篇出色的文章)。 像这样的代码会使已经处于负载状态的服务器承受更大的负载,并停止运行。

那么是否有可能使容器异步执行方法,而编写不需要阻塞线程的客户端呢? 它是。 下面的清单显示了一个servlet。

AsyncServlet2.java

@WebServlet(urlPatterns = { "/AsyncServlet2" }, asyncSupported = true)
public class AsyncServlet2 extends HttpServlet {@EJB private Service3 service;protected void doGet(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {final PrintWriter pw = response.getWriter();pw.write("<html><body>Started publishing with thread " + Thread.currentThread().getId() + "<br>");response.flushBuffer(); // send back to the browser NOWCompletableFuture<String> cf = new CompletableFuture<>();service.foo(cf);// since we need to keep the response open, we need to start an async contextfinal AsyncContext ctx = request.startAsync(request, response);cf.whenCompleteAsync((s, t)->{try {if(t!=null) throw t;pw.write("written in the future using thread " + Thread.currentThread().getId()+ "... service response is:");pw.write(s);pw.write("</body></html>");response.flushBuffer();ctx.complete(); // all done, free resources} catch (Throwable t2) {
...

第1行声明Servlet支持异步运行-不要忘记这一点! 第8-10行开始将数据写入响应,但有趣的位在第13行,其中调用了异步服务方法。 我们没有将Future用作返回类型,而是向其传递了CompletableFuture ,它用于将结果返回给我们。 怎么样? 第16行代码将启动异步servlet上下文,因此我们仍然可以在doGet方法返回后写入响应。 从第17行开始,然后有效地在CompletableFuture上注册了一个回调,一旦完成CompletableFuture并返回结果,该回调将被调用。 这里没有阻塞代码–没有线程被阻塞,没有线程被轮询,等待结果! 在负载下,服务器中的线程数可以保持最少,从而确保服务器可以高效运行,因为需要较少的上下文切换。

服务实现如下所示:

Service3.java

@Stateless
public class Service3 {@Asynchronouspublic void foo(CompletableFuture<String> cf) {// simulate some long running processThread.sleep(5000);cf.complete("bar");}
}

第7行确实很丑陋,因为它会阻塞,但假装这是代码调用大多数Web服务客户端和JDBC驱动程序会阻塞的API调用在Internet或慢速数据库中远程部署的Web服务。 或者,使用异步驱动程序 ,当结果可用时,完成第9行所示的将来。然后向CompletableFuture发出信号,可以调用在先前清单中注册的回调。

这不只是使用简单的回调吗? 这肯定是相似的,下面的两个清单显示了使用自定义回调接口的解决方案。

AsyncServlet3.java

@WebServlet(urlPatterns = { "/AsyncServlet3" }, asyncSupported = true)
public class AsyncServlet3 extends HttpServlet {@EJB private Service4 service;protected void doGet(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
...final AsyncContext ctx = request.startAsync(request, response);service.foo(s -> {
...pw.write("</body></html>");response.flushBuffer();ctx.complete(); // all done, free resources
...

Service4.java

@Stateless
public class Service4 {@Asynchronouspublic void foo(Callback<String> c) {// simulate some long running processThread.sleep(5000);c.apply("bar");}public static interface Callback<T> {void apply(T t);}
}

同样,在客户端中,绝对没有任何阻塞。 但是,由于以下原因,使用CompletableFutureAsyncServlet2Service3类的早期示例更好些:

  • CompletableFuture的API允许出现异常/失败,
  • CompletableFuture类提供用于异步执行回调和相关任务的方法,即在fork-join池中,以便整个系统使用尽可能少的线程运行,从而可以更有效地处理并发性,
  • 可将CompletableFuture与其他对象结合使用,以便您可以注册仅在多个CompletableFuture完成后才能调用的回调,
  • 回调不会立即被调用,而是池中有限数量的线程按它们应运行的顺序为CompletableFuture的执行提供服务。

在第一个清单之后,我提到异步EJB方法的实现存在一些问题。 除了阻塞客户端之外,另一个问题是,根据EJB 3.1 Spec的 4.5.3章,客户端事务上下文不会通过异步方法调用传播。 如果要使用@Asynchronous批注创建两个可以并行运行并在单个事务中更新数据库的方法,则该方法将无效。 这在某种程度上限制了@Asynchronous注释的使用。

使用CompletableFuture ,您可能认为可以在同一个事务上下文中并行运行多个任务,方法是先在EJB中启动一个事务,然后创建多个可运行对象,然后使用runAsync方法运行它们,该方法在执行中运行它们池,然后注册一个回调以使用allOf方法完成所有操作后allOf 。 但是您可能会因为多种原因而失败:

  • 如果您使用容器管理的事务,那么一旦导致事务开始的EJB方法将控制权返回给容器,事务将被提交-如果那时您的期货还没有完成,则您将不得不阻塞运行EJB方法的线程这样它就等待并行执行的结果,而阻塞正是我们要避免的,
  • 如果运行任务的单个执行池中的所有线程都被阻塞,等待它们的数据库调用应答,那么您将有可能创建性能不佳的解决方案–在这种情况下,您可以尝试使用非阻塞的异步驱动程序 ,但不能每个数据库都有这样的驱动程序,
  • 一旦任务在不同的线程(例如执行池中的线程)上运行,线程本地存储(TLS)就不再可用,因为正在运行的线程与将工作提交到执行池并进行设置的线程不同在提交工作之前将值存入TLS,
  • 诸如EntityManager 类的资源不是线程安全的 。 这意味着你无法通过EntityManager成提交给池的任务,而每个任务需要得到它自己的保持EntityManager实例,而是创建EntityManager取决于TLS(见下文)。

让我们通过以下代码更详细地考虑TLS,该代码显示了一种异步服务方法,该服务方法试图做几件事以测试允许的操作。

Service5.java

@Stateless
public class Service5 {@Resource ManagedExecutorService mes;@Resource EJBContext ctx;@PersistenceContext(name="asdf") EntityManager em;@Asynchronouspublic void foo(CompletableFuture<String> cf, final PrintWriter pw) {//pw.write("<br>inside the service we can rollback, i.e. we have access to the transaction");//ctx.setRollbackOnly();//in EJB we can use EMKeyValuePair kvp = new KeyValuePair("asdf");em.persist(kvp);Future<String> f = mes.submit(new Callable<String>() {@Overridepublic String call() throws Exception {try{ctx.setRollbackOnly();pw.write("<br/>inside executor service, we can rollback the transaction");}catch(Exception e){pw.write("<br/>inside executor service, we CANNOT rollback the transaction: " + e.getMessage());}try{//in task inside executor service we CANNOT use EMKeyValuePair kvp = new KeyValuePair("asdf");em.persist(kvp);pw.write("...inside executor service, we can use the EM");}catch(TransactionRequiredException e){pw.write("...inside executor service, we CANNOT use the EM: " + e.getMessage());}
...

第12行没有问题,您可以回滚当容器调用EJB方法时在第9行自动启动的事务。 但是该事务将不是可能由调用第9行的代码启动的全局事务。第16行也没问题,您可以使用EntityManager写入由第9行开始的事务内部的数据库。显示了在不同线程上运行代码的另一种方式,即使用Java EE 7中引入的ManagedExecutorService 。但是,这在任何时候都依赖TLS时也都会失败,例如,第22行和第31行会导致异常,因为在第9行启动的事务无法定位,因为使用TLS进行定位,并且第21-35行的代码使用与第19行之前的代码不同的线程运行。

下一个清单显示,第11-14行在CompletableFuture上注册的完成回调也与第4-10行在不同的线程中运行,因为在第6行的回调之外启动提交事务的调用将在第6行失败再次参见图13,因为第13行的调用在TLS中搜索当前事务,并且因为运行第13行的线程与运行第6行的线程不同,所以找不到事务。 实际上,下面的清单实际上有一个不同的问题:处理对Web服务器的GET请求的线程运行第JBAS010152: APPLICATION ERROR: transaction still active in request with status 0和11行,然后返回,此时JBoss日志JBAS010152: APPLICATION ERROR: transaction still active in request with status 0 –即使线程运行第13行可以找到该事务,它是否仍处于活动状态或容器是否已将其关闭也值得怀疑。

AsyncServlet5.java

@Resource UserTransaction ut;@Override
protected void doGet(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {ut.begin();	
...CompletableFuture<String> cf = new CompletableFuture<>();service.foo(cf, pw);
...cf.whenCompleteAsync((s, t)->{...ut.commit(); // => exception: "BaseTransaction.commit - ARJUNA016074: no transaction!"});
}

事务显然依赖于线程和TLS。 但这不仅仅是依赖TLS的事务。 以JPA为例,该JPA被配置为直接在TLS中存储会话(即与数据库的连接) ,或者被配置为将该会话的范围限定为当前的JTA事务 ,而该事务又依赖于TLS。 或以使用从EJBContextImpl.getCallerPrincipal提取的Principal进行安全检查为例,该Principal调用AllowedMethodsInformation.checkAllowed ,然后调用使用TLS的CurrentInvocationContext并简单地返回(如果在TLS中找不到上下文),而不是进行适当的权限检查如第112行所示。

这些对TLS的依赖意味着,在使用CompletableFuture或Java SE fork-join池或其他线程池(无论是否由容器管理)时,许多标准Java EE功能将不再起作用。

为了对Java EE公平起见,我在这里所做的事情都按设计工作! 规范实际上禁止在EJB容器中启动新线程。 我记得十多年前我曾经使用过旧版本的Websphere进行过一次测试-启动一个线程会引发异常,因为该容器确实严格遵守规范。 这是有道理的:不仅因为线程数应由容器管理,还因为Java EE对TLS的依赖意味着使用新线程会导致问题。 从某种意义上讲,这意味着使用CompletableFuture是非法的,因为它使用了不受容器管理的线程池(该池由JVM管理)。 使用Java SE的ExecutorService也是如此。 Java EE 7的ManagedExecutorService是一个特例-它是规范的一部分,因此您可以使用它,但是您必须了解这样做的含义。 EJB上的@Asynchronous批注也是如此。

结果是可以在Java EE容器中编写异步非阻塞应用程序,但是您确实必须知道自己在做什么,并且可能必须手动处理安全性和事务之类的事情,这确实是个问题。首先使用Java EE容器的原因。

那么是否有可能编写一个容器来消除对TLS的依赖以克服这些限制? 的确如此,但是解决方案并不仅仅依赖于Java EE。 该解决方案可能需要更改Java语言。 许多年前,在依赖注入之前,我曾经写过POJO服务,它在方法之间传递了JDBC连接,即作为服务方法的参数。 我这样做是为了可以在同一事务内(即在同一连接上)创建新的JDBC语句。 我所做的与JPA或EJB容器所需要做的事情并没有什么不同。 但是,现代框架没有使用TLS作为显式传递连接或用户之类的东西的方式,而是将TLS作为集中存储“上下文”的位置,即连接,事务,安全信息等。 只要您在同一线程上运行,TLS就是隐藏此类样板代码的好方法。 让我们假装TLS从未被发明过。 我们如何在不强制每种方法都将其作为参数的情况下传递上下文? Scala的implicit关键字是一种解决方案。 您可以声明参数可以隐式定位,这使编译器难以将其添加到方法调用中。 因此,如果Java SE引入了这样的机制,则Java EE不需要依赖TLS,我们可以构建真正的异步应用程序,在该应用程序中,容器可以像今天一样通过检查注释来自动处理事务和安全性! 也就是说,当使用同步Java EE时,容器会知道何时提交事务-在启动事务的方法调用结束时。 如果您异步运行,则需要显式关闭事务,因为容器不再知道何时执行此操作。

当然,保持不阻塞的需要以及因此不依赖TLS的需要在很大程度上取决于当前的方案。 我不认为我今天在这里描述的问题是当今的普遍问题,而是解决市场利基市场的应用程序所面临的问题。 只需看一下Java EE优秀工程师目前正在提供的工作数量,而同步编程就是其中的标准。 但是我确实相信,规模更大的IT软件系统将变得越来越多,它们处理的数据越多,阻塞API就会成为一个问题。 我还认为,当前硬件增长速度的放缓使这个问题更加复杂。 有趣的是,Java是否a)是否需要跟上异步处理的趋势,以及b)Java平台是否会采取行动来固定对TLS的依赖。

翻译自: https://www.javacodegeeks.com/2015/08/is-asynchronous-ejb-just-a-gimmick.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/358347.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

常见的清除浮动的五种解决办法

方法一:使用带clear属性的空元素 在浮动元素后使用一个空元素如 <div class="clear"></div> 并在CSS中赋予 .clear{clear:both;} 属性即可清理浮动。亦可使用 <br class="clear" /> 或 <hr class="clear" /> …

Kali环境下安装python3

Kali环境下安装python3 参考&#xff1a; 由于kali环境下是自动安装了python 2.7&#xff0c;没有python3以上的版本&#xff0c;现在开始安装python 1. 先下载python3的tar包 进入这个目录&#xff1a; cd /usr/local/ 创建一个新文件夹python3&#xff1a; mkdir /usr/loc…

用汇编语言写的第一个DOS程序

今天整理电脑时&#xff0c;看到两三年前学习汇编语言时写的显示“Hello”的程序。不禁感叹那个时候学得如此用心&#xff0c;同样的功能&#xff0c;我竟然用了四种写法。现贴出源码&#xff0c;为了以后继续学习&#xff0c;也为了给初学者一点启发。(删掉注释了&#xff0c;…

对安卓应用进行加固签名,为上架各大应用市场做准备

上架安卓各大应用市场之前需要对自己的应用进行签名加固&#xff0c;签名是为了证明你是这个应用的开发者&#xff0c;软著也是一种方式&#xff0c;这是不做介绍&#xff0c;加固是为了从安全角度给安装包加一个保护层&#xff0c;防止被恶意破解及攻击。下面简单介绍一下签名…

ADO.Net 事务操作

DbTransaction转载于:https://www.cnblogs.com/lxf1117/p/4773742.html

docker安装pocbox(漏洞测试验证辅助平台)

PoCBox - 漏洞测试验证辅助平台 开发这个平台的初衷是帮助自己在漏洞挖掘测试中更加方便快捷的辅助自己进行漏洞验证。 一开始的想法是框架化、模块化&#xff0c;但是开发着开发着就发现有点累&#xff0c;于是采用了原始的方法去开发&#xff1a;原生JavaScriptPHP。 PoCBo…

内存映射获取行数_使用内存映射文件获取巨大的矩阵

内存映射获取行数总览 矩阵可能真的很大&#xff0c;有时甚至比您可以容纳在一个数组中的要大。 您可以通过具有多个数组来扩展最大大小&#xff0c;但这会使堆大小确实很大且效率低下。 一种替代方法是在内存映射文件上使用包装器。 内存映射文件的优点是它们对堆的影响很小&a…

大前端最强vscode教程(基础篇)

这段时间入职了一家外包公司的前端工程师岗位,前端编辑器用起来,前端一般会用到几个编译器,VScode、sublime text3、webstorm、Hbuid等,这里主要介绍VScode. 初次使用vscode时各种不适应,所有需要用到的功能貌似都需要单独安装插件才能用。这让很多初次使用vscode的朋友有…

pdf转换成可编辑的word转换器

头条号&#xff1a;近期有不少网友向我咨询有关PDF转换成可编辑的Word文档的软件&#xff0c;小Q认为网上虽然有很多类似的PDF转换成Word转换器&#xff0c;但是从性能以及转换效果来看&#xff0c;迅捷PDF转换成Word转换器免费版v6.0算是比较理想的选择&#xff0c;有需要的童…

edusrc0day挖掘技巧

网瑞达web资源管理系统0day ps&#xff1a; 作为在edusrc的小白&#xff0c;经常看见大师傅们的刷屏&#xff0c;我也很向往能像大师们一样有一次刷屏的机会&#xff0c;于是有了这一次的渗透之旅。 思路&#xff1a;要想刷屏上分&#xff0c;就得找系统来挖掘&#xff0c;对…

通过NFS访问编年引擎

总览 编年史引擎是数据虚拟化层。 它抽象化了访问&#xff0c;操纵和订阅各种数据源的复杂性&#xff0c;因此该数据的用户无需知道实际存储数据的方式或位置。 这意味着该数据可以在系统之间迁移或以更有效的方式存储&#xff0c;但对于开发人员来说使用起来会很复杂。 基本接…

个人或者公司如何写版权认证的证明文件?

项目场景&#xff1a; 现在好多平台都在做知识付费&#xff0c;比如百度文库、CSDN、微博、头条、知乎等等&#xff0c;因此我想给大家做一些付费文档&#xff0c;想上传到百度文库的知识店铺 问题描述&#xff1a; 一般人不知道这个版权认证文件怎么写&#xff0c;怎么弄&am…

Bypass WAF实战总结

0X00前言 上个月刷了一波洞&#xff0c;然后这个月初远程支持了一个HW&#xff0c;在文件上传getshell的时候&#xff0c;碰到个各式各样的云waf&#xff0c;通过一个月的实战&#xff0c;总结了几个比较实用的技巧&#xff0c;文章总结的不全&#xff0c;只是基于我实战中用到…

Git教程学习总结(分享给热爱学习的你,团队的协作离不开你呀)

目录 Git 教程 Git 安装配置 Git 工作流程 Git 工作区、暂存区和版本库 Git 创建仓库 Git 基本操作 Git 分支管理 Git 查看提交历史 git log git blame Git 标签 Git 远程仓库(Github) Git Gitee Git 服务器搭建 Git 教程 Git 是一个开源的分布式版本控制系统&…

Linux的shell编写

-eq //等于 -ne //不等于 -gt //大于 -lt //小于 ge //大于等于 le //小于等于实验中遇到的问题&#xff1a; 1.NAMEuser1 中间不能有空格 2.[ xxx ] xxx前面和后面要有空格 任务1&#xff1a;使用case语句编…

rest spring_Spring的REST服务发现性,第5部分

rest spring这是关于使用Spring 3.1和Spring Security 3.1和基于Java的配置来建立安全的RESTful Web Service的系列文章的第五篇。 上一篇文章介绍了RESTful服务HATEOAS的可发现性的概念&#xff0c;然后介绍了一些由测试驱动的实际方案。 本文将重点介绍可发现性的实际实现以及…

为啥这么多程序员大佬学习Cortex-M3

Cortex-M3是一个32位处理器内核。内部的数据路径是32位的&#xff0c;寄存器是32位的&#xff0c;存储器接口也是32位的。CM3采用了哈佛结构&#xff0c;拥有独立的指令总线和数据总线&#xff0c;可以让取指与数据访问并行不悖。这样一来数据访问不再占用指令总线&#xff0c;…

Ffuf使用教程

kali安装教程链接&#xff1a;https://www.iculture.cc/cybersecurity/pig210 该工具用途广泛&#xff0c;可用于多种用途。一些用途&#xff1a; • 目录发现&#xff0c;可选择在 URL 中的任何位置进行模糊测试。 • 子域名发现 • 使用各种 HTTP 方法进行模糊测试。 安装 …

PHP中的__toString方法(实现JS里的链式操作)

_toString方法是在打印对象时自动调用的魔术方法,如果不声明会报以下错 Catchable fatal error: Object of class String could not be converted to 示例: PHP里有很多的字符串函数,假如要先过滤字符首尾的空格,再求出字符串的长度,一般会这么写:strlen(trim($str));如果要实…

如何有效地使用反射

本文是我们名为“ 高级Java ”的学院课程的一部分。 本课程旨在帮助您最有效地使用Java。 它讨论了高级主题&#xff0c;包括对象创建&#xff0c;并发&#xff0c;序列化&#xff0c;反射等。 它将指导您完成Java掌握的过程&#xff01; 在这里查看 &#xff01; 目录 1.简…