2 Aralık 2020 Çarşamba

Servlet 3.0 Asynchronous Request Processing

Giriş
1. İlk Http sunucularında thread per connection yöntemi kullanılıyordu. Http 1.0, istek işlendikten sonra TCP bağlantısını kapattığı için bu yöntem işe yarıyordu.

2. Daha sonra Http 1.1 geldi ve TCP bağlantısı kapatılmamaya başlandı. Bu yüzden thread per connection yöntemi yerine thread per request yöntemine geçildi. Yani bildiğimiz thread pool yöntemi. 

3. Ancak zaman içinde bu yönteme de bazı değişiklikler gerekmeye başladı ve ortaya Reactive Programming ve bu yöntemle geliştirilen web sunucuları çıkmaya başladı. Reactive Programming daha aza sayıda thread kullanır ve thread'lerin bloke olmamasını ister. Daha az thread ile de context switch maliyeti azalır. Bu yüzden bir sürü thread çalıştırmaya göre daha verimli. 

İşin özeti servet sunucuları 2inci maddeden 3üncü maddeye tam olarak geçiş yapamadılar, ancak yine de 3üncü maddedekine benzer bazı iyileştirmeler yapıldı. Bu yazı yapılan iyileştirmelerden birisini anlatıyor.

Bu yazıdakine ilave olarak yapılan bazı iyileştirmeler şöyle
1 : Servlet 3.0 ile bir de Server Push yeteneği geliyor
2 : Servlet 3.1 ile bir de NIO yeteneği geliyor. Açıklaması şöyle
Servlet 3.1 NIO:
As described above, Servlet 3.0 allowed asynchronous request processing but only traditional I/O (as opposed to NIO) was permitted. Why is traditional I/O a problem?

In traditional I/O, there are two scenarios to consider:

If the data coming into the server (I/O) is blocking or streamed slower than the server can read, then the server thread that is trying to read this data has to wait for that data.
On the other hand, if the response data from the server written to ServletOutputStream is slow, the client thread has to wait. In both cases, the server thread doing the traditional I/O (for requests/responses) blocks.
In other words, with Servlet 3.0, only the request processing part became async, but not the I/O for serving the requests and responses. If enough threads block, this results in thread starvation and affects performance.

With Servlet 3.1 NIO, this problem is solved by ReadListener and WriteListener interfaces. These are registered in ServletInputStream and ServletOutputStream. The listeners have callback methods that are invoked when the content is available to be read or can be written without the servlet container blocking on the I/O threads. So these I/O threads are freed up and can now serve other request increasing performance.
Burada amaç 
- Okuma aşamasında servlet thread'i eğer veri yavaş geliyorsa bloke etmemek
- Yazma aşamasında eğer karşı taraf veya bağlantı yavaşsa servlet thread'i bloke etmemek

Asynchronous Request Processing Yeteneği Neden Lazım?
Sebebini anlamak için "Fire and Forget İşler" ve "Asynchronous Request Processing" arasındaki farka bakmak lazım

1. Fire and Forget İşler Nedir?
Açıklaması şöyle. Fire and Forget tarzı işler tamamen asenkron çalışır. Hemen bir cevap dönmesi beklenmez. 
In some cases you can return to the client immediately while a background job completes processing. For example sending an email, kicking off a database job, and others represent fire-and-forget scenarios that can be handled with Spring's @Async support or by posting an event to a Spring Integration channel and then returning a confirmation id the client can use to query for the results.
2. Asynchronous Request Processing Nedir?
Açıklaması şöyle. Burada amaç Servlet Engine'e ait thread'leri tamamen tüketmemek.
In other cases, where the result is required, we need to decouple processing from the Servlet container thread or else we'll exhaust its thread pool. Servlet 3 provides just such support where a Servlet (or a Spring MVC controller) can indicate the response should be left open after the Servlet container thread is exited.

To achieve this, a Servlet 3 web application can call request.startAsync() and use the returned AsyncContext to continue to write to the response from some other separate thread. At the same time from a client's perspective the request still looks like any other HTTP request-response interaction. It just takes longer to complete. 
Servlet Engine'de Thread'ler Tüketilirse Ne Olur?
Açıklaması şöyle. Servlet sunucusu cevap vermediği için istemciler hata almaya başlayacaktır.
This can lead to Thread Starvation – since our servlet thread is blocked until all the processing is done. If server gets a lot of requests to process, it will hit the maximum servlet thread limit and further requests will get Connection Refused errors.
Connection Refused hatasına sebep olabilecek bazı şeyle şöyle
There could be many reasons, but the most common are:

1. The port is not open on the destination machine.

2. The port is open on the destination machine, but its backlog of pending connections is full.

3. A firewall between the client and server is blocking access (also check local firewalls)
Bizi burada 2inci madde ilgilendiriliyor. Yani sunucunun tüm kaynaklarının tükenmesi, kapasitesinin dolması durumu.

Spring Servlet'leri
Spring servletleri otomatik olarak asenkron çalıştıracak şekilde yaratıyorservletRegistrationBean.isAsyncSupported() metodu ile kontrol edilebilir.

Eğer kendimiz servlet yazıyor olsaydık @WebServlet anotasyonunun asyncSupported alanını true yapardık

Asenkron Çalışmak İçin
1. Servlet API + Executor kullanılabilir
2. Callable dönülebilir.
3. DeferredResult dönülebilir.
4. ResponseBodyEmitter dönülebilir
1. StreamingResponseBody dönülebilir

Servlet API + Executor
Şöyle yaparız
ExecutorService executorService = Executors.newFixedThreadPool(10);

@RequestMapping("/hello")
public void hello(HttpServletRequest request) {
  AsyncContext asyncContext = request.startAsync();
  //Time out
  asyncContext.setTimeout(10000);
  executorService.submit(() -> {
    try {
      //Sleep for 5 seconds, simulate service operation
      TimeUnit.SECONDS.sleep(5);
      //Output response results
      asyncContext.getResponse().getWriter().println("hello world");
      log.info ("end of asynchronous thread processing");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      asyncContext.complete();
    }
  });
  log.info ("servlet thread processing ended");
}
Callable
Örnek
Şöyle yaparız
@RequestMapping("/hello_v3")
public WebAsyncTask<String> hello_v3() {
  
  Callable<String> callable=new Callable<String>() {
    @Override
    public String call() throws Exception {
      ...
      return "...";
    }
  };
}
Örnek - timeout
Şöyle yaparız
@RequestMapping("/hello_v3")
public WebAsyncTask<String> hello_v3() {
  Callable<String> callable=new Callable<String>() {
    @Override
    public String call() throws Exception {
      ...
      return "hello_v3";
    }
  };
  //Unit: ms
  WebAsyncTask<String> webAsyncTask=new WebAsyncTask<>(10000,callable);
  return webAsyncTask;
}
Callable İçin Asenkron Ayarlar

Hiç yorum yok:

Yorum Gönder