Reactive Programlama Nedir? Server-side Reactive Programlama

Gökhan Ayrancıoğlu
Delivery Hero Tech Hub
8 min readNov 17, 2021

--

Reactive programlama asenkron veri akışlarına (data streams) dayalı ve gerçek zamanlı olay (event) bazlı bir yazılım paradigmasıdır. Yani işlemler senkron bir biçimde birbirlerinin tamamlanmasını beklemezler. Reactive programlama, çalışma zamanında asenkron olarak diğer işlemlerin yürütülmesi olaylarını izleyerek gerçekleştirir.

Tanımı yaptığımıza göre daha başından bir anlatım ile süreci ve sonrasında reactive programlamayı anlamaya yönelik kavramlar silsilesine devam edebiliriz.

Günümüz modern uygulamaların birçoğunun çok sayıda concurrent/eşzamanlı isteği karşılayabilecek yapıda olması gerekiyor. Dolayısıyla geleneksel yöntemlerin yetersiz kaldığı işlemler karşımıza çıkıyor. Önceden beridir yazılım mühendisliğinin ilgi alanlarından olan donanımı en verimli biçimde kullanarak yazılım geliştirme prensibine göre hareket etmek gerekiyor.

Günümüzde microservisler, cloud-native uygulamalar ve distrubuted sistemler oldukça yaygın, resourceları daha iyi kullanmak ve low latency (düşük bekleme süreleri) ile birlikte responsive (hızlı tepki verebilmeyi) olmayı istiyor.

Terminoloji

Asenkron: Bir işlem yapılırken o işlemin sonucu beklenmeden başka bir işlem yapabilmeye yani eşzamansız işlemlere denir.

Data Stream: Zaman içinde sırayla gerçekleşen bir event/olay dizisidir. Streamler bize değerler, hatalar ve tamamlandı gibi sinyaller verir.

Reactive programlama akışları(streamleri) izleyerek/dinleyerek olaylara karşı tepki vererek ilerler, bu durum Observer Design Pattern ile mümkündür.

Backpressure Nedir?

Bizim beklediğimizden/halledebileceğimizden daha fazla yük gelecekse bunu işleyemeyebiliriz. Örneğin: Db’den handle edebileceğimizden fazla sonuç dönüyorsa biz bunu db’ye bildirebiliyoruz “Seni yakalayana kadar lütfen yavaşla”. Bu stable bir sistem kurmak için en önemli özelliklerden biridir. Reactive programlamanın en avantajlı olduğu noktalardan da biridir.

Imperative — Tradational/Geleneksel Programlama

Reactive programlamayı anlatmadan önce geleneksel yöntemi göz önüne almamız gerekiyor. İstekler bir thread-pool içinden her bir isteğe bir thread atanarak yürütülüyor. Ancak thread pool kapasitesi kadar istek işlenebiliyor. Bu yöntemde her thread blocking anlamına geliyor ve bu da bekleme, daha çok kaynak kullanımı gibi durumlara yol açabiliyor. Circuit breaker gibi yapılarla desteklenebilse de yeterli olmadığı noktalar mevcut. Geleneksel imperative programlanan uygulamaların genel özellikleri:

  • Senkron ve Blocking → İstekler sekron ve blocking olarak işleniyor. İstek yapıldığında bir thread herhangi bir I/O işlemi bloklandığında onu bekler. Thread başka işler için bloke durumda olur ve işlem tamamlanınca cevap dönebilir.
  • İstek başına bir thread → İstek başına bir thread ayrılması aynı zamanda gelen istek sayısını sınırlar. Thread pool kadar istek alınabilir hale gelir. Aynı zamanda çok istek gelmesi performansı da etkileyebilir.
  • Sistem kaynaklarının daha iyi kullanılmaması → Senkron ve blocking modelde belirli bir istek sayısını karşılayabilmek yüksek trafikte yeni instance’lara ihtiyaç duyar. Bu da kaynak kullanımı açısından oldukça maliyetli bir operasyon gerektirir.
  • Backpressure mekanizması desteği yok → Ani bir istek artışı olursa, sunucu veya istemci kesintileri olabilir. Bundan sonra, uygulamaya kullanıcılar erişemez. Backpressure’a detaylıca değineceğiz, kısaca ani yük artışlarına karşı sistemin kesintiye uğramamasını sağlayan mekanizma diyebiliriz.

Her dilde çeşitli çözüm yöntemleri olsa da belli noktalarda kilitleniyor, zorlanabiliyor ya da çok karmaşık çözümler ortaya çıkabiliyor.

Her bir thread belirli bir memory kullanıyor. Trafik altındaki uygulamalar için iyi bir performans için çok yüksek bellek boyutlarına ihtiyaç duyuluyor. Kubernetes gibi yapılarla Horizontal scaling yapılabiliyor. Bu yöntem şuan için iyi çözümlerden biri ve gelecekte de kullanılmaya devam edecek ancak her trafik artışında yeni instancelar oluşturmak maaliyet ve karmaşıklık olarak karşımıza başka sorunlar çıkarabiliyor.

Maliyetleri ve scaling problemlerini de aklımızda bulundurursak, her instance bir maliyet ve biz mühendisler az şeyle çok iş yapabilme eğilimindeyiz. Bu tarz sorunları çözebilmek ve ortaya daha iyi iş çıkartabilmek için daha az bellek kullanımı, dolayısıyla daha instance ile daha efektif bir scalability sağlayabiliriz.

Reactive programlama da işlemleri asenkron ve non-blocking olarak uygulayarak daha az thread pool ile daha çok isteğin işlem görebilmesi amaçlanıyor.

Geleneksel/Imperative Programlama bitiyor mu?

Her yeni teknolojik gelişme eskisini öldürüyor gözüyle bakmamak gerek. Yani cevap hayır. İhtiyaçlar göz önüne alındığında geleneksel yöntemin yeterli olduğu bir çok nokta mevcut. Çoğu küçük ve orta ölçekli uygulama hala oldukça verimli bir şekilde bu yöntemle ideal çalışabilmektedir.

Reactive Programlama’nın Derinliklerine

  • Yeni olmayan ama yeni yeni popülerleşen bir programlama paradigmasıdır.
  • Asenkron ve non-blocking operasyonları geliştirmemiz için biçilmiş kaftan.
  • Veri akışını event/message driven olarak streamler üzerinden oluşturur
  • Reactive programming != Functional Programlama.
    Functional programlama ile birlikte harika işler çıkartabilirsiniz.
  • Data streamlerde backpressure bulunur.
  • Asenkron ve non-blocking yapısı sayesinde öngörülebilir cevap süreleri sunar.
  • Sistem kaynaklarının daha iyi kullanımını sunar. Threadler non-blocking ve asenkron olduğu için bloklanıp kalmayacaktır. Uygulama daha az thread ile daha çok kullanıcıya efektif bir şekilde hizmet verebilecektir.

Hem müzik dinliyorum hem burada blog yazıyorum ve blocklanmadan ikisini eşzamanlı olarak yürütüyorum :))

Reactive Programlama Nasıl Çalışır?

Reactive programlamada tüm mesele streamler ile veriyi olay bazlı olarak yönetmekten geçer. Eventlar (olaylar), mesajlar, çağrılar ve hatta hatalar bir data stream üzerinden iletilir. Reactive programlama ile bu akışlar sürekli olarak gözlenir/izlenir/observe ve bir değer değişiminde direkt olarak tepki verir ve sıradaki işlemi gerçekleştirir.

Bir uygulama programlarken herhangi bir şeyden/her şeyden veri akışları oluşturmalısınız: Kullanıcı işlemleri, HTTP istekleri, alınan mesajlar, verilecek mesajlar, bildirimler, bir değişkendeki değişiklikler, cacheleme olayları, database işlemleri; değişebilecek ve oluşabilecek her şey için diyebiliriz.

Data sourcetan (Veri kaynağı) alınan her sonuç için bir tane event yada mesaj oluşturulur. Data Source: External Service (Herhangi bir başka servis), Database (Veritabanı) yada File (Dosya) olabilir. Eğer data source sonucunda tamamlanmış ya da hata alınmışsa bir tane event ya da mesaj oluşmuştur. Yani her iki durumda da bir eventımız mevcut.

Database’e bir sorgu yapıldığında, servis hemen cevap döner ve veri hazır olunca stream tarafından pushlanır. Bütün veri elementleri (itemler) için bir event oluşturulur. onComplete ile birlikte ise bu data stream tamamlanır.

Hata olunca ne oluyor ?

Akış ile ilgili oluşan her şey bir event ya da mesaja karşılık geliyor. Dolayısıyla oluşan hata da event olarak oluşuyor.

itemlar alınırken hata ile karşılaştık ve bu hata bir event olarak onError’a düştü. Exception’ı nasıl handle edeceğimizi de onError kısmında çözümleyebiliyoruz.

Database’e sorgu attık ve hiçbir sonuç yok. Bu durumda yine de onComplete eventı oluşur.

Kayıt için ise kayıt isteğini attık, çağrımız hızlıca cevap döndü ve başarılı tamamlandıysa onComplete event’ı ile bunu anlayabiliriz.

  • onNext ile stream ederken bir sonraki item’a geçebiliriz.
  • onComplete başarıyla tamamlandığını belirtirken
  • onError hata durumu oldugunu gösterir.

Backpressure Desteği

Reactive programlamada backpressure’ın öneminden bahsetmiş ve ne olduğuna değinmiştik, biraz detay verip nasıl uygulayabileceğimize bakalım. Örneğin, başka bir servisten veri alan bir client uygulamamız var. Servis, eventları 100x hızında gönderebilirken, client uygulaması olayları 20x hızında işleyebiliyor. Bu durumda client uygulamamız aşırı yükle performans kaybına uğrayabilir, hatta kesintiler gerçekleşebilir. Reactive programlamanın en güzel özelliklerinden olan backpressure ile birlikte client uygulamamız server’dan sadece 20x’lik data işleyebileceğini iletir ve servis sadece 20x’lik bir data gönderimi yapar. Böylece client işleyebileceği miktarda yük ile performanslı çalışmasına devam eder..

Stream apilerindeki lambda operasyonlarına benzer şekilde süreçler yönetilebilir. Yani fonksiyonel programlama reactive programlama ile çok iyi bir ikilidir.

Hangi uygulamalar için uygun? Nerelerde kullanmalıyım?

Artan kullanıcı sayısı ve trafikle birlikte artık backend servisleri de reactive ve asenkron işlemler yapabilme ihtiyacı duyuyor. Bu ihtiyaç bizi Reactive Programlama seçeneğine götürüyor. Netflix yine burada da büyük bir oyuncu, bir süredir Java tabanlı sistemlerini Reactive modele dönüştürüyor. Asenkron çalışmasını sağlamak için sistemde ‘obsearvable’ katmanlarla, yoğun trafiği asenkron olarak karşılayabiliyorlar.

  • IoT Uygulamaları: Milyonlarca sensörün veri gönderdiği bir dünyada asenkron ve non-blocking işlemler tam da ihtiyaç olan şey. Her geçen gün gelişen sensör ve IoT cihazların varlığı daha çok trafiği anlık ve asenkron bir biçimde karşılayabilmeyi içeriyor. Bu da kullanıcı trafiğinden daha da yoğun bir trafik ortaya çıkarıyor. Bu trafiği kesintilere uğratmadan reactive programlama ile birlikte handle edebilirsiniz. Dolayısıyla IoT uygulamaları reactive programlamanın en çok kullanıldığı alanlardan biridir.
  • Veri monitoring, loglama uygulamaları: Verilerin loglarını işlemek ve bunu görselleştirmek için reactive programa oldukça sık kullanılıyor.
  • Oyun ve Sosyal Medya uygulamaları gibi kullanıcının tepkilerini ve hareketlerini inceleyen, izleyen uygulamaların analizleri ve veri işleme işlemleri için reactive programlama kullanılmaktadır.
  • Batch Uygulamaları: Özellikle toplu veriyle uğraşan uygulamalarda asenkron olarak threadlerin non-blocking bir şekilde yönetilmesi hem performans hem de zaman/maaliyet açısında oldukça fayda sağlamakta ve tam bunun için reactive programlama kullanılmaktadır.
  • İşlerin asenkron ve non-blocking yürütülmesi gereken, gerekmese de bununla birlikte iyi performans verebilecek uygulamalarda her zaman için reactive programlama bir seçenek olmalıdır.

Reactive manifesto

Reactive programlamadan bahsederken reactive manifestodan bahsetmeden olmaz. Reactive Manifesto, reactive programlamanın temel ilkelerini tanımlayan bir belgedir. reactivemanifesto.org

Reactive manifestonun bize öğütleri:

  • Reactive sistemler responsive olmalıdır. Sistem zamanında yanıt vermelidir. Responsive sistemler, tutarlı/stabil bir servis sunmak için hızlı ve tutarlı yanıt süreleri sağlamaya odaklanır.
  • Reactive sistemler resilient olmalıdır. Sistemin herhangi bir sorun ile karşılaşması durumunda yanıt vermeye devam etmelidir. Yani sistem kesintiye uğramamalıdır. Sorunları/hataları, componentleri birbirinden izole ederek her componentin kendi içinde resilient olması ile mümkün kılar. Bir component üzerindeki sorun diğerlerini etkilemez böylece sistem kesintiye uğramadan devam eder.
  • Reactive sistemler esnek olmalıdır. Reactive sistemler, değişikliklere tepki verebilir ve değişen iş yükü altında responsive kalabilir. Donanımları efektif kullanarak daha az zaman/maliyetle işlemlerini tamamlayabilir.
  • Reactive sistemler message driven olmalıdır. Resilient ilkesini oluşturmak için reactive sistemlerin, asenkron mesaj alışverişiyle birlikte componentler arasında iletişim kurabilir.

Rx — Reactive eXtension

Rx, Reactive eXtension’ın kısaltılmış halidir ve reactive programlama yapabilmeniz için neredeyse her dile özel bir kütüphane/api desteği sunar. Observable streamler ve lambda tarzı operatörler kullanarak asenkron ve event-driven uygulamalar geliştirmek için kütüphaneler içerir. Geliştiriciyi threadler oluşturma, senkron ve asenkron hatta concurrency gibi operasyonları yönetmesine yardımcı olur.

Rx kütüphanelerini kimler kimler kullanıyor.

Daha fazlasını resmi sitede bulabilirsiniz: http://reactivex.io/

Reactive programlamayı anlamak ve anlatmak, uygulamaktan çok daha zor :)

Bu bir reactive programlama yazı dizisinin ilk yazısıdır. Dolayısıyla serinin diğer yazılarını takip ederek, uygulayabilir ve kavramları daha iyi anlayabilirsiniz. Reactive programlamayı bir anda tam olarak anlamak zor olsa da, pratikler ile bu sorunun üstesinden gelebilirsiniz.

Serinin diğer yazılarını okumak için buyrunuz:

--

--

Gökhan Ayrancıoğlu
Delivery Hero Tech Hub

Software Engineer @Yemeksepeti • #Java • #Spring Boot • #Kotlin • #Spark • #Microservices • https://gokhana.dev