Gymterview
middle

Что такое паттерн Bulkhead?

Bulkhead (переборка) — это паттерн отказоустойчивости, изолирующий ресурсы для разных внешних вызовов, чтобы сбой одного компонента не исчерпал ресурсы для других.

Аналогия из жизни: корабль разделён на водонепроницаемые отсеки (bulkheads). Если один отсек затоплен, другие остаются сухими и корабль не тонет.

Проблема без Bulkhead

Пример
Без Bulkhead:
┌──────────────────────────────────┐
│ Thread Pool (20 потоков)         │
│ [customer] [customer] [customer] │ ← все потоки заняты зависшим
│ [customer] ... нет потоков для   │   customer-service
│ payment или notification!        │
└──────────────────────────────────┘

С Bulkhead:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Customer Pool│ │ Payment Pool │ │ Notif. Pool  │
│ (5 потоков)  │ │ (10 потоков) │ │ (5 потоков)  │
│ [зависли]    │ │ [работают]   │ │ [работают]   │
└──────────────┘ └──────────────┘ └──────────────┘

Два типа Bulkhead

Критерий Thread Pool Semaphore
Изоляция Полная (отдельные потоки) Частичная (общий пул)
Overhead Выше (переключение контекста) Ниже
Таймаут Можно прервать поток Нет контроля
Подходит для Медленные вызовы Быстрые вызовы
Thread Pool Bulkhead
@Service
public class PaymentService {

    @Bulkhead(name = "customerService", type = Bulkhead.Type.THREADPOOL,
              fallbackMethod = "getCustomerFallback")
    public CompletableFuture<CustomerDto> getCustomer(Long id) {
        return CompletableFuture.supplyAsync(() -> customerClient.getCustomer(id));
    }

    private CompletableFuture<CustomerDto> getCustomerFallback(Long id, Throwable t) {
        log.warn("Bulkhead для customer-service полон: {}", t.getMessage());
        return CompletableFuture.completedFuture(CustomerDto.unknown(id));
    }
}
resilience4j:
  thread-pool-bulkhead:
    instances:
      customerService:
        maxThreadPoolSize: 5
        coreThreadPoolSize: 3
        queueCapacity: 10
        keepAliveDuration: 60s
      paymentGateway:
        maxThreadPoolSize: 10
        coreThreadPoolSize: 5
        queueCapacity: 20
Semaphore Bulkhead
@Bulkhead(name = "customerService", type = Bulkhead.Type.SEMAPHORE)
public CustomerDto getCustomer(Long id) {
    return customerClient.getCustomer(id);
}
resilience4j:
  bulkhead:
    instances:
      customerService:
        maxConcurrentCalls: 5
        maxWaitDuration: 500ms

Комбинация паттернов отказоустойчивости (порядок имеет значение!)

Пример
Запрос → Bulkhead → CircuitBreaker → Retry → Timeout → Вызов сервиса

На собеседовании: объясните через аналогию с кораблём и покажите, что понимаете разницу между Thread Pool и Semaphore Bulkhead. Ключевой вопрос: почему Bulkhead стоит перед Circuit Breaker в цепочке — потому что он ограничивает количество одновременных вызовов ещё до проверки circuit breaker.