Job<List<FacebookFriend>> j = new Job<List<FacebookFriend>>(){private User user;public void setUser(User u){ user = u;}@Overridepublic List<FacebookFriend> doJobWithResult() throws Exception {return FacebookFriendsProvider.friendsOf(user);}}.setUser(myUser);List<FacebookFriend> result = await(j.now());
Uygulamanizin bu action'una ayni anda 2 request attiginizda eger ikisinin toplam bitmesi asagi yukari 5 sn surecegini dusunuyorsaniz agir yanilgilardasiniz benim gibi. 10 saniye suruyor, cunku once biri sonra oteki handle ediliyor. (dev modunda boyle en azindan, prod modunda 2 yerine 4 falan yazabilirsiniz, tahmin edersiniz o zaman da 4. sirada gelen request 20 sn sonra cevap alir.)
Selam,
Konuyla ilgili daha önce parça parça yazmıştım aslında. Işin özünde bir trade-off var ve ilk boş vakitte uzun bir blog yazmak gerekiyor gibi. Neden non-blocking IO, neden Tomcat "eski teknoloji", neden Java ile ancak bu kadar, vs...
Ahmet, kodu biraz daha toparlamak için anonymous inner class yerine ayrı Job sınıfları yazabilirsin sanıyorum. Ama bunun dışında yapacak pek bir şey yok gibi ne yazık ki.
Yine uzun bir maille karsinizdayim. Oncelikle kacimiz bu sayfanin ilk 5 paragrafini hatirliyor merak ediyorum. :) Megersem play'de aninda return etmeyen islemleri controller'in icinde yapmaya kalkarsaniz uygulamaniz birakin scale etmeyi, 2-3 kisinin bile ayni anda kullanamadigi bir sistem haline donusuyormus.
Nasil mi oluyor, cok basit bir ornek:public static void controllerIcindeBirMetod(){print 'request geldi'sleep 5 sn.print 'request bitti'}Uygulamanizin bu action'una ayni anda 2 request attiginizda eger ikisinin toplam bitmesi asagi yukari 5 sn surecegini dusunuyorsaniz agir yanilgilardasiniz benim gibi. 10 saniye suruyor, cunku once biri sonra oteki handle ediliyor. (dev modunda boyle en azindan, prod modunda 2 yerine 4 falan yazabilirsiniz, tahmin edersiniz o zaman da 4. sirada gelen request 20 sn sonra cevap alir.)
Ben bu durumun farkinda degildim acikcasi load test yapana kadar. Verdigim sayfada dokumante edilmis demek de yalan olur zira oteki dillerden gelen gelistiriciler icin bu durum vurgulanmamis. Uzucu.
Gelistirdigim sosyal uygulamada cok sayida external servisle synchronous (o an cevap donmem gereken) request icinden iletisim kuruyorum. Cok basit bir ornek, Facebook'uyla baglanan kullanicinin arkadas listesini almam gerekiyor, Facebook API'den bunu synchronous cekiyorum.
Iste bu noktada eger o sayfada belirtilen yeni job yaratip await() metodunu kullanmazsam, 2 kisi ayni anda Facebook arkadaslarini listeleme requestini cagirdiginda kafadan ziyaretcilerin geri kalani siteyi kullanamiyor.Bu meseleyi ortadan kaldirmak icin bu tarz external requestler veya agir CPU islemleri iceren (diger tabirle returns-immediately olmayan) islerin hepsine yeni job yazmak zorunda kaliyorum. Ayni facebook friend listeleme orneginde, bu isi onceden
List<FacebookFriend> friends = FacebookFriendsProvider.friendsOf(user);seklinde hallederken simdi:Job<List<FacebookFriend>> j = new Job<List<FacebookFriend>>(){private User user;public void setUser(User u){ user = u;}@Overridepublic List<FacebookFriend> doJobWithResult() throws Exception {return FacebookFriendsProvider.friendsOf(user);}}.setUser(myUser);List<FacebookFriend> result = await(j.now());gibi "igrenc" bir yontemle yapmak zorunda kaliyorum. Kaldi ki bu sekilde 15 civari external synchronous requestimiz var, hepsini cevirmek zorunda kaldik.
Simdi merak ediyorsunuz, nesi sacma bunun diye, bence su sebeplerden:1) Job execution'lar safe degil, calisacaklarinin garantisi yok.
2) Job'lar arkaplanda calistiklari icin firlattiklari exceptionlar log olarak gorunuyor sadece, size de await'in sonucu null donuyor, icerde ne olup bittigini ogrenemiyorsunuz.
3) Hele bizim gibi validation icin exception-driven bir stratejiniz varsa, ornegin bu metodlariniz ResourceNotFoundException, InsufficientPrivilegesException gibi durumu anlatan exceptionlar atiyorsa cagirdiginiz servisin (yani bu ornekte facebook'tan friend'leri cekmenin) ne tip bir hata dondurdugunu asla bilemeyeceksiniz.
4) Son igrenclik sebebi bos yere tek satirlik kodu 15 satir yapivermesi.Henuz (1) nolu maddeden zarar gormedim ama kalan kisim benim icin cok onemliydi. Peki ben ne mi yaptim?:
- Job'un return type'ini Object yaptim.
- Bu sekilde istersem List<FacebookFriend> istersem Exception dondurebiliyorum.
- Yani servisim exception firlatirsa ben de onu yukari throw etmek yerine job result gibi donduruyorum.
- Controller action'umda instanceof yardimiyla sonuc exception'sa ilgili islemi yapiyorum (orn, kullaniciya sifreniz yanlis falan diyorum)
- Degilse, yani sonuc List<FacebookFriend> ise sonucu kullaniyorum.
- Sonuc olarak daha igrenc, yepisyeni bir dirty hack ortaya cikarmis oluyorum.
Simdi suc kimde ve niye? Hepimizin external servislerle, Solr-Lucene vb modullerle, agir DB islemleriyle, upload edilen dosyayi save eden, clouda upload eden librarylerle etkilesen uygulamalar yazdigi ve en onemlisi birlik ve beraberlige her zamankinden daha cok ihtiyac duydugumuz su zamanlarda sizi bu konuya duyarli olmaya davet ediyorum.
Saygilar.--Ahmet Alp Balkan
Hocam über adamsin gercekten cok tesekkurler. Aydinlandim. Pair gibi bisey kullanmak aklima nedense gelmedigi gibi play'in icinde Either oldugunu da bilmiyordum. (hatta E2, E3, E4, E5 bile varmis, bilginize).
Problem (tabi bu bir problem olarak goruluyorsa) mimari acidan API client'dan (FacebookFriendsProvider) kaynakli. Eger bunu disardan bulduysaniz async bir facebook client bulmanizda fayda var. Kendiniz yazdiysaniz metotlari Future dondurecek sekilde degistirmelisiniz.Durum aslinda play'in fixedthreadpool kullanmasiyla ayni mantik, yukaridaki ornekte her bir request icin bir job acip arkaplana atarsaniz 1000 kisi ayni anda arkadas listesini istedigi durumda play 1000 tane job ile facebook'a 1000 tane request gonderecektir. Bu sunucuda bir problem yaratmasa bile facebook tarafindan bir atak gibi algilanabilir sunucuyu engellemesine sebep olabilir. (goo.gl icin yaptim ordan biliyorum)Diger taraftan kendi threadpool'unu kullanan daha akilli bir client bu gibi istekleri kontrol altinda tutabilir. Ayrica Future dogrudan await'e gecirilebiliyordu diye hatirliyorum.
Burada kullanilabilecek bir diger yontem Either gibi bir yapi kullanmak. yani metot Either<Throwable, List<FacebookFriend>> gibi bir sey donecek (hatta Future<Either<Throwable, List<FacebookFriend>>>). Bu seyin icinde atiyorum isLeft(), isRight(), getLeft(), getRight() gibi metotlar olacak. Hatta simdi baktim play in icinde varmis play.libs.F.Either seklinde bir ornek. Boylece typesafe olunacak.
On Sun Mar 25 12:48:10 2012, Volkan YAZICI wrote:
> OSGi ve JGroups ile boğuşmaktan ancak nefes alabiliyorum, yorumlarım
> aşağıda.
>
> On Wednesday, March 21, 2012 1:08:36 PM UTC+2, Erdem Agaoglu wrote:
>
> Problem (tabi bu bir problem olarak goruluyorsa) mimari acidan API
> client'dan (FacebookFriendsProvider) kaynakli. Eger bunu
> disardan bulduysaniz async bir facebook client bulmanizda fayda
> var. Kendiniz yazdiysaniz metotlari Future dondurecek sekilde
> degistirmelisiniz.
>
> Durum aslinda play'in fixedthreadpool kullanmasiyla ayni mantik,
> yukaridaki ornekte her bir request icin bir job acip arkaplana
> atarsaniz 1000 kisi ayni anda arkadas listesini istedigi durumda
> play 1000 tane job ile facebook'a 1000 tane request gonderecektir.
> Bu sunucuda bir problem yaratmasa bile facebook tarafindan bir
> atak gibi algilanabilir sunucuyu engellemesine sebep olabilir.
> (goo.gl <http://goo.gl> icin yaptim ordan biliyorum)
>
> Diger taraftan kendi threadpool'unu kullanan daha akilli bir
> client bu gibi istekleri kontrol altinda tutabilir. Ayrica Future
> dogrudan await'e gecirilebiliyordu diye hatirliyorum.
>
>
> Aslında su yüzüne çıkmış bu (so-called) defficiency, insanları bir çok
> noktada single process/thread ve async server mimarilerine itmeye
> başladı. Bunun en net örneğini bence NodeJS tarafından yaşanan hype'ta
> görüyoruz. Aynı şeyi ben de bazen düşünmüyor değilim: JVM aslında
> single process geliştirilse de thread'ler eskisi gibi
> green/lightweight olsa; ve multicore olayını da ben kendi programımı
> birden fazla spawn ettiğim JVM'ler üzerinde halletsem. (Bkz. Reddit)
> Fakat bunun için V8'in Javascript'e yaptığına benzer olarak, JVM'in
> Java'ya sunduğu tüm fonksiyon ailesinin async olması lazım ki tüm bu
> emeğin bir anlamı olsun.
>
> Durumu biraz da aydınlığa kavuşturmak adına, Ahmet, aynı kodu NodeJS
> üzerinde yazmış olsaydın şöyle olacaktı:
>
> function index(httpReq, user) {
> FB.friendsOf(user, function() {
> // Rest of your index() logic goes here.
> httpReq.write(...);
> });
> }
>
> Yani nerede async metodlar, orada callback çorbası. Bu yüzden Play'in
> await()'ine biraz da şükretmek lazım.
>
> Bu arada şimdi düşündüm de NodeJS'e başka ne örnek verebiliriz diye,
> aklıma Gambit Scheme <http://www.iro.umontreal.ca/%7Egambit/> geldi.
> Hatta o cepheden Future tipinde yaklaşımlar ile benzer bir başarı
> hikayesi: Termite Scheme <http://code.google.com/p/termite/>.