Friday, April 14, 2006

[java] Jetty Continuations

ใน Web container ปัจจุบัน
model ของ thread จะเป็นแบบ Thread per Connection
นั่นก็คือ 1 connection ที่วิ่งเข้ามา จะมี 1 thread ที่ถูก assign ให้รับผิดชอบ
(ผลพวงมาจาก socket api, ที่มีการ assign connection ให้กับ thread)

แนวคิดใหม่ๆ ก็คือการใช้ NIO เข้ามาช่วย
อย่างใน Jetty6 จะมี model ที่เรียกว่า Thread per Request
ความหมายก็คือ connection ที่เปิดทิ้งไว้, แต่ไม่มี request วิ่งเข้ามา
จะไม่มี thread รองรับมัน (thread จะถูกส่งกลับเข้า pool)
เมื่อไรก็ตามที่มี request เข้ามา จึงจะมีการ assign thread ให้

บางคนอาจจะสงสัยว่า 1 connection มีได้หลาย request ด้วยหรือ
Connection ใน HTTP1.1 จะมี default เป็น keep alive
และเมื่อบวกกับ App สมัยใหม่ที่เป็น ajax style
มีข้อมูลวิ่งไปมาถี่ๆ, connection ที่ browser เปิดไว้ก็แทบจะไม่ disconnect เลย
บางคนถึงกับล้อว่า นี่เราจะเปลี่ยน browser ให้กลายเป็น client-server แบบเดิมๆหรืออย่างไร
(client-server แบบที่เปิด connection ค้างไว้)

แล้ว Jetty Continuations คืออะไร
Continuations ใน Jetty ไม่ใช่ continuation แบบใน scheme, ruby (พวก callcc)
ความหมายจริงๆของมันก็คือ ในระหว่างการทำงานของ Servlet,
servlet สามารถที่จะสั่ง suspend thread ได้
โดยถ้ามีการสั่ง suspend เมื่อไร, jetty จะทำการ detach thread ออกจาก request,
และคืน thread กลับเข้าสู่ thread pool

แล้ว App ประเภทไหนที่เหมาะกับ Continations?
ที่ชัดเจนที่สุด ก็น่าจะเป็นพวกที่ต้องมีการ polling เป็นระยะๆ เช่น พวก chat
หรือพวกที่มี nature การทำงานเป็น asynchronous
เช่น app ที่มีการเรียกใช้ service ผ่านทาง JMS แล้วต้องรอ reply message

ลองมาทดสอบ NIO กับ continuation ดู
เริ่มด้วย servlet แบบง่ายๆ
protected void processRequest(HttpServletRequest request, 
HttpServletResponse response)
throws ServletException, IOException {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();

out.println("<html>");
out.println("<head>");
out.println("<title>Servlet HelloServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet HelloServlet at " + request.getContextPath () + "</h1>");
out.println("</body>");
out.println("</html>");

out.close();
}

ใน servlet ให้มี sleep 100 ms,
ส่วน version ที่เป็น continuation ก็แค่เปลี่ยนจาก sleep เป็น
    Continuation ctn = ContinuationSupport.getContinuation(request,this);
ctn.suspend(100);


ผมใช้ jmeter ทดสอบ โดยเรียก 120 request ใน 1 วินาที
เป็นเวลา 20 วินาที (รวมทั้งหมด 2400 request)
ตั้ง duration assert ไว้ที่ 200 ms

Jetty 6 NIO
50 % 109 105 106
90 % 147 125 125
> 200 ms 69 91 82

Jetty 6 BIO
50 % 103 103 104
90 % 111 111 114
> 200 ms 0 2 83

Jetty 6 NIO + continuations
50 % 108 106 106
90 % 120 125 122
> 200 ms 0 78 89

Tomcat 5.0.30
50 % 104 103 103
90 % 111 114 113
> 200 ms 0 0 0

Note: 50, 90 % คือ distribution ของ response time
เช่น 50% ใช้เวลาน้อยกว่าหรือเท่ากับค่าที่ระบุ


ดูแล้ว NIO จะช้ากว่า BIO (block IO) อยู่นะ
ส่วน continuation ก็ไม่ต่างกับ NIO
และ jetty ใน version นี้เริ่มสู้กับ tomcat 5.0.x ได้แล้ว

ที่นี้ลองเปลี่ยนช่วงเวลาที่ sleep กับ suspend เป็น 1000 ms บ้าง
Thread.sleep(100);
==========================
ctn.suspend(1000);


ใช้ JMeter เรียกที่ 120 request/ sec, 20 วินาที เหมือนเดิม
แต่เปลี่ยน assert เป็น 1100 ms
ผลที่ได้

Jetty 6 NIO
50 % 1021 1008 1009
90 % 1397 1438 1419
> 1100 ms 956 919 883

Jetty 6 BIO
50 % 1002 1003 1002
90 % 1439 1549 1487
> 1100 ms 788 786 707

Jetty 6 NIO + Continuation
50 % 1002 1002 1002
90 % 1015 1011 1013
> 1100 ms 126 93 93

Tomcat 5.0.30
50 % 1005 1002 1002
90 % 1349 1035 1257
> 1100 ms 562 398 400

จะเห็นว่ากรณีที่ thread เริ่มสะสมกันมากๆ
(เนื่องจากเราตั้งค่า sleep ไว้ที่ 1 sec. ทำให้ request ชุดที่ 2 เริ่มเข้ามากวน request ชุดที่ 1)
jetty continuations ให้ผลลัพท์ที่ดีขึ้นอย่างเห็นได้ชัด

ข้อที่ต้องระวังในการใช้ jetty continuation ก็คือ
หลังจากที่เราสั่ง suspend ไปแล้ว, เมื่อ thread resume กลับมาอีกครั้ง
HttpServletRequest ที่ได้ในคราวนี้ จะไม่ใช่ตัวเดิมแล้ว
ค่า parameter ต่างๆ จะหายหมด
ดังนั้นเราต้อง save ค่านี้เก็บไว้ใน session หรือใน database ก่อน
นอกจากนี้ ยัง suspend ยังห้ามใช้หลังจากมีการเขียน response ไปแล้ว

ประเด็นที่ผมยังไม่รู้คำตอบ ก็คือ
ในการใช้งานจริง เรามักจะวาง web container ไว้หลัง web server
คำถาม ก็คือพวก persistent connection ที่เกิดขึ้น มันจะเกิดกับ web server
แล้ว jetty continuation ยังมีประโยชน์อยู่อีกหรือเปล่า
case หนึ่งที่เห็นว่า น่าจะมีประโยชน์อยู่บ้าง ก็คือพวก asynchronouse nature
(พวกที่ใช้ jms ต่อไปยัง service)
เพราะถ้าใช้ jetty contination เข้ามาช่วย ก็น่าจะทำให้เรา
เพิ่มจำนวน connection ระหว่าง web server กับ web container ได้
ซึ่งมีผล ทำให้ through put เพิ่มขึ้น

Related link from Roti

2 comments:

nontster said...

เพิ่งมาดูเรื่องนี้ครับ น่าสนใจดี ที่เว็บของ Jetty บอกว่าContinuations จะโดนแทนที่ด้วย Servlet-3.0

Khajochi said...

เยี่ยมไปเลยครับ เขียนไว้หลายปีแล้วแต่ก็ยังเข้ามาอ่าน กำลังทำเรื่อง jetty อยู่เลยครับ @khajochi