Okhttp3, http2 мультиплексирование POST запрашивает большое время отклика при пиковой загрузке

Мое приложение будет отправлять около 1000 запросов POST / минуту на мой сервер Tomcat (который поддерживает http/2), который будет опрашивать заданный URL и возвращать HTML и время отклика. Я хочу добиться истинного мультиплексирования http/2 для повторного использования соединения TCP между моим приложением и сервером Tomcat. мой клиент использует ohttp, и я могу успешно установить соединение и использовать его в течение длительного времени, но когда количество запросов увеличивается (что-то вроде: в очереди:50, работает:50), время отклика также увеличивается (оно достигает 2000 мс-15000 мс или даже хуже, что обычно занимает 300-500 мс). Я могу понять, что это происходит из-за перегрузки соединения tcp со слишком большим количеством запросов, поэтому я решил открыть несколько соединений tcp и позволить ему распределять нагрузку запроса между соединениями tcp. Я заставляю клиента открывать несколько соединений, используя

Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(100);
dispatcher.setMaxRequestsPerHost(5);
ConnectionPool cp = new ConnectionPool(5, 5, TimeUnit.MINUTES);

С помощью wireshark я вижу 5 открытых соединений, а также вижу, что 4 запроса закрываются сразу после успешного первого рукопожатия, я не знаю, является ли это ожидаемым поведением мультиплексирования http/2.

Если это ожидаемое поведение, как я могу оптимизировать его, чтобы уменьшить время ответа на каждый запрос при пиковой загрузке?

Или возможно ли использовать несколько соединений для распределения нагрузки во время пиковой нагрузки?

Моя примерная программа выглядит следующим образом:

Security.insertProviderAt(Conscrypt.newProvider(), 1);
sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, new TrustManager[] {
    new X509TrustManager() {
        @Override
        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[]{};
        }
    }       
}, new java.security.SecureRandom());
sslSocketFactory = sslContext.getSocketFactory();
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(100);
dispatcher.setMaxRequestsPerHost(5);
ConnectionPool cp = new ConnectionPool(5, 1, TimeUnit.DAYS);

okHttpClient = new OkHttpClient().newBuilder()
.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustManager[0])
.dispatcher(dispatcher)
.connectionPool(cp)
.hostnameVerifier(new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
})
.build();

Мы используем conceryt для включения ALPN в самом jdk8,

try {
    String url = "https://localhost:8081/myAent/getOutput?1000000004286_1520339351002_"+System.currentTimeMillis();
    String json = "<?xml version=\"1.0\" standalone=\"no\"?><UC mid=\"1000000005011\" pollNow=\"true\" use_ipv6=\"false\" rca=\"0\" rcaFN=\"P|TA|D|J|M\" pFtct=\"\" sFtct=\"\" issecondarydc=\"false\" ssDc=\"false\" nocache=\"1\" storeHtmlResp=\"true\" storeTroubleScrnSht=\"false\" moConfig=\"false\" xconlen=\"8265\"  noScreenshotRecheckForSSLErrors=\"true\" avgDnsTime=\"null\" isProxyRequired=\"false\" userroles=\"EVAL_USER\" uid=\"102030230293029021\" retryDelay=\"2\" retry=\"true\" idcLocUrl=\"http://localhost:8080/app/receivemultipartdata\" isRemoteAgent=\"true\" sendHeaders=\"false\" api=\"ab_345nnn4l4lj4lk23nl4k23342lk4l23j4\" ut=\""+(System.currentTimeMillis()+"")+"\" mt=\"URL\" dctimeout=\"70\" pollinterval=\"1440\" locid=\"48\" log=\"1\" currentstatus=\"1\" postUrl=\"https://example.com\"><Url acc=\"\" forced_ips=\"\" use_ipv6=\"false\" client_cert=\"\" mid=\"1000000005011\" sotimeout=\"60\" ua=\"\" ds=\"117.20.43.94\" ucc=\"\" md=\"false\" client_cert_pass=\"\" context=\"default\" unavail_alert=\"\" ssl_protocol=\"\" avail_alert=\"\" enabledns=\"false\" enableBouncyCastle=\"false\" cc=\"false\" a=\"https://www.example.com/tools.html\" upStatusCodes=\"\" regex_alert=\"\" probeproxy=\"false\" m=\"G\" keyword_case=\"0\" regex=\"\" rbc=\"\" t=\"30\" lc=\"English\"><PD></PD><CH hn=\"\" hd=\"_sep_\" hv=\"\"/><AI ps=\"\" un=\"\"/></Url></UC>";
    RequestBody body = RequestBody.create(MediaType.get("application/json; charset=utf-8"), json);
    Request request = new Request.Builder()
        .url(url)
        .post(body)
        .build();
    long nanoStartTime = System.nanoTime();
    okHttpClient.newCall(request).enqueue(new Callback() {
        @Override 
        public void onFailure(Call call, IOException e) {
            System.out.println("okhttp3:: Request failed"+ e);
        }

        @Override
        public void onResponse(Call call, okhttp3.Response responseObj) throws IOException {
            try (ResponseBody body = responseObj.body()) {
                long nanoEndTime = System.nanoTime();
                long nanoDiffTime = TimeUnit.NANOSECONDS.toMillis(nanoEndTime - nanoStartTime);
                System.out.println("okhttp3:: Succeded response ***"+body+"$$$");
                System.out.println("okhttp3:: Request Succeded protocol ***"+responseObj.protocol()+"$$$, time is "+nanoDiffTime);
            }
        }
    });

} catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

Как я могу оптимизировать okhttpclient для использования нескольких сокетов / соединений tcp для достижения мультиплексирования http/2 для распределения нагрузки по запросу.

Клиент: Tomcat apache - 9.0.x, Jdk - 8, библиотека Http - Okhttp3, Os - Ubuntu/Centos, провайдер безопасности - Conscrypt(для поддержки ALPN в jdk 8).

Сервер: Tomcat apache - 9.0.16, Jdk -10.0.1, Os - Ubuntu/Centos, OpenSSL - 1.1.1a для поддержки TLSv1.3

1 ответ

Решение

То, что вы видите, является результатом объединения соединений в OkHttp. OkHttp не знает заранее, будут ли установлены HTTP/2-соединения (против HTTP/1.1), поэтому позволяет установить значение соединений setMaxRequestsPerHost. В настоящее время* нет балансировки нагрузки на стороне клиента, поэтому они быстро объединяются в одно соединение, которое вы видите.

В настоящее время вы можете достичь этого с помощью нескольких клиентов или тщательного управления совершенно разными хостами и соединениями. nb OkHttp будет работать против вас здесь и оптимизировать для типичного случая, когда одно соединение лучше, чем несколько, например, SSL-сертификат определяет перекрывающиеся альтернативные имена субъекта


* Следуйте за этой проблемой для поддержки https://github.com/square/okhttp/issues/4530

Другие вопросы по тегам