我知道在EJB中乱搞线程是一个很大的禁忌,但我想就如何处理这种情况征求意见。我的EJB正在调用外部Web服务,有时可能会返回“忙”状态。当发生这种情况时,我想等待一段时间,然后使用与以前相同的数据重新提交请求。
实现这一目标的最佳方式是什么?
答案 0 :(得分:12)
EJB 3.1带来了一个新的@Asynchronous
feature,你可以利用它:
@Asynchronous
@TransactionAttribute(NOT_SUPPORTED)
public Future<WebServiceResult> callWebService(int retries) {
WebServiceResult result = webService.call();
if (!result.equals(BUSY)) {
return result;
}
if (retries <= 0) {
throw new TooBusyException();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return callWebService(retries - 1);
}
然后只需通过以下方式呼叫您的网络服务:
Future<WebServiceResult> result = yourEJB.callWebService(1);
// Can do some interesting stuff here.
// ...
// ...
result.get(2, SECONDS); // Block for up to 2 seconds.
正如您所见,您可以免费获得可配置的重试次数和超时时间。
这与仅调用Thread.sleep()
有什么不同?返回Future
更明确,更易于管理。我也不认为Thread.sleep()
是有害的。唯一的问题是这个EJB实例现在可以更长时间被其他客户端重用。使用Future
异步调用在其他EJB和线程池中发生。至于Thread#interrupt()
在catch块中的重要性,请参阅Why invoke Thread.currentThread.interrupt() when catch any InterruptException?
另一个想法:使用调用Web服务的方面,捕获BusyException
一次并重试。
答案 1 :(得分:11)
在EJB restrictions FAQ中明确指出你
不应该创建或管理线程
将一个线程置于睡眠状态算作“管理”它。
在您的情况下,当Web服务返回“忙”状态时,您可以安排作业稍后重试发送消息,例如使用Quartz Scheduler。执行将在那里结束,任何进一步的处理都应委托给作业调度程序。
答案 2 :(得分:0)
但是外部Web服务是外部的,您正在打开它的网络连接,并且您希望进行一些管理工作。这就是JCA的用途,而不是EJB。
答案 3 :(得分:0)
以下是带有简单控件池的建议(或替代方法):
1-将上下文(EJB)作为参数传递给方法(其余端点,调度程序,默认方法)
2-使用互补的调度程序或实体标志控制状态
3-注意数据/处理量
4-建议:指标,日志和测试,强烈建议测试
5-此代码在SpringBoot上,但已在EJB上下文下在Jboss(经过修改)中进行了测试-仔细测试
6-根据需要使用/修改:(发送建议/评论)
BaseControlExecutor.java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class BaseControlExecutor {
private final ScheduledThreadPoolExecutor poolExec = new ScheduledThreadPoolExecutor(2);
public void execWithTimeout(final Runnable runnable, long timeout,
TimeUnit timeUnit) throws Exception {
execWithTimeout(new Callable<Object>() {
@Override
public Object call() throws Exception {
runnable.run();
return null;
}
}, timeout, timeUnit);
}
public <T> T execWithTimeout(Callable<T> callable, long timeout, TimeUnit timeUnit) throws Exception {
final Future<T> future = poolExec.submit(callable);
try {
return future.get(timeout, timeUnit);
} catch (TimeoutException e) {
future.cancel(true);
throw e;
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof Error) {
throw (Error) t;
} else if (t instanceof Exception) {
throw (Exception) t;
} else {
throw new IllegalStateException(t);
}
}
}
}
EndpointControlRest.java
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@RestController
@RequestMapping(value = "/report")
@Api(tags = "Endpoint of Future")
public class EndpointControlRest extends BaseControlExecutor {
Logger logger = LoggerFactory.getLogger(EndpointControlRest.class);
//single metric of execution
protected final AtomicLong counter = new AtomicLong();
@GetMapping(path = "/withThread", produces = { "application/json" })
@ApiOperation(value = "Return Hello count.")
public String greeting() {
Long countRunner = counter.incrementAndGet();
String json = ""; //or EJB context to use in Thread - becareful
new Thread(() -> {
try {
execWithTimeout(new Runnable() {
@Override
public void run() {
Instant start = Instant.now();
logger.info("Report init - " + countRunner);
//generating reports
generateBackgroundReport(json);
logger.info("Report End - " + countRunner);
Instant finish = Instant.now();
long timeElapsed = Duration.between(start, finish).toMillis();
logger.info("###DEBUG - " + countRunner + " - OK |Time exe: " + timeElapsed);
}
}, 120, TimeUnit.SECONDS);
} catch (TimeoutException e) {
logger.info("###DEBUG - " + countRunner + " - Timeout - " + e.getMessage());
} catch (Exception e) {
logger.info("###DEBUG - " + countRunner + " - Exception - " + e.getMessage());
}
}).start();
logger.info("####DEBUG - Rest call released");
return "Hello " + countRunner;
}
public String generateBackgroundReport(String json){
//simulating work
Long x = 0L;
for(Long i = 0L; i < 1000000000L; i ++){
x = i + 1;
}
logger.info("####DEBUG -report: " + x);
return "OK";
}
}