【问题标题】:How to efficiently use RestTemplate in multithreading application?如何在多线程应用程序中高效使用 RestTemplate?
【发布时间】:2015-07-01 03:18:32
【问题描述】:

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

下面是我的 DataClient 类:

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;
    }
}

执行实际任务的简单类:

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 一起使用。

【问题讨论】:

    标签: java multithreading spring apache-httpclient-4.x resttemplate


    【解决方案1】:

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

    【讨论】:

    • 我已经在我的问题中创建了RestTemplate 的一个实例?我已经稍微更新了它,告诉我们我是如何使用 DataClient 类的。所以我对所有调用都使用相同的 HTTP 属性。你能再看看我的问题吗?
    • 我看到您正在为同步调用创建一个新线程。基本上浪费了在不需要时创建新线程的成本。请看stackoverflow.com/a/29447961/2776345
    【解决方案2】:

    如果您在 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

    【讨论】:

      【解决方案3】:

      RestTemplate itself is thread-safe。但是,我注意到您的private RestTemplate restTemplate 不是final。因此不清楚它是来自DataClient 的构造函数的safely published。引用永远不会更改,因此您可以简单地将其更改为 final 以确保。幸运的是,在你的任何任务尝试使用它之前,参考will be safely published,因为ExecutorService 做了这样的保证,所以我相信你的代码是线程安全的。

      【讨论】:

        猜你喜欢
        • 2014-02-10
        • 1970-01-01
        • 1970-01-01
        • 2018-04-28
        • 2010-09-09
        • 2019-05-22
        • 1970-01-01
        • 1970-01-01
        • 2015-03-13
        相关资源
        最近更新 更多