【问题标题】:Multiple HTTPS requests with limited connections in javajava中连接有限的多个HTTPS请求
【发布时间】:2015-12-18 23:31:06
【问题描述】:

我正在处理一项任务,我需要从我的 java 程序向 HTTPS url 发出多个请求并读取响应。这个过程将针对不同的请求重复多次。

如果我只使用 1 个线程按顺序发出请求,每个请求的延迟(请求和响应之间的时间差)约为 300 毫秒。吞吐量约为每秒 3.3 个请求。

但是,由于目标是获得高吞吐量,我决定使用多个线程,每个线程在给定时间点发出请求。

一些重要的细节:

  1. 我只使用那些 URL 实例数作为线程数。这个想法是每个线程使用单个 URL 实例并在每次发出请求时调用 new URL(url).openConnection()。

  2. 每次读取响应后,我都会使用 inputStream.close() 关闭输入流,这种关闭将使套接字可重用。

  3. 我没有调用 httpConnectionURL.disconnect(),因为这将关闭底层套接字。

  4. 我已使用 System.setProperty("http.maxConnections", threadCount); 将 http.maxConnections 设置为线程数;

  5. 我还在使用“netstat -a | grep | wc -l”检查在任何给定时间点打开的连接数,这总是给出等于或高于预期的线程数的数字。

即使做了所有这些,我也没有得到预期的吞吐量。 对于 1 个线程,当我获得 3.3 的吞吐量时,我假设使用 100 个线程我应该获得至少每秒 300 的吞吐量。

谁能解释一下我哪里出错了。或任何其他更好的解决方案。 下面是我的代码 sn-p。

Main Class:
    public static void main(String[] args)
    {
      URL[] urlConnArray = new URL[threadCount];
      for(int j = 0;j < urlConnArray.length;j++)
        urlConnArray[j] = new URL(regURL);

      System.setProperty("http.keepalive", "true");
      System.setProperty("http.maxConnections", String.valueOf(threadCount));

      for(int i=0;i<1000000;i++)
      {
         Thread regThread = new Thread(new RegisterThread(urlConnArray[i]));
         regThread.start();
      }
    }

RegisterThread Class:
    public class RegisterThread implements Runnable
    {
       httpConn = (HttpURLConnection) urlConnArray[i].openConnection();
       httpConn.setUseCaches(false);
       httpConn.setDoOutput(true);
       httpConn.setRequestMethod("POST");
       httpConn.setRequestProperty("Content-Type", "application/json" );
       //Prepare the request body.....
       long requestTime = System.currentTimeMillis();
       InputStream is = httpConn.getInputStream();
       long responseTime = System.currentTimeMillis();
       long latency = responseTime - requestTime;
       reader = new BufferedReader(new InputStreamReader(is));  
       StringBuffer response = new StringBuffer();
       String line = "";
       while ((line = reader.readLine()) != null) 
       {
          response.append(line);
       } 
       is.close();
     }

【问题讨论】:

    标签: java multithreading url connection httpurlconnection


    【解决方案1】:

    很遗憾,你的假设是错误的。

    当我获得 3.3 的吞吐量时为 1 个线程。我假设使用 100 个线程,我应该获得每秒至少 300 个的吞吐量。

    多线程的性能取决于 CPU 的核心数。如果您在一个核心 CPU 上运行应用程序,您可能不会注意到多线程带来的任何好处。由于单核 CPU 导致多线程之间的上下文切换,甚至情况会变得更糟。在这种情况下,您的结果不如同一用例的单线程处理。

    如果你有 100 个核心 CPU,你可以获得类似于每个线程 300-500 毫秒的结果,有趣的是线程没有在对象/方法上使用很多共享锁。

    如果您想微调性能,我建议您进行以下更改(一般而言,而不是针对您的问题)

    1) 使用 ExecutorService 等 java 高级线程功能,并将线程池计数声明为 CPU 的核心数。

    2) 尽量避免线程间共享锁。

    java support for parallel processing查看java对多核CPU中多线程的支持

    执行器服务代码示例。

    // Here replace 10 with number > number of CPU cores for better performance
    
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    
    executorService.execute(new Runnable() {
        public void run() {
            // Here add your business logic
            System.out.println("Asynchronous task");
        }
    });
    
    executorService.shutdown();
    

    看看Executor Service usage

    【讨论】:

      【解决方案2】:

      线程过多

      • 代码在 1,000,000 处创建了太多的并发线程(代码循环 1M 次创建一个新线程并每次都启动)。

      • 正在池中的 URL 定义,并且每次都打开一个新连接

      • 您还可以从各种来源(Apache、Grizzly、Netty 等)提供的一些自定义 REST 客户端获得比内置 JDK URL/连接类更好的性能。

      I/O 绑定

      应用程序受 I/O 限制,而不是 CPU 限制。

      这个应用程序应该有比内核多得多的线程,但不是 100 万! (我很惊讶它没有耗尽内存)。

      你应该使用更多线程的原因

      1. I/O 阻塞当前线程,同时等待来自远程系统的数据

      2. 在此期间可以更好地利用 CPU 执行其他工作

      3. 因此,使用比 CPU 内核更多的线程来阻塞 I/O 将导致更好的 CPU 使用率和更好的 I/O。

      Paul Tyma's overview on synchronous I/O & Non-blocking I/O (2008) 是一本非常有用的读物​​。

      Java 8 流媒体

      虽然 Java 8 中的 JDK 为 CPU 绑定任务提供了流式处理基础架构(此处不适合),但我们创建了一个库 simple-react,它是专为您的目的而设计的 - 在您有阻塞 IO 的情况下提高系统吞吐量。使用 simple-react 你可以创建一个类似这样的 Stream

         LazyReact streamBuilder = new LazyReact(threadCount);  //create a Stream builder with x threads
      
         streamBuilder.range(0,1000000)
                      .map(i-> new RegisterThread(urlConnArray[i])))
                      .forEach(url-> url.run());
      

      【讨论】:

      • 好文章。我只有一个疑问。不明白为什么 ExecutorService 是邪恶的。在不使用 JDK 1.8 功能的情况下,JDK 1.7 中有什么替代方案?
      • 这不是 ExecutorService 本身,只是 newCachedThreadPool 方法。这将从池中返回一个线程,如果有可用的,但如果没有,它总是创建一个新的。这意味着线程池可能会变得巨大并导致整个系统崩溃。 Executors.newFixedThreadPool 是一种更安全、更明智的选择。
      猜你喜欢
      • 2012-12-08
      • 1970-01-01
      • 2017-12-29
      • 2016-05-20
      • 1970-01-01
      • 1970-01-01
      • 2018-05-26
      • 2012-11-21
      • 2017-06-18
      相关资源
      最近更新 更多