İçeriğe geç

Servisler Arası İletişim Desenleri

Orta 10 dk Sık karşılaşılır

Önce şunu oku: Monolit mi, mikroservis mi?

30 saniyede özet

Bir istek dört servise uğruyorsa bekleme süreleri toplanır, çalışma ihtimalleri ise çarpılır. Çağrıları aynı anda yapmak beklemeyi kısaltır ama çalışma ihtimalini hiç değiştirmez; onun için başka bir çözüm gerekir.

Birini telefonla aradığında onun o anda müsait olması gerekir. Dört kişiyi arka arkaya aramak zorundaysan, işin biraz daha zorlaşır.

Zincirde bekleme toplanır, ayakta olma ihtimali çarpılır.
Adım adım oku
  1. Senkron zincirde A, B'yi; B, C'yi; C de D'yi arar ve her biri cevap gelene kadar bekler.
  2. Bekleme süreleri toplanır: isteğin süresi zincirdeki bütün servislerin süreleri kadardır.
  3. Her servis kendi başına zamanın yüzde doksan dokuzunda ayaktaysa, dördünün birden ayakta olma ihtimali çarpılarak düşer.
  4. Mesaj bırakmak zinciri kırar: gönderen beklemez, alıcı uygun olunca işler; bedeli cevabın hemen gelmemesidir.
  1. Bayt: Servislerimizin her biri tek başına çok güvenilir. Zincir de öyle olur, değil mi?

  2. Sen: Mantıklı gibi.

  3. Bayt: Ama dört servisi arka arkaya çağırınca hem yavaşladık hem daha sık hata aldık!

  4. Bayt: Telefonla aramak ile mesaj bırakmak arasındaki farkı düşün.

Bir istek dört servise uğruyor. Bu cümlenin iki ayrı maliyeti var ve bunlar farklı matematikle hesaplanır.

Biri toplanır, biri çarpılır

Dört servis zincir hâlinde senkron çağrılıyor ve her biri yüzde 99.9 ayakta. Zincirin tamamı birlikte daha mı sık ayakta, daha mı az? Cevabı göster

Daha az. Kullanılabilirlikler çarpılır; her yeni halka zinciri biraz daha kırılgan yapar.

Gecikme toplanır:

Gateway ──60ms──> Order ──60ms──> Payment ──60ms──> Stock ──60ms──> Shipping
toplam: 240 ms

Kullanılabilirlik çarpılır:

her servis %99.9 → 0.999⁴ = %99.60 → ayda ~173 dakika kesinti

Dört tane “üç dokuzlu” servis, birlikte üç dokuzdan daha kötüdür. Zincire eklenen her halka ikisini birden bozar.

Kafam karıştı, daha basit anlat

Servisleri zincir hâlinde arka arkaya çağırırsan bekleme süreleri toplanır. Her birinin çalışma ihtimali ise çarpılır: halka arttıkça zincirin kopma ihtimali büyür.

Hızlı kontrolBaşlangıç

Senkron ve asenkron iletişim arasındaki temel fark nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Servisler arası iletişim — gecikme toplanır, kullanılabilirlik çarpılır

Tohum 5
  • Orderbekliyor
  • Paymentbekliyor
  • Inventorybekliyor
  • Shippingbekliyor

Gecikme — toplanır

0 ms/ beklenen 240 ms

4 × 60 ms

Kullanılabilirlik — çarpılır

%99.60

%99.9 ^ 4 · ayda ~173 dk kesinti

Hız
Adım 0

Şu an ne oldu?

İstek başlıyor

Zincirde bir sonraki çağrı, bir öncekinin cevabı gelmeden başlayamaz. Bu yüzden süreler toplanır.

Aklında kalsın: Her servis tek tek %99.9 kullanılabilir; 4 tanesi birlikte %99.60.

Görevler0/3

  • Zincirdeki tek bir servisin düşmesiyle bütün isteği kaybetaçık

    İpucu

    Kullanılabilirliği en düşük dokuza, servis sayısını en yükseğe çek. Olmazsa "Yeni senaryo" dene.

  • Dört veya daha fazla servisi tek bir servisin süresinde çağıraçık

    İpucu

    Deseni paralel yap. Gecikme en yavaş çağrıya iner — ama kullanılabilirlik hâlâ çarpılır.

  • Her servis %90 kullanılabilirken isteği yine de kabul ettiraçık

    İpucu

    Asenkron desende çağıran yalnızca broker’a yazar; aşağıdaki servisler sonra, kendi hızlarında işler.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

Karşılaştır:

  • Zincir, 4 servis, 60 ms. Toplam 240 ms. Kullanılabilirlik kartına bak: %99.60.
  • Paralel yap. Gecikme 60 ms’ye düşüyor ama kullanılabilirlik değişmiyor.
  • Servis sayısını 6 yap. İkisi de kötüleşiyor: gecikme 360 ms, kullanılabilirlik %99.40.
  • Dokuz sayısını 2 yap (%99). Dört servis birlikte %96 — ayda 29 saat kesinti.
  • Asenkron seç. Çağıran 10 ms’de 202 alıyor ve kullanılabilirlik kartı boşalıyor: cevap yolu artık servislere bağlı değil.

Simülatörün altındaki Görevler bu dört gözlemi hedefe çeviriyor: zincirin kırılması, paralelin gecikme kazancı ve asenkronun kullanılabilirlik kazancı.

Kafam karıştı, daha basit anlat

Telefonla aramak, karşı taraf açana kadar beklemektir. Mesaj bırakmak ise hemen işine dönmektir, cevap sonra gelir.

Hızlı kontrolOrta

Dört servisi zincir yerine paralel çağırmak neyi değiştirir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Telefonla aramak

DesenGecikmeKullanılabilirlikNe zaman
ZincirToplamÇarpımHer adım bir öncekinin sonucuna ihtiyaç duyuyorsa
Paralel / BranchEn yavaş çağrıÇarpımÇağrılar birbirinden bağımsızsa
AggregatorEn yavaş çağrıÇarpımBir ekran için birden fazla servisten veri toplanıyorsa

Üçünde de kullanılabilirlik aynı: çarpım. Çünkü başarı için hepsinin dönmesi gerekir.

Paralelleştirme — gecikmeyi düzeltir
var user = supplyAsync(() -> userClient.fetch(id));
var orders = supplyAsync(() -> orderClient.fetch(id));
var prefs = supplyAsync(() -> prefsClient.fetch(id));
allOf(user, orders, prefs).join(); // 3 × 60 ms değil, 60 ms

Ama bu kod hâlâ üç servisin de ayakta olmasını gerektirir.

Kullanılabilirliği gerçekten düzelten şeyler. Paralelleştirme değil, bağımlılığı kaldırmak veya zorunluluğunu azaltmak:

  • Asenkron mesajlaşma. Çağıran broker’a yazar ve döner. Tüketici düşse bile mesaj kuyrukta bekler — karşılığında eventual consistencySistemin bir süre tutarsız kalıp sonunda tutarlı hâle gelmesi. Saga'nın verdiği garanti budur; arada geçilen ara durumu tasarımın kabul etmesi gerekir.Sözlükte gör → kabul edersin.
  • Zorunlu olmayan çağrıyı isteğe bağlı yapmak. Öneri servisi düştüyse ürün sayfası önerisiz açılsın. allOf yerine anyOf + fallback.
  • Önbellek. Servis düştüğünde bayat veri dönmek, hiç dönmemekten iyidir.
  • Veriyi kopyalamak. Sipariş servisi müşteri adını kendi kaydında tutarsa, o çağrı tamamen ortadan kalkar.

Son madde en güçlüsüdür: yapılmayan çağrı asla başarısız olmaz.

Hızlı kontrolOrta

Her çağrıyı senkron mu kalmalı, asenkrona mı çevrilmeli diye sınıflandır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Senkron kalmalı

Çağıran cevabı görmeden devam edemez

    Asenkrona çevrilmeli

    Sonuç cevabın bir parçası değil

      Mesaj bırakmanın bedeli

      Asenkron mesajlaşma bedava değil. Sattığın şeyler:

      • Eventual consistency. İstemci 202 aldı ama iş henüz bitmedi. Arayüzün bunu göstermesi gerekir.
      • “Bitti mi?” sorusu. Durum sorgulama endpoint’i, webhook veya bir olay akışı gerekir.
      • Hata görünürlüğü. Senkron çağrıda hata çağırana döner; asenkronda kuyrukta kalır ve kimse bakmazsa kaybolur. DLQ ve alarm şart — ayrıca tüketicinin idempotencyAynı işlemin bir kez de on kez de çalışsa aynı sonucu vermesi. Dağıtık sistemde timeout aldığında işlemin yapılıp yapılmadığını bilemediğin için zorunludur.Sözlükte gör → olması gerekir, çünkü teslim en-az-bir-kezdir.
      • İşletme yükü. Broker artık bakımını yapman gereken bir bileşen.

      Bu yüzden her çağrıyı asenkron yapmak da yanlıştır. Kullanıcının cevabı beklediği işlemler senkron kalmalıdır.

      Timeout bütçesi. Zincirde her servisin kendi timeout’u varsa toplam bekleme çarpıcı şekilde büyür:

      Gateway timeout: 1000 ms
      ├─ Order timeout: 800 ms
      │ └─ Payment timeout: 800 ms ← ikisi birlikte 1600 ms, gateway çoktan pes etti

      Kural: timeout bütçesiBir zincirdeki timeout değerlerinin içten dışa artacak şekilde bölünmesi. Dıştaki içtekinden küçükse çağıran vazgeçer ama iş devam eder.Sözlükte gör → yukarıdan aşağı dağıtılır. Her servis, çağırandan aldığı kalan süreyi bilmeli ve alt çağrısına ondan azını vermeli. Aksi halde gateway vazgeçtikten sonra alt servisler boşuna çalışmaya devam eder.

      Kafam karıştı, daha basit anlat

      Mesaj bıraktığında iş hemen bitmez. Kullanıcıya “alındı, işleniyor” demek ve sonucu sonra göstermek gerekir.

      Hızlı kontrolİleri

      Bir servis olay yayınlarken veritabanına yazma ile mesaj gönderme arasındaki tutarlılığı nasıl sağlarsın?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Tuzaklar, servis keşfi ve yük dengeleme

      Senkron çağrı yapabilmek için önce adresi bulmak gerekir:

      • İstemci tarafı keşif — istemci kayıt defterinden listeyi alır ve kendi seçer (Eureka + Ribbon). Ek ağ sıçraması yok, ama her istemciye mantık gömülür.
      • Sunucu tarafı keşif — istemci sabit bir adrese gider, yönlendirmeyi altyapı yapar (Kubernetes Service, yük dengeleyici). Basit, ama bir sıçrama daha ekler.

      Kubernetes ortamında ikincisi neredeyse her zaman doğru cevaptır — keşif altyapının işidir, uygulamanın değil.

      Aşağıdaki örnek bir bankanın mobil uygulamasından: ana ekran üç servisten veri topluyor, hesap dökümü PDF’i ise mesajla hazırlanıyor.

      Derinleş · Mobil bankacılık: senkron toplama, asenkron döküm 5 dosya · ~105 satır · ilk okumada atlayabilirsin
      Proje dosyaları

      src/main/java/com/bank/mobile/ HomeScreenAggregator.java Ana ekran: hesaplar ve kartlar zorunlu, kampanyalar değil. Üçü aynı anda çağrılır; kampanya servisi düşerse ekran kampanyasız açılır.

      src/main/java/com/bank/mobile/HomeScreenAggregator.java
      @Service
      class HomeScreenAggregator {
      private final AccountsClient accounts;
      private final CardsClient cards;
      private final CampaignsClient campaigns;
      private final ExecutorService io = Executors.newVirtualThreadPerTaskExecutor();
      HomeScreenAggregator(AccountsClient accounts, CardsClient cards, CampaignsClient campaigns) {
      this.accounts = accounts;
      this.cards = cards;
      this.campaigns = campaigns;
      }
      HomeScreen load(String customerNo, Deadline deadline) {
      Duration budget = deadline.remainingMinus(Duration.ofMillis(50)); // keep time to answer
      var accountList = supplyAsync(() -> accounts.of(customerNo), io).orTimeout(budget.toMillis(), MILLISECONDS);
      var cardList = supplyAsync(() -> cards.of(customerNo), io).orTimeout(budget.toMillis(), MILLISECONDS);
      // Optional: a failure here must not take the home screen down with it.
      var offers = supplyAsync(() -> campaigns.offersFor(customerNo), io)
      .completeOnTimeout(List.of(), budget.toMillis(), MILLISECONDS)
      .exceptionally(ex -> List.of());
      // Latency: the slowest of the three. Availability: accounts AND cards must answer.
      return new HomeScreen(accountList.join(), cardList.join(), offers.join());
      }
      }

      src/main/java/com/bank/web/ Deadline.java Timeout bütçesi: gateway'in verdiği son an bir başlıkla taşınır; her alt çağrı kalan süreden azını alır.

      src/main/java/com/bank/web/Deadline.java
      // "Answer by this instant", set once at the edge and passed down the chain.
      public record Deadline(Instant at, Clock clock) {
      public static final String HEADER = "X-Deadline";
      public static Deadline fromHeader(String epochMillis, Clock clock, Duration fallback) {
      return epochMillis == null
      ? new Deadline(Instant.now(clock).plus(fallback), clock)
      : new Deadline(Instant.ofEpochMilli(Long.parseLong(epochMillis)), clock);
      }
      public Duration remainingMinus(Duration margin) {
      Duration left = Duration.between(Instant.now(clock), at).minus(margin);
      if (left.isNegative() || left.isZero()) {
      throw new DeadlineExceededException(); // the caller has already given up: do no work
      }
      return left;
      }
      }

      src/main/java/com/bank/web/ DeadlinePropagation.java Başlığı her giden isteğe ekleyen interceptor: alt servis de ne kadar süresi kaldığını bilir.

      src/main/java/com/bank/web/DeadlinePropagation.java
      @Component
      class DeadlinePropagation implements ClientHttpRequestInterceptor {
      @Override
      public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
      throws IOException {
      Deadline deadline = RequestDeadline.current(); // set by a filter from the incoming header
      if (deadline != null) {
      request.getHeaders().set(Deadline.HEADER, String.valueOf(deadline.at().toEpochMilli()));
      }
      return execution.execute(request, body);
      }
      }

      src/main/java/com/bank/statement/ StatementRequestController.java Bir yıllık hesap dökümü beklenecek bir iş değil: istek 202 döner, durum ayrı bir uç noktadan sorulur.

      src/main/java/com/bank/statement/StatementRequestController.java
      @RestController
      @RequestMapping("/statements")
      class StatementRequestController {
      private final StatementRequests requests;
      private final KafkaTemplate<String, StatementRequested> kafka;
      StatementRequestController(StatementRequests requests, KafkaTemplate<String, StatementRequested> kafka) {
      this.requests = requests;
      this.kafka = kafka;
      }
      // A year of movements takes a while: do not hold the phone's connection for it.
      @PostMapping
      ResponseEntity<StatementStatus> request(@RequestBody @Valid StatementQuery query) {
      StatementStatus status = requests.open(query); // PENDING, with an id
      kafka.send("statement-requests", query.iban(), StatementRequested.of(status.id(), query));
      return ResponseEntity.accepted().location(URI.create("/statements/" + status.id())).body(status);
      }
      @GetMapping("/{id}")
      StatementStatus status(@PathVariable UUID id) {
      return requests.get(id); // PENDING, READY (with a link) or FAILED
      }
      }

      src/main/java/com/bank/statement/ StatementRequestedConsumer.java Dökümü hazırlayan tüketici idempotent: aynı mesaj iki kez gelirse ikinci PDF üretilmez.

      src/main/java/com/bank/statement/StatementRequestedConsumer.java
      @Component
      class StatementRequestedConsumer {
      private final StatementRequests requests;
      private final StatementPdf pdf;
      StatementRequestedConsumer(StatementRequests requests, StatementPdf pdf) {
      this.requests = requests;
      this.pdf = pdf;
      }
      // At-least-once delivery: the same request may arrive twice.
      @KafkaListener(topics = "statement-requests", groupId = "statement-builder")
      @Transactional
      void on(StatementRequested event) {
      if (!requests.claimIfPending(event.requestId())) return; // already built or building
      URI file = pdf.build(event.iban(), event.from(), event.to());
      requests.markReady(event.requestId(), file); // the app's next poll sees READY
      }
      }

      Kendini sına

      Şimşek turu1/5

      Senkron bir çağrı zincirinde bekleme süreleri toplanır.

      Soru 1/4Orta

      Dört servisi zincir yerine `CompletableFuture` ile paralel çağırdın. Ne değişti?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aklında kalacak üç şey

      1. 1 İki ayrı hesap var: bekleme süreleri toplanır, ayakta kalma ihtimalleri çarpılır. Bu ikisini karıştırmamak zincir tasarımının tamamını açıklar.
      2. 2 Çağrıları paralel yapmak beklemeyi toplamdan en uzuna indirir, ama ayakta kalma ihtimalini değiştirmez: hâlâ her çağrının başarılı olması gerekir.
      3. 3 Asenkron iletişim anlık bağımlılığı kaldırır; karşılığında 'iş ne zaman bitti?' sorusunu ve verinin bir süre farklı görünmesini getirir.
      Sonraki kapı Gateway, BFF, servis: mimari çizimdeki her kutu hangi soruya cevap veriyor? Mikroservis Katmanları — İstek Nereden Geçiyor · 10 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı

      Bu dersin üstüne kurulanlar

      Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.