我正在使用Spring,RxJava和非阻塞数据处理。在我的测试应用程序中,我想实现以下测试工作流程:
RT:请求线程(Tomcat NIO)
WT:工作线程(固定大小为1且队列大小为5的线程池)
HT:Hystrix线程(具有默认设置的Hystrix线程池)
(这只是模拟昂贵的数据处理与对远程资源的依赖关系的一个例子)
我有两个代码变体:
@Async
来调用WT(第2步)和Rx的Observable
来完成剩下的工作(http://localhost:9001/value
)http://localhost:9001/value-rx
)(http://localhost:9002/value
是远程资源)
变体2工作得很好,但变体1(带@Async
)遇到了一些问题。通过分析异常,线程转储,线程池状态和日志文件,看起来ListenableFuture
(由步骤2中的@Async
服务方法返回)无限地阻塞线程池,线程本身是等候。因此,RxJava无法在给定的线程池中根据需要运行回调代码(步骤6)。 30秒后抛出异常并且整个过程失败,因为线程池仍然被阻塞,我不明白为什么。
如果我多次使用变量1,则第二个(以及所有后续请求)在步骤2(而不是6)中失败,因为线程池(size = 1)仍然被ListenableFuture
阻止(下面的堆栈跟踪)。
Variant 2能够“同时”处理多个请求,直到队列已满,即使只有1个请求线程和1个工作线程。
Observable
的实例映射到ListenableFuture
。 为什么@Async
造成这种情况?我该如何解决?
以下是代码:
App1Controller
@Slf4j
@RestController
public class App1Controller {
@Autowired
private App1Service app1Service;
@ResponseBody
@RequestMapping("/value")
public ListenableFuture<String> value() {
final ListenableFuture<String> future;
log.info("before invoke 'app1Service'");
future = this.app1Service.value();
log.info("after invoke 'app1Service'");
return future;
}
@ResponseBody
@RequestMapping("/value-rx")
public ListenableFuture<String> valueRx() {
final Observable<String> observable;
log.info("before invoke 'app1Service'");
observable = this.app1Service.valueRx();
log.info("after invoke 'app1Service'");
return new ObservableListenableFuture<>(observable);
}
}
App1Service
@Slf4j
@Service
public class App1Service {
@Autowired
private TaskExecutor taskExecutor;
@Autowired
private App2Service app2Service;
@Async
public ListenableFuture<String> value() {
final ListenableFuture<String> future;
log.info("before start processing");
this.doSomeStuff();
future = new ObservableListenableFuture<>(this.valueFromApp2Service());
log.info("after start processing");
return future;
}
public Observable<String> valueRx() {
final Observable<String> observable;
log.info("before start processing");
observable = Observable.<String>create(s -> {
this.doSomeStuff();
this.valueFromApp2Service().subscribe(
result -> {
log.info("next (processing)");
s.onNext(result);
},
throwable -> {
log.info("error (processing)");
s.onError(throwable);
},
() -> {
log.info("completed (processing)");
s.onCompleted();
});
}).subscribeOn(Schedulers.from(this.taskExecutor));
log.info("after start processing");
return observable;
}
private Observable<String> valueFromApp2Service() {
final AsyncSubject<String> asyncSubject;
log.info("before invoke 'app2Service'");
asyncSubject = AsyncSubject.create();
this.app2Service.value().observeOn(Schedulers.from(this.taskExecutor)).subscribe(
result -> {
log.info("next (from 'app2Service')");
asyncSubject.onNext(this.doSomeMoreStuff(result));
}, throwable -> {
log.info("error (from 'app2Service')");
asyncSubject.onError(throwable);
}, () -> {
log.info("completed (from 'app2Service')");
asyncSubject.onCompleted();
});
log.info("after invoke 'app2Service'");
return asyncSubject;
}
private void doSomeStuff() {
log.info("do some expensive stuff");
this.sleep(1000);
log.info("finish some expensive stuff");
}
private String doSomeMoreStuff(final String valueFromRemote) {
log.info("do some more expensive stuff with '{}'", valueFromRemote);
this.sleep(2000);
log.info("finish some more expensive stuff with '{}'", valueFromRemote);
return "MODIFIED " + valueFromRemote;
}
private void sleep(final long milliSeconds) {
try {
Thread.sleep(milliSeconds);
} catch (final InterruptedException e) {
e.printStackTrace();
}
}
}
App2Service
@Slf4j
@Service
public class App2Service {
@HystrixCommand(commandKey = "app2")
public Observable<String> value() {
Observable<String> observable;
log.info("before invoke remote service");
observable = new ObservableResult<String>() {
@Override
public String invoke() {
log.info("invoke");
return new RestTemplate().getForEntity("http://localhost:9002/value", String.class).getBody();
}
};
log.info("after invoke remote service");
return observable;
}
}
配置
应用程序(主要/配置类):
@EnableCircuitBreaker
@SpringBootApplication
public class Application {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
@Configuration
@EnableAsync
public static class AsyncConfiguration {
@Bean
public TaskExecutor taskExecutor() {
final ThreadPoolTaskExecutor taskExecutor;
taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(1);
taskExecutor.setMaxPoolSize(1);
taskExecutor.setQueueCapacity(5);
taskExecutor.setThreadNamePrefix("worker-");
return taskExecutor;
}
}
}
application.properties:
server.port=9001
server.tomcat.max-threads=1
hystrix.command.app2.fallback.enabled=false
hystrix.command.app2.execution.isolation.thread.timeoutInMilliseconds=15000
变量1(第一次调用)的日志输出
16:06:31.871 [nio-9001-exec-1] before invoke 'app1Service'
16:06:31.879 [nio-9001-exec-1] after invoke 'app1Service'
16:06:31.887 [ worker-1] before start processing
16:06:31.888 [ worker-1] do some expensive stuff
16:06:32.890 [ worker-1] finish some expensive stuff
16:06:32.891 [ worker-1] before invoke 'app2Service'
16:06:33.135 [x-App2Service-1] before invoke remote service
16:06:33.136 [x-App2Service-1] after invoke remote service
16:06:33.137 [x-App2Service-1] invoke
16:06:33.167 [ worker-1] after invoke 'app2Service'
16:06:33.172 [ worker-1] after start processing
16:07:02.816 [nio-9001-exec-1] Exception Processing ErrorPage[errorCode=0, location=/error]
java.lang.IllegalStateException: Cannot forward after response has been committed
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:328)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:318)
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:439)
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:305)
at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:399)
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:438)
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:291)
at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1709)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:649)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1521)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1478)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
变量2的日志输出(第一次调用)
16:07:54.465 [nio-9001-exec-1] before invoke 'app1Service'
16:07:54.472 [nio-9001-exec-1] before start processing
16:07:54.500 [nio-9001-exec-1] after start processing
16:07:54.500 [nio-9001-exec-1] after invoke 'app1Service'
16:07:54.517 [ worker-1] do some expensive stuff
16:07:55.522 [ worker-1] finish some expensive stuff
16:07:55.522 [ worker-1] before invoke 'app2Service'
16:07:55.684 [x-App2Service-1] before invoke remote service
16:07:55.685 [x-App2Service-1] after invoke remote service
16:07:55.686 [x-App2Service-1] invoke
16:07:55.717 [ worker-1] after invoke 'app2Service'
16:08:05.786 [ worker-1] next (from 'app2Service')
16:08:05.786 [ worker-1] do some more expensive stuff with 'value from app2 service'
16:08:07.791 [ worker-1] finish some more expensive stuff with 'value from app2 service'
16:08:07.791 [ worker-1] completed (from 'app2Service')
16:08:07.791 [ worker-1] next (processing)
16:08:07.792 [ worker-1] completed (processing)
WT的线程转储(在使用变体1之后)
"worker-1" #24 prio=5 os_prio=31 tid=0x00007fe2be8cf000 nid=0x5e03 waiting on condition [0x0000000123413000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c0d68fb0> (a org.springframework.util.concurrent.ListenableFutureTask)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
at java.util.concurrent.FutureTask.get(FutureTask.java:191)
at org.springframework.util.concurrent.SettableListenableFuture.get(SettableListenableFuture.java:122)
at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:110)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- <0x00000006c0d68170> (a java.util.concurrent.ThreadPoolExecutor$Worker)
WT的线程转储(使用变体2之后)
"worker-1" #24 prio=5 os_prio=31 tid=0x00007fc6136dd800 nid=0x5207 waiting on condition [0x000000012d638000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c02f5388> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
解
异步拦截器使用简单的Future
,无法处理ListenableFuture
。在我再次审查了线程转储后,我注意到FutureTask.get
得到了。这是一个阻止电话。这意味着当使用1个线程时,变体1是内置死锁。
此代码有效:
控制器
@ResponseBody
@RequestMapping("/value")
public ListenableFuture<String> value() {
final SettableListenableFuture<String> future;
this.app1Service.value(future);
return future;
}
服务
@Async
public void value(final SettableListenableFuture<String> future) {
this.doSomeStuff();
this.valueFromApp2Service().subscribe(future::set, future::setException);
}
答案 0 :(得分:0)
我想你已经回答了你的问题(或者我遗漏了一些东西):
如果我多次使用变体1,则第二次使用变体1(以及随后的所有变量 请求),在步骤2(而不是6)中失败,因为线程 pool(size = 1)仍然被ListenableFuture阻塞(堆栈 跟踪下面)。
您的TaskExecutor
只有一个可用的帖子,用于@Async
。然后,从该线程,您想再次使用TaskExecutor
作为Observable:
this.app2Service.value().observeOn(Schedulers.from(this.taskExecutor)).subscribe(
但是,您的池中没有可用的线程。如果增加coreSize,或为RxJava内容定义不同的TaskExecutor ,它应该可以正常工作。
修改强>
如果确实需要异步执行app1Service.value()
,您可以从中删除@Asynch,并将任务显式提交给taskExecutor,以便您可以向ListenableFuture
。如果您将结果类型更改为DeferredResult
,则可以在执行ListenableFuture
的回调时设置结果:
@Autowired
private TaskExecutor taskExecutor;
@ResponseBody
@RequestMapping("/value")
public DeferredResult<String> value() {
final DeferredResult<String> dr = new DeferredResult<String>();
taskExecutor.execute(() -> {
final ListenableFuture<String> future = app1Service.value();
future.addCallback(new ListenableFutureCallback<String>() {
@Override
public void onSuccess(String result) {
dr.setResult(result);
}
@Override
public void onFailure(Throwable ex) {
dr.setErrorResult(ex);
}
});
});
return dr;
}
或者,当然,使用您的解决方案,这是更好的。