Utilisation et optimisation de CloseableHttpClient

Idées d'optimisation HttpClient 1. Pooling 2. Connexion longue 3. Réutilisation de httpclient et httpget 4. Paramètres de configuration raisonnables (nombre maximum de requêtes simultanées, divers délais, nombre de tentatives) 5. Asynchrone 6. Lire la suite code source

1. Contexte
Nous avons une entreprise qui appellera un service basé sur http fourni par d'autres départements, et le volume d'appels quotidien est de dizaines de millions. Utilisez httpclient pour terminer l'entreprise. J'ai regardé le code métier et effectué quelques optimisations car le qps ne pouvait pas être téléchargé auparavant, et je l'ai enregistré ici.

Commencez par comparer avant et après: avant l'optimisation, le temps d'exécution moyen est de 250 ms; après optimisation, le temps d'exécution moyen est de 80 ms, ce qui réduit la consommation de deux tiers, et le conteneur ne bouge plus et alerte que le thread est épuisé, rafraîchissant ~

2. Analyse
L'implémentation d'origine du projet est relativement approximative, c'est-à-dire qu'un httpclient est initialisé à chaque fois qu'une demande est faite, un objet httpPost est généré, exécuté, puis l'entité est extraite du résultat renvoyé, enregistrée sous forme de chaîne, et finalement la réponse et le client sont explicitement fermés. Nous analysons et optimisons un peu:

2.1 surcharge de création répétée httpclient

httpclient est une classe thread-safe, il n'est pas nécessaire d'être créé par chaque thread à chaque fois qu'il est utilisé, gardez-en un globalement.

2.2 La surcharge de la création répétée de connexions TCP

La négociation à trois voies de TCP et la vague de quatre fois des deux processus de bouclage de pied sont trop coûteuses pour les demandes à haute fréquence. Imaginez si nous devons passer 5 ms pour le processus de négociation pour chaque demande, puis pour un système unique avec un qps de 100, nous passerons 500 ms pour la poignée de main et la vague en 1 seconde. Si vous n'êtes pas un dirigeant principal, nous, les programmeurs, ne devrions pas faire un si gros problème, et changer pour garder en vie un moyen de réaliser la réutilisation des connexions!

2.3 La surcharge de la mise en cache répétée des entités

Dans la logique d'origine, le code suivant a été utilisé:

HttpEntity entity = httpResponse.getEntity();
String response = EntityUtils.toString(entity);


Ici, nous sommes équivalents à copier une copie supplémentaire de contenu dans une chaîne, et la httpResponse d'origine conserve toujours une copie du contenu, qui doit être consommée. Dans le cas d'une concurrence élevée et d'un contenu très volumineux, elle consommera beaucoup de mémoire. Et, nous devons explicitement fermer la connexion, moche.

3. Implémentation
Selon l'analyse ci-dessus, nous devons principalement faire trois choses: l'une est le client singleton, l'autre est la connexion keep-alive mise en cache, et la troisième est de mieux gérer les résultats renvoyés. Ne disons pas un, parlons de deux.

En ce qui concerne la mise en cache des connexions, il est facile de penser au regroupement des connexions à la base de données. httpclient4 fournit un PoolingHttpClientConnectionManager en tant que pool de connexions. Ensuite, nous optimisons grâce aux étapes suivantes:

3.1 Définir une stratégie de maintien en vie

En ce qui concerne keep-alive, cet article ne développera pas l'explication, mentionnons simplement que l'utilisation ou non de keep-alive dépend de la situation de l'entreprise, ce n'est pas une panacée. Encore une chose, il y a beaucoup d'histoires entre keep-alive et time_wait / close_wait.

Dans ce scénario d'entreprise, nous sommes équivalents à un petit nombre de clients fixes qui accèdent au serveur pendant longtemps et très fréquemment.Il est très approprié pour activer le keep-alive.

Une autre mention, http keep-alive et tcp KEEPALIVE ne sont pas la même chose. De retour au texte principal, définissez une stratégie comme suit:

ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
    @Override
    public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
        HeaderElementIterator it = new BasicHeaderElementIterator
            (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
        while (it.hasNext()) {
            HeaderElement he = it.nextElement();
            String param = he.getName();
            String value = he.getValue();
            if (value != null && param.equalsIgnoreCase
               ("timeout")) {
                return Long.parseLong(value) * 1000;
            }
        }
        return 60 * 1000;//如果没有约定,则默认定义时长为60s
    }
};


3.2 Configurer un PoolingHttpClientConnectionManager

PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(500);
connectionManager.setDefaultMaxPerRoute(50);//例如默认每路由最高50并发,具体依据业务来定
也可以针对每个路由设置并发数。

3.3 Générer httpclient

httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .setKeepAliveStrategy(kaStrategy)
                .setDefaultRequestConfig(RequestConfig.custom().setStaleConnectionCheckEnabled(true).build())
                .build();


Remarque: l'utilisation de la méthode setStaleConnectionCheckEnabled pour supprimer un lien fermé n'est pas recommandée. Un meilleur moyen consiste à activer manuellement un thread et à exécuter régulièrement les méthodes closeExpiredConnections et closeIdleConnections, comme indiqué ci-dessous.

public static class IdleConnectionMonitorThread extends Thread {
    
    private final HttpClientConnectionManager connMgr;
    private volatile boolean shutdown;
    
    public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
        super();
        this.connMgr = connMgr;
    }
 
    @Override
    public void run() {
        try {
            while (!shutdown) {
                synchronized (this) {
                    wait(5000);
                    // Close expired connections
                    connMgr.closeExpiredConnections();
                    // Optionally, close connections
                    // that have been idle longer than 30 sec
                    connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                }
            }
        } catch (InterruptedException ex) {
            // terminate
        }
    }
    
    public void shutdown() {
        shutdown = true;
        synchronized (this) {
            notifyAll();
        }
    }
    
}


3.4 Réduire les frais généraux lors de l'utilisation de httpclient pour exécuter la méthode

Il convient de noter ici que ne fermez pas la connexion.

Un moyen réalisable d'obtenir du contenu est similaire à la copie du contenu de l'entité:

res = EntityUtils.toString (response.getEntity (), "UTF-8");
EntityUtils.consume (response1.getEntity ());
Cependant, le moyen le plus recommandé est de définir un ResponseHandler, ce qui est pratique pour vous et moi au lieu de vous surprendre Exception et fermez le flux. Ici, nous pouvons regarder le code source pertinent:

 

public <T> T execute(final HttpHost target, final HttpRequest request,
            final ResponseHandler<? extends T> responseHandler, final HttpContext context)
            throws IOException, ClientProtocolException {
        Args.notNull(responseHandler, "Response handler");
 
        final HttpResponse response = execute(target, request, context);
 
        final T result;
        try {
            result = responseHandler.handleResponse(response);
        } catch (final Exception t) {
            final HttpEntity entity = response.getEntity();
            try {
                EntityUtils.consume(entity);
            } catch (final Exception t2) {
                // Log this exception. The original exception is more
                // important and will be thrown to the caller.
                this.log.warn("Error consuming content after an exception.", t2);
            }
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            }
            if (t instanceof IOException) {
                throw (IOException) t;
            }
            throw new UndeclaredThrowableException(t);
        }
 
        // Handling the response was successful. Ensure that the content has
        // been fully consumed.
        final HttpEntity entity = response.getEntity();
        EntityUtils.consume(entity);//看这里看这里
        return result;
    }


Comme vous pouvez le voir, si nous utilisons le resultHandler pour exécuter la méthode execute, la méthode consume sera finalement appelée automatiquement, et cette méthode consume est la suivante:

public static void consume(final HttpEntity entity) throws IOException {
        if (entity == null) {
            return;
        }
        if (entity.isStreaming()) {
            final InputStream instream = entity.getContent();
            if (instream != null) {
                instream.close();
            }
        }
    }


Vous pouvez voir qu'il a finalement fermé le flux d'entrée.

4. Autres
Au cours des étapes ci-dessus, une méthode d'écriture de httpclient qui prend en charge une forte concurrence est essentiellement terminée. Voici quelques configurations et rappels supplémentaires:

4.1 Quelques configurations de timeout de httpclient

CONNECTION_TIMEOUT est le délai d'expiration de la connexion, SO_TIMEOUT est le délai d'expiration du socket, les deux sont différents. Le délai d'expiration de la connexion est le temps d'attente avant que la demande ne soit lancée; le délai d'expiration du socket est le délai d'attente des données.

HttpParams params = new BasicHttpParams();
//设置连接超时时间
Integer CONNECTION_TIMEOUT = 2 * 1000; //设置请求超时2秒钟 根据业务调整
Integer SO_TIMEOUT = 2 * 1000; //设置等待数据超时时间2秒钟 根据业务调整
 
//定义了当从ClientConnectionManager中检索ManagedClientConnection实例时使用的毫秒级的超时时间
//这个参数期望得到一个java.lang.Long类型的值。如果这个参数没有被设置,默认等于CONNECTION_TIMEOUT,因此一定要设置。
Long CONN_MANAGER_TIMEOUT = 500L; //在httpclient4.2.3中我记得它被改成了一个对象导致直接用long会报错,后来又改回来了
 
params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, CONNECTION_TIMEOUT);
params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SO_TIMEOUT);
params.setLongParameter(ClientPNames.CONN_MANAGER_TIMEOUT, CONN_MANAGER_TIMEOUT);


// Teste si la connexion est disponible avant de soumettre la requête
params.setBooleanParameter (CoreConnectionPNames.STALE_CONNECTION_CHECK, true);
 
// De plus, définissez le nombre de tentatives pour le client http, la valeur par défaut est 3; actuellement il est désactivé (si le volume du projet n'est pas suffisant, cette valeur par défaut C’est tout)
httpClient.setHttpRequestRetryHandler (new DefaultHttpRequestRetryHandler (0, false));
4.2 Si nginx est configuré, nginx doit également définir keep-alive pour les deux extrémités

Dans les affaires actuelles, la situation sans nginx est relativement rare. Par défaut, nginx ouvre une longue connexion avec le client et utilise un lien court avec le serveur. Notez les paramètres keepalive_timeout et keepalive_requests côté client, et les paramètres keepalive côté amont. La signification de ces trois paramètres ne sera pas répétée ici.

Ce qui précède est tous mes paramètres. Grâce à ces paramètres, la durée initiale de 250 ms pour chaque requête a été réduite à environ 80 et l'effet a été remarquable.

Le package JAR est le suivant:

<!-- httpclient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.6</version>
</dependency>


code montrer comme ci-dessous:

 

//Basic认证
private static final CredentialsProvider credsProvider = new BasicCredentialsProvider();
//httpClient
private static final CloseableHttpClient httpclient;
//httpGet方法
private static final HttpGet httpget;
//
private static final RequestConfig reqestConfig;
//响应处理器
private static final ResponseHandler<String> responseHandler;
//jackson解析工具
private static final ObjectMapper mapper = new ObjectMapper();
static {
    System.setProperty("http.maxConnections","50");
    System.setProperty("http.keepAlive", "true");
    //设置basic校验
    credsProvider.setCredentials(
            new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
            new UsernamePasswordCredentials("", ""));
    //创建http客户端
    httpclient = HttpClients.custom()
            .useSystemProperties()
            .setRetryHandler(new DefaultHttpRequestRetryHandler(3,true))
            .setDefaultCredentialsProvider(credsProvider)
            .build();
    //初始化httpGet
    httpget = new HttpGet();
    //初始化HTTP请求配置
    reqestConfig = RequestConfig.custom()
            .setContentCompressionEnabled(true)
            .setSocketTimeout(100)
            .setAuthenticationEnabled(true)
            .setConnectionRequestTimeout(100)
            .setConnectTimeout(100).build();
    httpget.setConfig(reqestConfig);
    //初始化response解析器
    responseHandler = new BasicResponseHandler();
}
/*
 * 功能:返回响应
 * @author zhangdaquan
 * @date 2019/1/3 上午11:19
 * @param [url]
 * @return org.apache.http.client.methods.CloseableHttpResponse
 * @exception
 */
public static String getResponse(String url) throws IOException {
    HttpGet get = new HttpGet(url);
    String response = httpclient.execute(get,responseHandler);
    return response;
}
 
/*
 * 功能:发送http请求,并用net.sf.json工具解析
 * @author zhangdaquan
 * @date 2018/8/15 下午2:21
 * @param [url]
 * @return org.json.JSONObject
 * @exception
 */
public static JSONObject getUrl(String url) throws Exception{
    try {
        httpget.setURI(URI.create(url));
        String response = httpclient.execute(httpget,responseHandler);
        JSONObject json = JSONObject.fromObject(response);
        return json;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}
/*
 * 功能:发送http请求,并用jackson工具解析
 * @author zhangdaquan
 * @date 2018/12/24 下午2:58
 * @param [url]
 * @return com.fasterxml.jackson.databind.JsonNode
 * @exception
 */
public static JsonNode getUrl2(String url){
    try {
        httpget.setURI(URI.create(url));
        String response = httpclient.execute(httpget,responseHandler);
        JsonNode node = mapper.readTree(response);
        return node;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}
/*
 * 功能:发送http请求,并用fastjson工具解析
 * @author zhangdaquan
 * @date 2018/12/24 下午2:58
 * @param [url]
 * @return com.fasterxml.jackson.databind.JsonNode
 * @exception
 */
public static com.alibaba.fastjson.JSONObject getUrl3(String url){
    try {
        httpget.setURI(URI.create(url));
        String response = httpclient.execute(httpget,responseHandler);
        com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(response);
        return jsonObject;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

 

Je suppose que tu aimes

Origine blog.csdn.net/superiorpengFight/article/details/104007627
conseillé
Classement