2016-05-17 33 views
2

Ich versuche, mit einer Java-Implementierung eines einfachen HTTP-Client, der einen Socket offen hält und verwendet es wieder andere (oder gleiche) URLs auf dem gleichen Host abfragen .Java Socket Keep am Leben ist langsam, Wiedereröffnung eines Sockets ist schneller

Ich habe eine einfache Implementierung, die java.net.Socket verwendet, aber irgendwie ist die Leistung, wenn ich den Sockel offen halte, schlimmer als wenn ich einen neuen erzeuge.

Ergebnisse erste, voll ausführbaren Code unten:

Mit Keep-Alive: langsamer beginnend bei Iteration Nr 2

> java -server -Xms100M -Xmx100M -cp . KeepAlive 10 true 
--- Warm up --- 
18 
61 
60 
60 
78 
62 
59 
60 
59 
60 
Total exec time: 626 
--- Run --- 
26 
59 
60 
61 
60 
59 
60 
60 
62 
58 
Total exec time: 576 

die Buchse Recreating jedes Mal bessere Ergebnisse gibt:

> java -server -Xms100M -Xmx100M -cp . KeepAlive 10 false 
--- Warm up --- 
188 
34 
39 
33 
33 
33 
33 
33 
34 
33 
Total exec time: 494 
--- Run --- 
33 
35 
33 
34 
44 
34 
33 
34 
32 
34 
Total exec time: 346 

Keep-Alive. Java (Standalone, keine Abhängigkeiten)

import java.io.BufferedReader; 
import java.io.DataOutputStream; 
import java.io.InputStreamReader; 
import java.net.InetSocketAddress; 
import java.net.Socket; 

public class KeepAlive { 

    private static final String NL = "\r\n"; 
    private static final int READ_SIZE = 1000; 
    private Socket socket; 
    private DataOutputStream writer; 
    private BufferedReader reader; 

    public static void main(String[] args) throws Exception { 
     if (args.length == 2) { 
      KeepAlive ka = new KeepAlive(); 
      System.out.println("--- Warm up ---"); 
      ka.query(Integer.parseInt(args[0]), args[1].equals("true")); 
      System.out.println("--- Run ---"); 
      ka.query(Integer.parseInt(args[0]), args[1].equals("true")); 
     } else { 
      System.out.println("Usage: keepAlive <n queries> <reuse socket>"); 
     } 
    } 

    private void query(int n, boolean reuseConnection) throws Exception { 
     long t0 = System.currentTimeMillis(); 
     if (reuseConnection) { 
      open(); 
      for (int i = 0; i < n; i++) { 
       long tq0 = System.currentTimeMillis(); 
       query(); 
       System.out.println(System.currentTimeMillis() - tq0); 
      } 
      close(); 
     } else { 
      for (int i = 0; i < n; i++) { 
       long tq0 = System.currentTimeMillis(); 
       open(); 
       query(); 
       close(); 
       System.out.println(System.currentTimeMillis() - tq0); 
      } 
     } 
     System.out.println("Total exec time: " + (System.currentTimeMillis() - t0)); 
    } 

    private void open() throws Exception { 
     socket = new Socket(); 
     socket.setKeepAlive(false); 
     socket.connect(new InetSocketAddress("example.org", 80)); 
     writer = new DataOutputStream(socket.getOutputStream()); 
     reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 
    } 

    private void query() throws Exception { 
     StringBuilder req = new StringBuilder(); 
     req.append("GET/HTTP/1.1").append(NL); 
     req.append("Host: example.org").append(NL); 
     req.append("Connection: Keep-Alive").append(NL); 
     req.append(NL); 
     String reqStr = req.toString(); 

     long t0 = System.currentTimeMillis(); 
     writer.writeBytes(reqStr); 
     writer.flush(); 

     String line; 
     int contentLength = 0; 
     while ((line = reader.readLine()) != null) { 
      if (line.startsWith("Content-Length: ")) { 
       contentLength = Integer.parseInt(line.substring(16)); 
      } 
      if (line.equals("")) { 
       char[] buf = new char[contentLength]; 
       int offset = 0; 
       while (offset < contentLength) { 
        int len = contentLength - offset; 
        if (len > READ_SIZE) { 
        len = READ_SIZE; 
        } 
        int ret = reader.read(buf, offset, len); 
        if (ret == -1) { 
        System.out.println("End of stream. Exiting"); 
        System.exit(1); 
        } 
        offset += ret; 
       } 

       break; 
      } 
     } 
    } 

    private void close() throws Exception { 
     writer.close(); 
     reader.close(); 
     socket.close(); 
    } 
} 
Jetzt

, ich bin ziemlich sicher, dass entweder:

  1. der Web-Server der neuen Anforderungen im Umgang mit schnellen (HTTP Keep Alive and TCP keep alive)

  2. etwas saugt ist falsch mit der Art, wie ich die gepufferten Leser verwenden, da das ist, wo die ganze Zeit verloren, aber bei den anderen Methoden zur Verfügung sucht (und ich versuche, ein paar), kann ich nicht finden, was ich tun müssen, um dieses Problem beheben ...

Jede Idee, wie ich kann mach das w Ork schneller? Vielleicht eine Config auf das ichduersiees Server ändern? ...


Lösung

Wie unten durch apangin erläutert, wird die langsame perf von Nagle-Algorithmus verursacht, die standardmäßig aktiviert ist. Mit setTcpNoDelay (true), habe ich die aktualisierten folgenden perfs erhalten:

Ohne Keep-Alive:

java -server -Xms100M -Xmx100M -cp . KeepAlive 10 false 
--- Warm up --- 
49 
22 
25 
23 
23 
22 
23 
23 
28 
28 
Total exec time: 267 
--- Run --- 
31 
23 
23 
24 
25 
22 
23 
25 
33 
23 
Total exec time: 252 

Mit Keep-Alive: Also hier

java -server -Xms100M -Xmx100M -cp . KeepAlive 10 true 
--- Warm up --- 
13 
12 
12 
14 
11 
12 
13 
12 
11 
12 
Total exec time: 168 
--- Run --- 
14 
12 
11 
12 
11 
12 
13 
11 
21 
28 
Total exec time: 158 

wir die Frieds sehen -alive Version, die bei jeder Iteration und auch beim Vergleich der gesamten Ausführungszeiten weitaus besser abschneidet als die nicht-keep-alive-Version. :)

+0

Sie testen nicht wirklich wie hier. In einem hämmerst du den Server so hart wie du kannst. Auf der anderen Seite pausieren Sie zwischen den einzelnen Abfragen. Haben Sie versucht, die Gesamtzeit auf dem Client für jeden Lauf zu testen? – BevynQ

+0

Ja, das ist der Punkt des Tests: zu sehen, welches der beiden unterschiedlichen Verhaltensweisen am besten abschneidet. Die Frage ist: "Wie schnell kann ich eine Anfrage stellen und die Antwort mit oder ohne Keep-Alive bekommen". Ich habe die Gesamtzeit der Ausführung für Sie hinzugefügt. – fabien

Antwort

8

Das ist der Effekt von Nagle's algorithm. Es verzögert das Senden von TCP-Paketen in Erwartung weiterer ausgehenden Daten.

Nagles Algorithmus interagiert schlecht mit TCP delayed acknowledgment in schreiben-schreiben-lesen Szenarien. Dies ist genau Ihr Fall, weil writer.writeBytes(reqStr) Byte für Byte eine Zeichenfolge sendet.

Jetzt haben Sie zwei Möglichkeiten, das Verhalten zu beheben:

  1. Verwendung socket.setTcpNoDelay(true) Nagle-Algorithmus zu deaktivieren;
  2. die komplette Anforderung in einem Vorgang senden: writer.write(reqStr.getBytes());

In beiden Fällen werden die wiederverwendet Verbindung expectedly schneller arbeiten.

+0

Warum sollte das nicht für beide Verbindungstypen gelten? – EJP

+0

@EJP Offensichtlich zwingt 'socket.close()' alle ausstehenden Daten sofort zu senden. Dies passiert nicht, wenn eine Steckdose offen gehalten wird. – apangin

+0

Das ist weder offensichtlich noch richtig, und die Anfrage muss noch gesendet werden, ohne den Sockel zu schließen. Alles was 'close()' tut, was die Daten betrifft, ist ein FIN, der nach den anstehenden Daten gesendet wird. Der [Nagle-Algorithmus] (https://tools.ietf.org/html/rfc896) wird nicht deaktiviert. – EJP

-2
reader.read(buf); 

Ihr Test ist ungültig. Sie lesen nicht unbedingt die gesamte Antwort. Sie müssen dies in eine Schleife ändern, die die zurückgegebenen Daten zählt. Und wenn Sie nicht die gesamte Antwort lesen, werden Sie im Keep-Alive-Fall nicht mehr synchron sein.

+0

Sie liegen nicht falsch. Die hier getestete Nutzlast ist zwar so klein (ungefähr ~ 1Kb) ist aber egal. Ich habe die Schleife zum obigen Code hinzugefügt und die Ergebnisse aktualisiert. Gleich wie erwartet. – fabien