12

Ho una libreria che viene utilizzata dal cliente e stanno passando l'oggetto DataRequest che ha userid, timeout e alcuni altri campi in esso. Ora uso questo oggetto DataRequest per creare un URL e poi faccio una chiamata HTTP usando RestTemplate e il mio servizio restituisce una risposta JSON che io uso per creare un oggetto DataResponse e restituire a questo oggetto DataResponse.Come impostare RequestConfiguration per richiesta usando RestTemplate?

Di seguito è riportata la mia classe DataClient utilizzata dal cliente passando l'oggetto DataRequest. Sto utilizzando il valore di timeout passato dal cliente in DataRequest per timeout della richiesta se impiega troppo tempo nel metodo getSyncData.

public class DataClient implements Client { 

    private final RestTemplate restTemplate = new RestTemplate(); 
    private final ExecutorService service = Executors.newFixedThreadPool(10); 

    // this constructor will be called only once through my factory 
    // so initializing here 
    public DataClient() { 
     try { 
      restTemplate.setRequestFactory(clientHttpRequestFactory()); 
     } catch (Exception ex) { 
      // log exception 
     } 
    }   

    @Override 
    public DataResponse getSyncData(DataRequest key) { 
     DataResponse response = null; 
     Future<DataResponse> responseFuture = null; 

     try { 
      responseFuture = getAsyncData(key); 
      response = responseFuture.get(key.getTimeout(), key.getTimeoutUnit()); 
     } catch (TimeoutException ex) { 
      response = new DataResponse(DataErrorEnum.CLIENT_TIMEOUT, DataStatusEnum.ERROR); 
      responseFuture.cancel(true); 
      // logging exception here    
     } 

     return response; 
    } 

    @Override 
    public Future<DataResponse> getAsyncData(DataRequest key) { 
     DataFetcherTask task = new DataFetcherTask(key, restTemplate); 
     Future<DataResponse> future = service.submit(task); 

     return future; 
    } 

    // how to set socket timeout value by using `key.getSocketTimeout()` instead of using hard coded 400 
    private ClientHttpRequestFactory clientHttpRequestFactory() { 
     HttpComponentsClientHttpRequestFactory requestFactory = 
      new HttpComponentsClientHttpRequestFactory(); 
     RequestConfig requestConfig = 
      RequestConfig.custom().setConnectionRequestTimeout(400).setConnectTimeout(400) 
       .setSocketTimeout(400).setStaleConnectionCheckEnabled(false).build(); 
     SocketConfig socketConfig = 
      SocketConfig.custom().setSoKeepAlive(true).setTcpNoDelay(true).build(); 

     PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = 
      new PoolingHttpClientConnectionManager(); 
     poolingHttpClientConnectionManager.setMaxTotal(300); 
     poolingHttpClientConnectionManager.setDefaultMaxPerRoute(200); 

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

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

DataFetcherTask classe:

public class DataFetcherTask implements Callable<DataResponse> { 

    private final DataRequest key; 
    private final RestTemplate restTemplate; 

    public DataFetcherTask(DataRequest key, RestTemplate restTemplate) { 
     this.key = key; 
     this.restTemplate = restTemplate; 
    } 

    @Override 
    public DataResponse call() throws Exception { 
     // In a nutshell below is what I am doing here. 
     // 1. Make an url using DataRequest key. 
     // 2. And then execute the url RestTemplate. 
     // 3. Make a DataResponse object and return it. 
    } 
} 

clienti all'interno della nostra società utilizzerà la mia libreria in questo modo, come mostrato di seguito utilizzando mia fabbrica nella loro base di codice -

// if they are calling `getSyncData()` method 
DataResponse response = DataClientFactory.getInstance().getSyncData(key); 

// and if they want to call `getAsyncData()` method 
Future<DataResponse> response = DataClientFactory.getInstance().getAsyncData(key); 

Io sono l'attuazione sync call as async + waiting dal momento che voglio strozzarli con il numero di thread altrimenti possono bombardare il nostro servizio senza alcun controllo.

problema dichiarazione: -

ho intenzione di aggiungere un'altra variabile timeout chiamato socket timeout nella mia classe DataRequest e voglio utilizzare tale valore variabile (key.getSocketTimeout()) nel mio metodo clientHttpRequestFactory() invece di utilizzare rigido codificato 400 valore. Qual è il modo migliore ed efficace per farlo?

In questo momento sto usando Inversion of Control e passando RestTemplate in un costruttore per condividere il RestTemplate tra tutti i miei oggetti Task. Sono confuso ora come utilizzare il valore key.getSocketTimeout() nel mio metodo clientHttpRequestFactory(). Penso che questa sia principalmente la domanda di design su come usare efficientemente RestTemplate in modo da poter usare il valore key.getSocketTimeout() nel mio metodo clientHttpRequestFactory().

Ho semplificato il codice in modo che l'idea diventi chiara cosa sto cercando di fare e io sono su Java 7. L'utilizzo di ThreadLocal è l'unica opzione che ho qui o c'è un modo migliore e ottimizzato?

risposta

1

ThreadLocal è un modo per passare il valore dinamico che normalmente si passa attraverso le proprietà del metodo, ma si sta utilizzando un'API che non si può/non si desidera modificare.

È possibile impostare ThreadLocal (possibile una struttura di dati contenente più valori) in un determinato livello nello stack di thread e utilizzarlo più in alto nello stack.

È questo l'approccio migliore? NO, dovresti davvero passare il valore alla catena delle chiamate al metodo, ma a volte questo non è pratico.

Potete fornire un esempio di come il mio codice sarà simile con ThreadLocal

Si potrebbe iniziare con

static final ThreadLocal<Long> SOCKET_TIMEOUT = new ThreadLocal<>(); 

Per impostarlo si può fare

SOCKET_TIMEOUT .set(key.getSocketTimeout()); 

e per ottenere il valore che puoi fare

long socketTimeout = SOCKET_TIMEOUT.get(); 
+0

Grazie per il tuo suggerimento, quindi quali sono le opzioni che ho che posso utilizzare qui? qualche idea? – user1950349

+0

@ user1950349 AFAICS le altre opzioni saranno peggiori rispetto all'utilizzo di un ThreadLocal. –

+0

Vedo. Puoi fornire un esempio di come sarà il mio codice con 'ThreadLocal'. Posso fare alcuni test di carico e vedere se ha un impatto sulle prestazioni o meno. Non ho ancora lavorato con ThreadLocal e leggere documenti mi sta causando più confusione, quindi non sono sicuro di come posso usarlo nella mia soluzione. – user1950349

4

Come Peter explains, utilizzare ThreadLocal non è una buona idea qui. Ma non sono riuscito a trovare un modo per "passare il valore alla catena delle chiamate di metodo".

Se si utilizza semplicemente "Apache HttpClient", è possibile creare un HttpGet/Put/etc. e chiama semplicemente httpRequest.setConfig(myRequestConfig). In altre parole: imposta una configurazione di richiesta per richiesta (se non viene impostato nulla nella richiesta, viene utilizzata la configurazione della richiesta dal HttpClient che esegue la richiesta).

Al contrario, le chiamate RestTemplate createRequest(URI, HttpMethod) (definiti nella HttpAccessor) che utilizza il ClientHttpRequestFactory. In altre parole: non esiste alcuna opzione per impostare una configurazione di richiesta per richiesta.
Non sono sicuro del motivo per cui Spring ha lasciato questa opzione, sembra un requisito funzionale ragionevole (o forse mi manca ancora qualcosa).

Alcune note sul "possono bombardare il nostro servizio senza alcun controllo":

  • Questo è uno dei motivi per utilizzare il PoolingHttpClientConnectionManager: impostando i valori massimi appropriati, non ci può mai essere più di le connessioni massime specificate in uso (e quindi richieste in esecuzione) allo stesso tempo. L'assunto è che si riutilizza la stessa istanza RestTemplate (e quindi gestore connessione) per ogni richiesta.
  • Per rilevare un flusso in precedenza, specificare una quantità massima di attività in attesa nel threadpool e impostare un gestore di errori corretto (utilizzare workQueue e handler in this constructor).
+0

Sì, ho capito il punto riguardo a "ThreadLocal" ma ci sono altre opzioni che posso usare qui? Sembra che non ci sia modo che io possa usare qui? – user1950349

+0

1) aprire una richiesta di funzionalità per il client Web Spring. 2) usa Apache HttpClient direttamente (non restrestplate) e "manualmente" converti la risposta Json in un DataObject (dovrai reinventare la ruota in qualche modo ma non è così complicato). – vanOekel

+0

sicuro che vorrei aprire la richiesta di funzionalità.Ho provato ad utilizzare Apache HttpClient molto tempo fa, ma non ero in grado di farlo funzionare in modo efficiente in ambiente multithread. Se possibile, puoi fornire un esempio, come posso usare semplicemente 'Apache HttpClient' nella mia soluzione? Ciò sarà di grande aiuto e quindi eseguirò alcuni test di carico per vedere come funziona rispetto a "RestTemplate". – user1950349

Problemi correlati