Asdfasf

Thursday, September 20, 2012

Pooling and Reusing JAX-WS Client Ports


Load test'leri icin hazirladigim bir simulator uygulamasinda, load altinda kisa bir sure sonra uygulamadan server'a yapilan webservice isteklerinde gecikmeler farkettik. Simulator uygulamasi, apache tomcat'da calisan bir web uygulamasi ve webservis istekleri icin jax-ws ile urettigimiz client kod'u kullaniyor.
Cagri yapan kod asagidaki gibi, her istek oncesinde servis ve port yaratip istekte bulunuyor


 public void callServer(){  
      XXXService service = new XXXService();  
      XXXServicePortType port = service.getXXXServiceHttpSoap11Endpoint();  
      WsUtil.setEndpointAdress(port, TestManager.SERVER_URL);  
      Response response = null;  
      try {  
           response = port.callServer();  
      } catch (SOAPFaultException e) {  
      }  
 }  
 public static void setEndpointAdress(final Object port, final String newAddress) {  
     final BindingProvider bp = (BindingProvider) port;  
     bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, newAddress);  
 }  

Sorunun nedenini bulmak, gecikme ve beklemelerin neden kaynaklandigini anlamak icin simulator uygulamasinin thread dump'ini inceledim.

 jstack <process Id>  

thread dump'in soyledigi, thread'lerin yeni servis objesi yaratirken, WSDL definition'ini yuklerken bekledikleri


 "pool-1-thread-400" prio=3 tid=0x0000000002101800 nid=0x215 waiting for monitor entry [0xfffffd7ff2c35000]  
   java.lang.Thread.State: BLOCKED (on object monitor)  
     at org.apache.axis2.jaxws.ClientConfigurationFactory.getClientConfigurationContext(ClientConfigurationFactory.java:88)  
     - waiting to lock <0xfffffd7f8626c060> (a org.apache.axis2.jaxws.ClientConfigurationFactory)  
     at org.apache.axis2.jaxws.description.impl.DescriptionFactoryImpl.createServiceDescription(DescriptionFactoryImpl.java:92)  
     at org.apache.axis2.jaxws.description.impl.DescriptionFactoryImpl.createServiceDescription(DescriptionFactoryImpl.java:79)  
     at org.apache.axis2.jaxws.description.DescriptionFactory.createServiceDescription(DescriptionFactory.java:76)  
     at org.apache.axis2.jaxws.spi.ServiceDelegate.<init>(ServiceDelegate.java:212)  
     at org.apache.axis2.jaxws.spi.Provider.createServiceDelegate(Provider.java:59)  
     at javax.xml.ws.Service.<init>(Service.java:56)  
     at com.xxx.xxx.xxx.XXXService.<init>(XXXService.java:47)  


Kisaca, her webservice istegi oncesi yeni servis ve port objesi olusturup, WSDL definition'ini yukletmek ve yuklerken alt tarafta kuvvetle muhtemel syncronized bir block'ta zaman kaybetmek pekte mantikli degil.

Pooling bir cozum olacaktir.

Uygulama ayaga kalkarken, kullanacagimiz port'lari ihtiyacimiz kadar yaratip bir queue'da tutmak ve bu havuzdan kullanmak, kullandiktan sonra queue'ya baskalarinin kullanmasi icin koymak uygun olacaktir.


 static BlockingQueue<XXXServicePortType> portQueue = new LinkedBlockingQueue<XXXServicePortType>();  
 public static void initialize() {  
      try {  
           long start = System.currentTimeMillis();  
           int portCount = Integer.parseInt(TestManager.PORT_POOL_COUNT.trim());  
           for (int i = 0; i < portCount; i++) {  
                XXXService service = new XXXService();  
                XXXServicePortType port = service.getXXXServiceHttpSoap11Endpoint();  
                WsUtil.setEndpointAdress(port, TestManager.SERVER_URL);  
                try {  
                     portQueue.put(port);  
                } catch (InterruptedException e) {  
                     e.printStackTrace();  
                }  
           }  
           TestManager.debug("Producing ports took " + (System.currentTimeMillis() - start));  
      } catch (Exception e) {  
           TestManager.error("", e);  
      }  
 }  
 public void callServer(){  
      XXXServicePortType port = null;  
      Response response = null;  
      try {  
           long l = System.currentTimeMillis();  
           port = portQueue.take();        
           TestManager.debug("Taking port took:[" + (System.currentTimeMillis() - l) + " msec");                 
           response = port.callServer();  
      } catch (InterruptedException e) {  
           e.printStackTrace();  
      } finally{  
           if (port != null){  
                try {  
                     portQueue.put(port);  
                } catch (InterruptedException e) {  
                     e.printStackTrace();  
                }  
           }  
      }  
 }  


Pooling bize gozle gorulur, ciddi bir iyilesme kazandirdi. Ek olarak her bir port ve kullandigi fiziksel connection arasindaki iliskiden bahsetmek gerekirse:

netstat -n komutu ile load sirasinda server'a kurulan fiziksel baglanti'lari inceledigimde sunu gordum. Pool kullandigimizda, cagrilan url ayni oldugu durumda, port sayisinca connection yaratildigini ve operating system tarafindan canli tutuldugunu gordum. Havuzdaki client port objeleri, canli tutulan bu fiziksel http connection'larini her seferinde kullaniyorlar.

Cagrilan url'i anlik olarak degistirdigim durumda ise operating system yeni bir connection yaratiyor ve port objesi bu yeni connection uzerinden http call yapiyor. Bir sekilde fiziksel connection yonetimi operating system tarafindan yapiliyor ve kisaca port objesinin o anda cagiracagi ip:port icin hazirda bir http connection var ise o kullaniliyor, yok ise yaratiliyor.

SONUC OLARAK:

Webservice cagrisinda asil maliyetli olan service ve port objelerini her seferinde yaratip servis tanimini (WSDL dosyasini) yukletmek oldugunu gorduk, ve bunun yerine port objelerini ayaga kalkarken yaratip bir havuzda tutup tekrar tekrar kullanmak cozumunu uyguladik.

Fiziksel http connection yonetimi ise alt tarafta bir sekilde handle ediliyor ve dikkate deger bir maliyeti olmadigini soyleyebilirim.

No comments: