Как эффективно использовать RestTemplate в многопоточном приложении?


Я использую 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);
Теперь мой вопрос - я не уверен, правильно ли я использую RestTemplate с HttpComponentsClientHttpRequestFactory. Нужен ли мне PoolingHttpClientConnectionManager здесь вместе с RestTemplate вообще или нет? Моя главная цель-эффективно использовать RestTemplate в многопоточной среде. Поскольку моя библиотека будет использоваться под очень большой нагрузкой, поэтому она должна быть очень быстрой. Как и при большой нагрузке, я видел много соединений TIME_WAIT, поэтому я добавил clientHttpRequestFactory() метод для использования с RestTemplate.
3 2

3 ответа:

RestTemplate являетсяпотокобезопасным весной. Поэтому вы можете создать только один экземпляр RestTemplate в своем приложении и поделиться им с несколькими потоками. Это,конечно, предполагает, что вы будете использовать одни и те же свойства HTTP(например, timeout, set alive и т. д.) Для всех. В случае, если вам нужно изменить свойства соединения, вы можете создать пул в начале приложения объектов RestTemplate и использовать его для внедрения экземпляра RestTemplate в вызывающий класс.

Если все запросы, которые вы делаете на 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 сам по себе потокобезопасен . Однако я отмечаю, что ваш private RestTemplate restTemplate не является final. Поэтому не ясно, что это безопасно опубликовано из конструктора DataClient. Ссылка никогда не изменяется, поэтому вы можете просто изменить ее на final, чтобы быть уверенным. К счастью, Ссылка будет безопасно опубликована до того, как любая из ваших задач попытается ее использовать, потому что ExecutorService дает такую гарантию, поэтому я считаю, что ваш код потокобезопасен.