Di aplikasi monolith, kegagalan itu terang-terangan: satu proses mati, seluruh aplikasi ikut berhenti. Di sistem terdistribusi, kegagalan lebih sering datang setengah-setengah — proses kita sehat penuh, tetapi satu service tetangga melambat atau berhenti, dan hanya sebagian fitur yang terdampak. Keadaan ini disebut kegagalan parsial (partial failure), dan varian yang paling menyulitkan justru yang lambat: dependensi yang mati cepat memicu error yang jelas, sedangkan dependensi yang menggantung 30 detik diam-diam menahan thread, koneksi, dan memori pemanggil satu per satu sampai seluruh service ikut tumbang. Pola resilience adalah jawaban teknisnya — bukan mencegah kegagalan (mustahil), melainkan mengurungnya agar pengguna tetap dilayani.
Bulkhead dan Fallback #
Bulkhead menamai sekat kapal yang menjaga bocor di satu ruang tidak mengisi seluruh badan — pada microservices, sekatnya berupa batas berapa banyak pemanggilan yang boleh menghuni satu dependensi. Sisanya ditolak cepat dan dialihkan ke fallback: jawaban cadangan yang lebih murah. Program berikut memodelkan lima permintaan yang tiba bersamaan pada svc-pembayaran yang hanya sanggup memroses dua:
import java.util.ArrayList;
import java.util.List;
/** Bulkhead + fallback: beban berlebih dialihkan, tidak menumpuk di dependensi lambat. */
public class BulkheadFallback {
/** Gerbang pembatas: paling banyak `kapasitas` pemanggil menghuni dependensi. */
static class Bulkhead {
private final int kapasitas;
private int penghuni = 0;
Bulkhead(int kapasitas) {
this.kapasitas = kapasitas;
}
boolean cobaMasuk() {
if (penghuni < kapasitas) {
penghuni++;
return true;
}
return false;
}
void keluar() {
penghuni--;
}
int penghuni() {
return penghuni;
}
}
public static void main(String[] args) {
// Lima permintaan tiba hampir bersamaan (satu gelombang)
List<String> gelombang = List.of(
"pesanan-1001", "pesanan-1002", "pesanan-1003",
"pesanan-1004", "pesanan-1005");
System.out.println("== TANPA BULKHEAD: semua menumpuk di dependensi lambat ==");
System.out.println("menghuni svc-pembayaran : " + gelombang.size() + " pesanan");
System.out.println("langkah kerja tersumbat : " + (gelombang.size() * 2));
System.out.println();
System.out.println("== DENGAN BULKHEAD (kapasitas 2) + FALLBACK ==");
Bulkhead gerbang = new Bulkhead(2);
List<String> diproses = new ArrayList<>();
List<String> dialihkan = new ArrayList<>();
for (String p : gelombang) {
if (gerbang.cobaMasuk()) {
diproses.add(p);
} else {
dialihkan.add(p); // load shedding: ditolak cepat, dialihkan ke fallback
}
}
System.out.println("diproses svc-pembayaran : " + diproses);
System.out.println("dialihkan ke fallback : " + dialihkan + " (kwitansi cache)");
System.out.println("penghuni gerbang : " + gerbang.penghuni());
// Dua pesanan selesai; slot terbuka untuk gelombang berikutnya
gerbang.keluar();
gerbang.keluar();
System.out.println("setelah 2 selesai : penghuni = " + gerbang.penghuni()
+ ", slot kembali tersedia");
}
}Output yang Dihasilkan:
== TANPA BULKHEAD: semua menumpuk di dependensi lambat ==
menghuni svc-pembayaran : 5 pesanan
langkah kerja tersumbat : 10
== DENGAN BULKHEAD (kapasitas 2) + FALLBACK ==
diproses svc-pembayaran : [pesanan-1001, pesanan-1002]
dialihkan ke fallback : [pesanan-1003, pesanan-1004, pesanan-1005] (kwitansi cache)
penghuni gerbang : 2
setelah 2 selesai : penghuni = 0, slot kembali tersediaMari kita bedah komponennya:
class Bulkhead: penghitungpenghunidengan satu bataskapasitas. MethodcobaMasuk()tidak menunggu — bila gerbang penuh, pemanggil langsung menerimafalse(fail fast), itulah momen load shedding: menolak sebagian beban demi menyelamatkan sisanya.- Loop pemilahan gelombang: lima pesanan dicoba satu per satu ke gerbang kapasitas 2. Dua pertama (
pesanan-1001,pesanan-1002) menempati slot; tiga berikutnya langsung dialihkan ke daftardialihkan— dalam sistem nyata, alihan itu menerima kwitansi dari cache, bukan menunggu antrean panjang. - Baris “tanpa bulkhead”: kontras yang diukur adalah 10 langkah kerja tersumbat — lima pesanan menahan jalur pemanggil. Versi bersekat hanya menahan 2×2 langkah; tiga pesanan lain selesai seketika lewat fallback.
keluar()dua kali: begitu dua pekerjaan selesai,penghunikembali ke 0 dan slot terbuka untuk gelombang berikutnya — bulkhead menjaga antrean di depan gerbang tetap pendek.
Dalam JVM, sekat ini diwujudkan dengan Semaphore atau ThreadPoolExecutor terpisah per dependensi: kumpulan thread khusus untuk panggilan pembayaran, kumpulan lain untuk inventori — kelebihan beban di satu tetangga tidak menghabiskan thread tetangga lain.
Peta Pola Resilience #
Bulkhead dan fallback adalah dua dari enam pola yang bekerja bersama. Peta lengkapnya:
| Pola | Mengurung apa | Bentuk di Java |
|---|---|---|
| Timeout | waktu tunggu satu panggilan | .timeout(...) pada HttpRequest |
| Retry | kegagalan transien | loop dengan exponential backoff |
| Circuit breaker | service yang sakit lama | state machine CLOSED/OPEN/HALF_OPEN |
| Bulkhead | jumlah penghuni satu dependensi | Semaphore / thread pool terpisah |
| Fallback | efek kegagalan pada pengguna | jawaban cadangan: cache, nilai default |
| Load shedding | beban saat sistem kewalahan | tolak cepat di gerbang (HTTP 503 + Retry-After) |
Enam pola itu terbagi tiga kelompok: timeout dan retry bertindak di waktu (berapa lama menunggu, berapa kali mencoba), circuit breaker di status (kapan berhenti mencoba), dan bulkhead, fallback, load shedding di kapasitas (berapa yang boleh masuk, apa yang diberikan kepada yang ditolak). Sistem yang tangguh memakai kombinasi, bukan satu pola andalan.
Degradasi yang Anggun #
Tujuan akhir seluruh pola bukan “semua fitur selalu hidup”, melainkan degradasi anggun (graceful degradation): saat satu tetangga sakit, sistem turun ke versi lebih sederhana yang tetap berguna, alih-alih halaman error penuh. Toko daring yang kehilangan layanan rekomendasi tetap menampilkan katalog dan memproses pesanan — hanya bagian “sering dibeli bersama” yang menghilang sementara. Checkout yang kehilangan layanan ongkir real-time tetap menerima pesanan dengan estimasi standar, lalu menghitung ulang saat layanan pulih.
Desain degradasi menuntut satu keputusan bisnis per dependensi: mana yang wajib (pesanan harus bisa dibuat) dan mana yang bisa ditunda (email konfirmasi boleh terlambat — asalkan event-nya tersimpan). Pemetaan wajib/opsional inilah yang menentukan fallback mana yang perlu dibangun.
Tiga pola berikut — timeout, retry, circuit breaker — punya mekanisme yang cukup dalam untuk dibedah satu per satu dengan state machine yang bisa kita jalankan. Chapter berikutnya membangun ketiganya dalam bentuk mini namun lengkap.
Referensi #
- Martin Fowler: CircuitBreaker
- microservices.io: Circuit Breaker Pattern
- Martin Fowler: Microservices — Design for Failure
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
BulkheadFallback.java
Unduh berkasimport java.util.ArrayList;
import java.util.List;
/** Bulkhead + fallback: beban berlebih dialihkan, tidak menumpuk di dependensi lambat. */
public class BulkheadFallback {
/** Gerbang pembatas: paling banyak `kapasitas` pemanggil menghuni dependensi. */
static class Bulkhead {
private final int kapasitas;
private int penghuni = 0;
Bulkhead(int kapasitas) {
this.kapasitas = kapasitas;
}
boolean cobaMasuk() {
if (penghuni < kapasitas) {
penghuni++;
return true;
}
return false;
}
void keluar() {
penghuni--;
}
int penghuni() {
return penghuni;
}
}
public static void main(String[] args) {
// Lima permintaan tiba hampir bersamaan (satu gelombang)
List<String> gelombang = List.of(
"pesanan-1001", "pesanan-1002", "pesanan-1003",
"pesanan-1004", "pesanan-1005");
System.out.println("== TANPA BULKHEAD: semua menumpuk di dependensi lambat ==");
System.out.println("menghuni svc-pembayaran : " + gelombang.size() + " pesanan");
System.out.println("langkah kerja tersumbat : " + (gelombang.size() * 2));
System.out.println();
System.out.println("== DENGAN BULKHEAD (kapasitas 2) + FALLBACK ==");
Bulkhead gerbang = new Bulkhead(2);
List<String> diproses = new ArrayList<>();
List<String> dialihkan = new ArrayList<>();
for (String p : gelombang) {
if (gerbang.cobaMasuk()) {
diproses.add(p);
} else {
dialihkan.add(p); // load shedding: ditolak cepat, dialihkan ke fallback
}
}
System.out.println("diproses svc-pembayaran : " + diproses);
System.out.println("dialihkan ke fallback : " + dialihkan + " (kwitansi cache)");
System.out.println("penghuni gerbang : " + gerbang.penghuni());
// Dua pesanan selesai; slot terbuka untuk gelombang berikutnya
gerbang.keluar();
gerbang.keluar();
System.out.println("setelah 2 selesai : penghuni = " + gerbang.penghuni()
+ ", slot kembali tersedia");
}
}