我正在开发一个库,它将对象 DataRequest
作为输入参数和该对象的基础,我将构建一个URL,然后调用我们的应用服务器使用apache http客户端,然后将响应返回给使用我们库的客户。有些客户会调用 executeSync
方法来获取相同的功能,而有些客户会调用我们的 executeAsync
方法来获取数据。
executeSync()
- 等到我有结果,然后返回结果。executeAsync()
- 立即返回一个Future,如果需要,可以在其他事情完成后处理。以下是我的 DataClient
类,它有两种方法:
public class DataClient implements Client {
private final ForkJoinPool forkJoinPool = new ForkJoinPool(16);
private CloseableHttpClient httpClientBuilder;
// initializing httpclient only once
public DataClient() {
try {
RequestConfig requestConfig =
RequestConfig.custom().setConnectionRequestTimeout(500).setConnectTimeout(500)
.setSocketTimeout(500).setStaleConnectionCheckEnabled(false).build();
SocketConfig socketConfig =
SocketConfig.custom().setSoKeepAlive(true).setTcpNoDelay(true).build();
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager =
new PoolingHttpClientConnectionManager();
poolingHttpClientConnectionManager.setMaxTotal(300);
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(200);
httpClientBuilder =
HttpClientBuilder.create().setConnectionManager(poolingHttpClientConnectionManager)
.setDefaultRequestConfig(requestConfig).setDefaultSocketConfig(socketConfig).build();
} catch (Exception ex) {
// log error
}
}
@Override
public List<DataResponse> executeSync(DataRequest key) {
List<DataResponse> responsList = null;
Future<List<DataResponse>> responseFuture = null;
try {
responseFuture = executeAsync(key);
responsList = responseFuture.get(key.getTimeout(), key.getTimeoutUnit());
} catch (TimeoutException | ExecutionException | InterruptedException ex) {
responsList =
Collections.singletonList(new DataResponse(DataErrorEnum.CLIENT_TIMEOUT,
DataStatusEnum.ERROR));
responseFuture.cancel(true);
// logging exception here
}
return responsList;
}
@Override
public Future<List<DataResponse>> executeAsync(DataRequest key) {
DataFetcherTask task = new DataFetcherTask(key, this.httpClientBuilder);
return this.forkJoinPool.submit(task);
}
}
以下是我的 DataFetcherTask
类,它还有一个静态类 DataRequestTask
,通过制作网址调用我们的应用服务器:
public class DataFetcherTask extends RecursiveTask<List<DataResponse>> {
private final DataRequest key;
private final CloseableHttpClient httpClientBuilder;
public DataFetcherTask(DataRequest key, CloseableHttpClient httpClientBuilder) {
this.key = key;
this.httpClientBuilder = httpClientBuilder;
}
@Override
protected List<DataResponse> compute() {
// Create subtasks for the key and invoke them
List<DataRequestTask> requestTasks = requestTasks(generateKeys());
invokeAll(requestTasks);
// All tasks are finished if invokeAll() returns.
List<DataResponse> responseList = new ArrayList<>(requestTasks.size());
for (DataRequestTask task : requestTasks) {
try {
responseList.add(task.get());
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
return Collections.emptyList();
}
}
return responseList;
}
private List<DataRequestTask> requestTasks(List<DataRequest> keys) {
List<DataRequestTask> tasks = new ArrayList<>(keys.size());
for (DataRequest key : keys) {
tasks.add(new DataRequestTask(key));
}
return tasks;
}
// In this method I am making a HTTP call to another service
// and then I will make List<DataRequest> accordingly.
private List<DataRequest> generateKeys() {
List<DataRequest> keys = new ArrayList<>();
// use key object which is passed in contructor to make HTTP call to another service
// and then make List of DataRequest object and return keys.
return keys;
}
/** Inner class for the subtasks. */
private static class DataRequestTask extends RecursiveTask<DataResponse> {
private final DataRequest request;
public DataRequestTask(DataRequest request) {
this.request = request;
}
@Override
protected DataResponse compute() {
return performDataRequest(this.request);
}
private DataResponse performDataRequest(DataRequest key) {
MappingHolder mappings = DataMapping.getMappings(key.getType());
List<String> hostnames = mappings.getAllHostnames(key);
for (String hostname : hostnames) {
String url = generateUrl(hostname);
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(generateRequestConfig());
httpGet.addHeader(key.getHeader());
try (CloseableHttpResponse response = httpClientBuilder.execute(httpGet)) {
HttpEntity entity = response.getEntity();
String responseBody =
TestUtils.isEmpty(entity) ? null : IOUtils.toString(entity.getContent(),
StandardCharsets.UTF_8);
return new DataResponse(responseBody, DataErrorEnum.OK, DataStatusEnum.OK);
} catch (IOException ex) {
// log error
}
}
return new DataResponse(DataErrorEnum.SERVERS_DOWN, DataStatusEnum.ERROR);
}
}
}
对于每个DataRequest
对象,都有一个DataResponse
对象。现在,一旦有人通过传递DataRequest
对象来调用我们的库,我们在内部创建List<DataRequest>
对象,然后我们并行调用每个DataRequest
对象并返回List<DataResponse>
返回每个{{1}列表中的对象将对相应的DataResponse
对象进行响应。
以下是流程:
DataRequest
对象来致电DataClient
课程。他们可以根据需要调用DataRequest
或executeSync()
方法。executeAsync()
类(DataFetcherTask
个RecursiveTask
个ForkJoinTask's
个类中,给定一个key
个对象DataRequest
,我将生成List<DataRequest>
,然后为列表中的每个DataRequest
对象并行调用每个子任务。这些子任务在与父任务相同的ForkJoinPool
中执行。DataRequestTask
类中,我通过创建一个URL并返回其DataRequest
对象来执行每个DataResponse
对象。问题陈述:
由于此库是在非常高吞吐量的环境中调用的,因此它必须非常快。对于同步调用,可以在单独的线程中执行吗?在这种情况下,它将导致线程的额外成本和资源以及线程上下文切换的成本,所以我有点混淆。此外我在这里使用ForkJoinPool
这将节省我使用额外的线程池,但这是正确的选择吗?
有没有更好更有效的方法来做同样的事情呢?我正在使用Java 7并且可以访问Guava库,所以如果它可以简化任何事情,那么我也是开放的。
看起来我们在非常繁重的负载下运行时会看到一些争用。在非常繁重的负载下运行时,这段代码有没有办法进入线程争用?
答案 0 :(得分:0)
我认为在您的情况下最好使用异步http调用,请参阅link:HttpAsyncClient。而且您不需要使用线程池。
在executeAsync方法中创建空的CompletableFuture&lt; DataResponse&gt;()并将其传递给客户端调用,在回调调用中通过调用complete来设置completableFuture的结果(如果异常引发则为completeExceptionally)。 ExecuteSync方法实现看起来不错。
修改强>
对于java 7,只需要替换一个completableFuture来保证guava中的实现,比如ListenableFuture或类似的东西
答案 1 :(得分:0)
使用ForkJoinPool
的选择是正确的,它设计用于许多小任务的效率:
ForkJoinPool与其他类型的ExecutorService的区别主要在于使用工作窃取:池中的所有线程都试图查找并执行提交给池的任务和/或由其他活动任务创建的任务(最终阻止等待工作)都不存在)。当大多数任务产生其他子任务时(如大多数ForkJoinTasks),以及从外部客户端向池提交许多小任务时,这可以实现高效处理。特别是在构造函数中将asyncMode设置为true时,ForkJoinPools也适用于从未加入的事件样式任务。
我建议在构造函数中尝试asyncMode = true
,因为在您的情况下,任务永远不会加入:
public class DataClient implements Client {
private final ForkJoinPool forkJoinPool = new ForkJoinPool(16, ForkJoinPool.ForkJoinWorkerThreadFactory, null, true);
...
}
对于executeSync()
,您可以使用forkJoinPool.invoke(task)
,这是在池中执行同步任务执行以实现资源优化的托管方式:
@Override
public List<DataResponse> executeSync(DataRequest key) {
DataFetcherTask task = new DataFetcherTask(key, this.httpClientBuilder);
return this.forkJoinPool.invoke(task);
}
如果您可以使用Java 8,那么已经优化了一个公共池:ForkJoinPool.commonPool()