3. 实现舱壁隔离限制故障影响范围

实现舱壁隔离限制故障影响范围 学习卡片

任务说明

本案例要求实现舱壁隔离(Bulkhead Isolation)模式,将系统资源(线程池、连接池等)按业务域划分为独立舱室,使单个舱室的故障(如慢调用、异常堆积)不会耗尽共享资源,从而限制故障影响范围。完成目标:构建一个最小可运行的舱壁隔离框架,支持为不同业务域分配独立线程池,并验证当某一舱室发生故障时,其他舱室仍能正常处理请求。

核心实现

// Bulkhead.java - 舱壁隔离核心类
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class Bulkhead {
    private final String name;
    private final ThreadPoolExecutor executor;
    private final Semaphore semaphore;
    private final int maxConcurrentCalls;
    private final AtomicInteger rejectedCount = new AtomicInteger(0);

    public Bulkhead(String name, int coreThreads, int maxThreads, int queueCapacity, int maxConcurrentCalls) {
        this.name = name;
        this.maxConcurrentCalls = maxConcurrentCalls;
        this.semaphore = new Semaphore(maxConcurrentCalls);
        this.executor = new ThreadPoolExecutor(
            coreThreads, maxThreads, 30L, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(queueCapacity),
            new ThreadFactory() {
                private final AtomicInteger counter = new AtomicInteger(1);
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, name + "-thread-" + counter.getAndIncrement());
                }
            },
            new ThreadPoolExecutor.AbortPolicy()
        );
    }

    public <T> Future<T> submit(Callable<T> task) throws RejectedExecutionException {
        return executor.submit(() -> {
            if (!semaphore.tryAcquire()) {
                rejectedCount.incrementAndGet();
                throw new BulkheadFullException(name + " 舱室并发数已达上限");
            }
            try {
                return task.call();
            } finally {
                semaphore.release();
            }
        });
    }

    public int getRejectedCount() { return rejectedCount.get(); }
    public int getActiveCount() { return executor.getActiveCount(); }
    public String getName() { return name; }
    public void shutdown() { executor.shutdown(); }

    public static class BulkheadFullException extends RuntimeException {
        public BulkheadFullException(String msg) { super(msg); }
    }
}
// BulkheadDemo.java - 演示故障隔离效果
import java.util.concurrent.*;

public class BulkheadDemo {
    public static void main(String[] args) throws Exception {
        // 创建两个独立舱室:支付舱室(故障)和订单舱室(正常)
        Bulkhead paymentBulkhead = new Bulkhead("payment", 2, 2, 5, 2);
        Bulkhead orderBulkhead = new Bulkhead("order", 2, 2, 5, 2);

        // 模拟支付舱室故障:每个任务都阻塞5秒(模拟第三方支付接口超时)
        for (int i = 0; i < 3; i++) {
            final int taskId = i;
            paymentBulkhead.submit(() -> {
                System.out.println("[" + Thread.currentThread().getName() + "] 支付任务 " + taskId + " 开始(模拟慢调用)");
                Thread.sleep(5000);
                return "支付结果-" + taskId;
            });
        }

        // 立即向订单舱室提交任务,验证其不受支付舱室故障影响
        for (int i = 0; i < 3; i++) {
            final int taskId = i;
            orderBulkhead.submit(() -> {
                System.out.println("[" + Thread.currentThread().getName() + "] 订单任务 " + taskId + " 执行成功");
                return "订单结果-" + taskId;
            });
        }

        // 等待2秒后检查两个舱室状态
        Thread.sleep(2000);
        System.out.println("\n=== 2秒后舱室状态 ===");
        System.out.println("支付舱室活动线程数: " + paymentBulkhead.getActiveCount());
        System.out.println("订单舱室活动线程数: " + orderBulkhead.getActiveCount());

        // 再向支付舱室提交任务,验证其因并发数超限被拒绝
        try {
            paymentBulkhead.submit(() -> "额外支付任务");
        } catch (RejectedExecutionException e) {
            System.out.println("支付舱室拒绝新任务: " + e.getMessage());
        }

        // 验证订单舱室仍可继续处理
        orderBulkhead.submit(() -> {
            System.out.println("[" + Thread.currentThread().getName() + "] 订单舱室仍可接受新任务");
            return "ok";
        });

        Thread.sleep(1000);
        System.out.println("\n=== 最终状态 ===");
        System.out.println("支付舱室拒绝任务数: " + paymentBulkhead.getRejectedCount());
        System.out.println("订单舱室拒绝任务数: " + orderBulkhead.getRejectedCount());

        paymentBulkhead.shutdown();
        orderBulkhead.shutdown();
    }
}

运行与验证

编译运行:

javac Bulkhead.java BulkheadDemo.java
java BulkheadDemo

预期输出(关键观测点):

[payment-thread-1] 支付任务 0 开始(模拟慢调用)
[payment-thread-2] 支付任务 1 开始(模拟慢调用)
[order-thread-1] 订单任务 0 执行成功
[order-thread-2] 订单任务 1 执行成功
[order-thread-1] 订单任务 2 执行成功

=== 2秒后舱室状态 ===
支付舱室活动线程数: 2
订单舱室活动线程数: 0

支付舱室拒绝新任务: 支付舱室并发数已达上限
[order-thread-1] 订单舱室仍可接受新任务

=== 最终状态 ===
支付舱室拒绝任务数: 1
订单舱室拒绝任务数: 0

判定标准:当支付舱室因慢调用占满2个并发许可后,新任务被快速拒绝(而非排队等待);订单舱室全程无拒绝、无阻塞,任务即时完成。这证明故障被限制在支付舱室内,未扩散至订单舱室。

舱壁隔离故障影响范围验证时序 客户端请求 支付舱壁 maxConcurrent=2 订单舱壁 maxConcurrent=2 线程池 (2线程) 任务1: 阻塞5s 任务2: 阻塞5s 线程池 (2线程) 任务1: 立即完成 任务2: 立即完成 第三方支付接口超时 新任务被快速拒绝 隔离边界:故障不跨越 图例 故障舱室 正常舱室 拒绝信号

关键设计说明

本实现采用信号量+独立线程池双重隔离机制:每个舱室拥有专属线程池,从资源层面隔离;信号量控制最大并发调用数,当并发数超限时立即抛出异常而非排队等待,实现快速失败。演示中支付舱室因模拟慢调用占满全部并发许可后,后续任务被立即拒绝,而订单舱室因独立资源不受任何影响。实际应用中可将线程池参数(核心线程数、队列容量)和并发上限按业务SLA独立配置,例如支付舱室分配较少线程防止拖垮整体,订单舱室分配充足线程保障核心链路。