如何在多线程应用程序中高效使用 RestTemplate?

How to efficiently use RestTemplate in multithreading application?

我在我的一个图书馆中使用 RestTemplate 作为我的 HttpClient。我不确定我是否在多线程环境中正确使用它,因为我的库将在多线程环境中以非常重的负载使用,所以它必须非常快。

下面是我的 DataClient class:

public class DataClient implements Client {

    private RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
    private ExecutorService executor = Executors.newFixedThreadPool(10);

    // for synchronous call
    @Override
    public DataResponse executeSync(DataKey key) {
        DataResponse dataResponse = null;
        Future<DataResponse> future = null;

        try {
            future = executeAsync(key);
            dataResponse = future.get(key.getTimeout(), TimeUnit.MILLISECONDS);
        } catch (TimeoutException ex) {
            dataResponse = new DataResponse(null, DataErrorEnum.TIMEOUT, DataStatusEnum.ERROR);
            future.cancel(true);
        } catch (Exception ex) {
            dataResponse = new DataResponse(null, DataErrorEnum.CLIENT_ERROR, DataStatusEnum.ERROR);
        }

        return dataResponse;
    }

    //for asynchronous call
    @Override
    public Future<DataResponse> executeAsync(DataKey key) {
        Future<DataResponse> future = null;
        Task task = new Task(key, restTemplate);
        future = executor.submit(task);

        return future;
    }

    // does this looks right?
    private ClientHttpRequestFactory clientHttpRequestFactory() {
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
        // setting 2000 ms as the default timeout for each Http Request
        RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(2000).setConnectTimeout(2000)
                .setSocketTimeout(2000).setStaleConnectionCheckEnabled(false).build();
        SocketConfig socketConfig = SocketConfig.custom().setSoKeepAlive(true).setTcpNoDelay(true).build();

        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
        poolingHttpClientConnectionManager.setMaxTotal(800);
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(700);

        CloseableHttpClient httpClientBuilder = HttpClientBuilder.create()
                .setConnectionManager(poolingHttpClientConnectionManager).setDefaultRequestConfig(requestConfig)
                .setDefaultSocketConfig(socketConfig).build();

        requestFactory.setHttpClient(httpClientBuilder);
        return requestFactory;
    }
}

简单 class 将执行实际任务:

public class Task implements Callable<DataResponse> {

    private final DataKey key;
    private final RestTemplate restTemplate;

    public Task(DataKey key, RestTemplate restTemplate) {
        this.key = key;
        this.restTemplate = restTemplate;
    }

    @Override
    public DataResponse call() {
        DataResponse dataResponse = null;
        String response = null;

        try {
            String url = createURL();
            response = restTemplate.getForObject(url, String.class);

            dataResponse = new DataResponse(response, DataErrorEnum.OK, DataStatusEnum.SUCCESS);
        } catch (RestClientException ex) {
            dataResponse = new DataResponse(null, DataErrorEnum.SERVER_DOWN, DataStatusEnum.ERROR);
        } catch (Exception ex) {
            dataResponse = new DataResponse(null, DataErrorEnum.CLIENT_ERROR, DataStatusEnum.ERROR);
        }

        return dataResponse;
    }
}

下面是我的工厂,我用它来创建 DataClient 的单个实例,这意味着它也将有 RestTemplate.

的单个实例
public class DataClientFactory {

    private DataClientFactory() {}

    private static class ClientHolder {
        private static final DataClient INSTANCE = new DataClient();
    }

    public static Client getInstance() {
        return ClientHolder.INSTANCE;
    }
}

这就是我调用以获取数据的方式:

DataResponse response = DataClientFactory.getInstance().executeSync(dataKey);

现在我的问题是 - 我不确定我是否正确使用 RestTemplateHttpComponentsClientHttpRequestFactory。我需要 PoolingHttpClientConnectionManagerRestTemplate 吗?

我的主要目标是在多线程环境中高效地使用 RestTemplate。由于我的库将在非常重的负载下使用,因此它必须非常快。在重负载下,我看到很多 TIME_WAIT 连接,所以我添加了 clientHttpRequestFactory() 方法以与 RestTemplate.

一起使用

RestTemplate 是 Spring 中的 thread safe。所以你可能想要做的是在你的应用程序中只创建一个 RestTemplate 的实例,并在多个线程之间共享它。这当然是假设您将对所有人使用相同的 HTTP 属性(如超时、设置活动等)。如果您需要更改连接属性,您可以在 RestTemplate 对象的应用启动时创建一个池,并使用它将 RestTemplate 实例注入调用者 class.

如果您对 restTemplate 执行的所有请求都将通过执行程序 ExecutorService executor = Executors.newFixedThreadPool(10);,那么您可以通过这种方式自行管理 restTemplate 连接池。 不需要其他连接管理器。

然而,最好使用具有所有必要配置(超时、连接数等)的 PoolingHttpClientConnectionManager

结果,您编写的代码少得多,因为您不再需要固定线程池执行器, 因为你在 restTemplate 上做的每一个请求都会得到(你上面所做的):

final Future<CPoolEntry> future = this.pool.lease(..)

最后,如果你需要异步调用,也许值得一试http://hc.apache.org/httpcomponents-asyncclient-4.1.x/index.html

RestTemplate itself is thread-safe. However, I note that your private RestTemplate restTemplate is not final. It is therefore not clear that it is safely published from the constructor of DataClient. The reference is never changed, so you can simply change it to final to be sure. Fortunately, the reference will be safely published 在你的任何任务尝试使用它之前,因为 ExecutorService 做了这样的保证,所以我相信你的代码是线程安全的。