ejb能调用另一个ejb吗_异步EJB只是一个Gi头吗?
ejb能調(diào)用另一個ejb嗎
在之前的文章( 此處和此處 )中,我展示了當服務器負載沉重時,創(chuàng)建非阻塞異步應用程序可以提高性能。 EJB 3.1引入了@Asynchronous批注,用于指定方法將在將來的某個時間返回其結(jié)果。 Javadocs聲明必須返回void或Future 。 下面的清單顯示了使用此注釋的服務示例:
Service2.java
注釋在第4行上。該方法返回String類型的Future ,并在第10行上通過將輸出包裝在AsyncResult 。 在客戶端代碼調(diào)用EJB方法時,容器攔截了該調(diào)用并創(chuàng)建了一個任務,它將在另一個線程上運行,以便它可以立即返回Future 。 當容器然后使用其他線程運行任務時,它將調(diào)用EJB的方法并使用AsyncResult來完成給定調(diào)用者的Future 。 即使看起來與Internet上所有示例中的代碼完全一樣,此代碼也存在一些問題。 例如, Future類僅包含用于獲取Future結(jié)果的阻塞方法,而不包含用于在回調(diào)完成時注冊回調(diào)的任何方法。 這將導致如下所示的代碼,當容器處于加載狀態(tài)時,這是很糟糕的:
客戶端程序
//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...這種代碼是不好的,因為它導致線程阻塞,這意味著它們在這段時間內(nèi)無法做任何有用的事情。 當其他線程可以運行時,需要進行上下文切換,這會浪費時間和精力(有關(guān)成本或我以前的文章的結(jié)果,請參見這篇好文章)。 像這樣的代碼會使已經(jīng)處于負載狀態(tài)的服務器承受更大的負載,并停止運行。
那么是否有可能使容器異步執(zhí)行方法,而編寫不需要阻塞線程的客戶端呢? 它是。 以下清單顯示了一個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行開始將數(shù)據(jù)寫入響應,但是有趣的是第13行中的調(diào)用異步服務方法的行。 我們沒有將Future用作返回類型,而是向其傳遞了CompletableFuture ,它用于將結(jié)果返回給我們。 怎么樣? 第16行會啟動異步servlet上下文,因此我們?nèi)匀豢梢栽赿oGet方法返回后寫入響應。 從第17行開始,然后有效地在CompletableFuture上注冊了一個回調(diào),一旦CompletableFuture完成并返回結(jié)果,該回調(diào)將被調(diào)用。 這里沒有阻塞代碼–沒有線程被阻塞,沒有線程被輪詢,等待結(jié)果! 在負載下,服務器中的線程數(shù)可以保持最少,從而確保服務器可以高效運行,因為需要較少的上下文切換。
服務實現(xiàn)如下所示:
Service3.java
@Stateless public class Service3 {@Asynchronouspublic void foo(CompletableFuture<String> cf) {// simulate some long running processThread.sleep(5000);cf.complete("bar");} }第7行確實很丑陋,因為它會阻塞,但假裝這是代碼調(diào)用使用大多數(shù)Web服務客戶端和JDBC驅(qū)動程序會阻塞的API調(diào)用在Internet或較慢的數(shù)據(jù)庫中遠程部署的Web服務的代碼。 或者,使用異步驅(qū)動程序 ,當結(jié)果可用時,完成第9行所示的將來。然后向CompletableFuture發(fā)出信號,可以調(diào)用在先前清單中注冊的回調(diào)。
這不只是使用簡單的回調(diào)嗎? 這肯定是相似的,下面的兩個清單顯示了使用自定義回調(diào)接口的解決方案。
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);} }同樣,在客戶端中,絕對沒有任何阻塞。 但是,由于以下原因,使用CompletableFuture的AsyncServlet2和Service3類的早期示例更好一些:
- CompletableFuture的API允許出現(xiàn)異常/失敗,
- CompletableFuture類提供用于異步執(zhí)行回調(diào)和相關(guān)任務的方法,即在fork-join池中,以便整個系統(tǒng)使用盡可能少的線程運行,從而可以更有效地處理并發(fā)性,
- 可將CompletableFuture與其他對象結(jié)合使用,以便您可以注冊一個回調(diào),僅在多個CompletableFuture完成時才能調(diào)用該回調(diào),
- 回調(diào)不會立即被調(diào)用,而是池中有限數(shù)量的線程按它們應運行的順序為CompletableFuture的執(zhí)行提供服務。
在第一個清單之后,我提到異步EJB方法的實現(xiàn)存在一些問題。 除了阻塞客戶端之外,另一個問題是,根據(jù)EJB 3.1 Spec的 4.5.3章,客戶端事務上下文不會通過異步方法調(diào)用傳播。 如果您想使用@Asynchronous批注創(chuàng)建兩個可以并行運行并在單個事務中更新數(shù)據(jù)庫的方法,那么它將不起作用。 這在某種程度上限制了@Asynchronous注釋的使用。
使用CompletableFuture ,您可能會認為可以在同一個事務上下文中并行運行多個任務,方法是先在EJB中啟動一個事務,然后創(chuàng)建多個可運行對象,并使用runAsync方法運行它們,該方法在執(zhí)行中運行它們池,然后注冊一個回調(diào)以使用allOf方法完成所有操作后allOf 。 但是您可能會因為多種原因而失敗:
- 如果您使用容器管理的事務,那么一旦導致事務開始的EJB方法將控制權(quán)返回給容器,事務將被提交-如果那時您的期貨還沒有完成,您將不得不阻止運行EJB方法的線程這樣它就等待并行執(zhí)行的結(jié)果,而阻塞正是我們要避免的,
- 如果運行任務的單個執(zhí)行池中的所有線程都被阻塞,等待它們的數(shù)據(jù)庫調(diào)用應答,那么您將有可能創(chuàng)建性能不佳的解決方案–在這種情況下,您可以嘗試使用非阻塞的異步驅(qū)動程序 ,但不能每個數(shù)據(jù)庫都有這樣的驅(qū)動程序,
- 一旦任務在不同的線程(例如執(zhí)行池中的線程)上運行,線程本地存儲(TLS)就不再可用,因為正在運行的線程與將工作提交到執(zhí)行池并進行設置的線程不同在提交工作之前將值存入TLS,
- 諸如EntityManager 類的資源不是線程安全的 。 這意味著你無法通過EntityManager成提交給池的任務,而每個任務需要得到它自己的保持EntityManager實例,而是創(chuàng)建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行沒有問題,您可以回滾在容器調(diào)用EJB方法時在第9行自動啟動的事務。 但是該事務將不是可能由調(diào)用第9行的代碼啟動的全局事務。第16行也沒有問題,您可以使用EntityManager寫入由第9行開始的事務內(nèi)部的數(shù)據(jù)庫。顯示了在不同線程上運行代碼的另一種方式,即使用Java EE 7中引入的ManagedExecutorService 。但是,這在任何時候都依賴TLS時也會失敗,例如,第22行和第31行會導致異常,因為在第9行啟動的事務無法定位,因為使用TLS來定位,并且第21-35行中的代碼使用與第19行之前的代碼不同的線程運行。
下一個清單顯示,第11-14行在CompletableFuture上注冊的完成回調(diào)也與第4-10行運行在不同的線程中,因為在第6行的回調(diào)之外啟動提交事務的調(diào)用將在第6行失敗再次參考圖13,因為第13行的調(diào)用在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行可以找到該事務,它是否仍處于活動狀態(tài)或容器是否已關(guān)閉它也值得懷疑。
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中存儲會話(即與數(shù)據(jù)庫的連接) ,或者被配置為將該會話的范圍限定為當前的JTA事務 ,而該事務又依賴于TLS。 或以使用從EJBContextImpl.getCallerPrincipal提取的Principal進行安全性檢查為例,該Principal對AllowedMethodsInformation.checkAllowed進行調(diào)用,然后再調(diào)用使用TLS的CurrentInvocationContext并僅返回(如果在TLS中未找到任何上下文),而不是進行適當?shù)臋?quán)限檢查如第112行所示。
這些對TLS的依賴意味著,在使用CompletableFuture或Java SE fork-join池或其他線程池(無論是否由容器管理)時,許多標準Java EE功能將不再起作用。
公平起見,對于Java EE,我在這里所做的事情都是按設計的! 規(guī)范實際上禁止在EJB容器中啟動新線程。 我記得十多年前我曾經(jīng)使用過舊版本的Websphere進行過一次測試–啟動線程會引發(fā)異常,因為容器確實嚴格遵守規(guī)范。 這是有道理的:不僅因為線程數(shù)應由容器管理,還因為Java EE對TLS的依賴意味著使用新線程會導致問題。 從某種意義上講,這意味著使用CompletableFuture是非法的,因為它使用了不受容器管理的線程池(該池由JVM管理)。 使用Java SE的ExecutorService也是如此。 Java EE 7的ManagedExecutorService是一個特例-它是規(guī)范的一部分,因此您可以使用它,但是您必須了解這樣做的含義。 EJB上的@Asynchronous批注也是如此。
結(jié)果是可以在Java EE容器中編寫異步非阻塞應用程序,但是您確實必須知道自己在做什么,并且可能必須手動處理安全性和事務之類的事情,這確實是個問題。首先使用Java EE容器的原因。
那么是否有可能編寫一個容器來消除對TLS的依賴以克服這些限制? 的確如此,但是解決方案不僅僅取決于Java EE。 該解決方案可能需要更改Java語言。 許多年前,在依賴注入之前,我曾經(jīng)寫過POJO服務,它在方法之間傳遞了JDBC連接,即作為服務方法的參數(shù)。 我這樣做是為了可以在同一事務內(nèi)(即在同一連接上)創(chuàng)建新的JDBC語句。 我所做的與JPA或EJB容器所需要做的事情并沒有什么不同。 但是,現(xiàn)代框架沒有使用TLS作為顯式傳遞連接或用戶之類的東西的方式,而是使用TLS作為集中存儲“上下文”的位置,例如,連接,事務,安全信息等。 只要您在同一線程上運行,TLS就是隱藏此類樣板代碼的好方法。 讓我們假裝TLS從未被發(fā)明過。 我們?nèi)绾卧诓粡娭泼總€方法都將其作為參數(shù)的情況下傳遞上下文? Scala的implicit關(guān)鍵字是一種解決方案。 您可以聲明參數(shù)可以隱式定位,這使編譯器將其添加到方法調(diào)用中成為問題。 因此,如果Java SE引入了這種機制,則Java EE不需要依賴TLS,我們可以構(gòu)建真正的異步應用程序,在該應用程序中,容器可以像今天一樣通過檢查注釋來自動處理事務和安全性! 也就是說,當使用同步Java EE時,容器會知道何時提交事務-在啟動事務的方法調(diào)用結(jié)束時。 如果您異步運行,則需要顯式關(guān)閉事務,因為容器不再知道何時執(zhí)行此操作。
當然,保持不阻塞的需要以及因此不依賴TLS的需求在很大程度上取決于當前的方案。 我不相信我今天在這里描述的問題是當今的普遍問題,而是它們是處理市場細分市場的應用程序所面臨的問題。 只需看一下似乎為優(yōu)秀的Java EE工程師提供的工作數(shù)量,而同步編程就是其中的標準。 但是,我確實相信,規(guī)模更大的IT軟件系統(tǒng)會變得越來越多,它們處理的數(shù)據(jù)越多,阻塞API就會成為一個問題。 我還認為,當前硬件增長速度的下降使這個問題更加復雜。 有趣的是,Java是否a)是否需要跟上異步處理的趨勢,以及b)Java平臺是否會采取行動來固定對TLS的依賴。
翻譯自: https://www.javacodegeeks.com/2015/08/is-asynchronous-ejb-just-a-gimmick.html
ejb能調(diào)用另一個ejb嗎
總結(jié)
以上是生活随笔為你收集整理的ejb能调用另一个ejb吗_异步EJB只是一个Gi头吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 门市牌子备案(门市备案表)
- 下一篇: threadsafe_Agrona的Th