RPC调用在分布式系统中无处不在,但超时和异常处理却是很多开发者最容易忽视的部分。想象一下,你在调用一个远程服务时,对方服务挂了或者网络卡了,你的程序会一直等着等到什么时候?这就是超时控制要解决的问题。而异常处理,则是当事情出了岔子时,你的程序该怎么优雅地应对,而不是直接崩溃。

一、RPC调用超时控制的基本概念

1.1 什么是RPC超时

RPC(Remote Procedure Call,远程过程调用)说白了就是让本地代码去调用远程服务器的方法,就像你在本地调函数一样简单。但问题是,这个远程服务器可能不在线,或者网络太慢,你的调用就会一直等下去。超时控制就是给这个等待设一个上限时间,超过这个时间就不等了,直接告诉调用方"超时了"。

打个比方,你给快递员打电话问快递到哪了,你不可能一直等对方接电话吧?你会等个十几秒,如果对方没接,你就会挂掉,然后再打或者换个方式联系。这个"等个十几秒"就是超时时间。

1.2 超时的常见场景

在实际开发中,超时可能出现在这些地方:

  • 网络延迟:服务端和客户端之间的网络不稳定,数据包传输慢
  • 服务端处理慢:服务端正在处理大量请求,队列满了,你的请求排队等了很久
  • 服务端宕机:服务端直接挂了,你的请求石沉大海
  • 数据库查询慢:服务端在执行SQL时,查询走了全表扫描,执行了很长时间

二、超时控制的实现方案

2.1 客户端超时配置

客户端超时是最直接的超时控制方式。你在调用远程方法时,告诉框架"我最多等多久",超过这个时间框架就会主动中断等待,抛出一个超时异常。

以gRPC为例,你可以通过拦截器或者调用选项来设置超时:

/**
 * gRPC 客户端超时配置示例
 * 技术栈:Java 17 + Spring Boot 3.2 + gRPC 1.59
 */
@Component
public class GrpcClientConfig {

    /**
     * 创建带有超时设置的拦截器
     * 每个RPC调用都会带上这个超时时间
     */
    public ClientInterceptor timeoutInterceptor(long timeoutMs) {
        return (call, headers, next) -> {
            ClientCall<ByteString, ByteString> clonedCall = call;
            clonedCall.setDeadline(System.nanoTime() + Duration.ofMillis(timeoutMs).toNanos(), TimeUnit.NANOSECONDS);
            return next.startCall(call, headers);
        };
    }

    /**
     * 全局超时拦截器
     * 所有RPC调用统一设置超时时间
     */
    @Bean
    public ClientInterceptor globalTimeoutInterceptor() {
        return new io.grpc.ClientInterceptor() {
            @Override
            public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
                    MethodDescriptor<ReqT, RespT> method, 
                    CallOptions callOptions, 
                    Channel next) {
                
                // 如果没有设置超时,则使用默认超时时间5秒
                if (callOptions.getDeadlineNanoTime() == 0) {
                    long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
                    callOptions = callOptions.withDeadline(deadline, TimeUnit.NANOSECONDS);
                }
                return next.newCall(method, callOptions);
            }
        };
    }
}

2.2 服务端超时处理

服务端超时是站在服务提供方的角度做控制。即使客户端没设超时,服务端也应该设一个"最大处理时间",避免某个请求占用资源太久。

/**
 * gRPC 服务端超时处理示例
 * 技术栈:Java 17 + Spring Boot 3.2 + gRPC 1.59
 */
@GrpcService
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {

    private final UserServiceMapper userMapper;

    public UserServiceImpl(UserServiceMapper userMapper) {
        this.userMapper = userMapper;
    }

    /**
     * 获取用户信息
     * 服务端设置最大处理时间3秒
     */
    @Override
    public void getUser(GetUserRequest request, StreamObserver<GetUserResponse> responseObserver) {
        try {
            // 检查是否超时
            long deadline = responseObserver.getDeadline();
            if (deadline != 0) {
                long remaining = deadline - System.nanoTime();
                if (remaining < 0) {
                    throw Status.DEADLINE_EXCEEDED
                        .withDescription("服务端处理超时,请重试")
                        .asRuntimeException();
                }
            }

            // 执行业务逻辑
            User user = userMapper.findById(request.getUserId());
            if (user == null) {
                responseObserver.onError(Status.NOT_FOUND
                    .withDescription("用户不存在")
                    .asRuntimeException());
                return;
            }

            GetUserResponse response = GetUserResponse.newBuilder()
                .setUserId(user.getId())
                .setUsername(user.getUsername())
                .setEmail(user.getEmail())
                .build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();

        } catch (StatusRuntimeException e) {
            // 已经是gRPC状态异常,直接传递
            responseObserver.onError(e);
        } catch (Exception e) {
            // 未知异常,包装为INTERNAL错误
            responseObserver.onError(Status.INTERNAL
                .withDescription("服务端内部错误:" + e.getMessage())
                .withCause(e)
                .asRuntimeException());
        }
    }
}

2.3 超时传播与链路追踪

在分布式系统中,一个请求可能经过多个服务。如果第一个服务设了3秒超时,但它调用的第二个服务要4秒才能返回,第一个服务就会超时。这时候就需要超时传播——第一个服务把自己的剩余超时时间传给第二个服务,让第二个服务知道自己最多只能用多少时间。

/**
 * 超时传播示例 - 服务A调用服务B时传递剩余超时时间
 * 技术栈:Java 17 + Spring Boot 3.2 + gRPC 1.59
 */
@Service
public class OrderService {

    private final PaymentServiceGrpc.PaymentServiceBlockingStub paymentStub;
    private final InventoryServiceGrpc.InventoryServiceBlockingStub inventoryStub;

    public OrderService(
            PaymentServiceGrpc.PaymentServiceBlockingStub paymentStub,
            InventoryServiceGrpc.InventoryServiceBlockingStub inventoryStub) {
        this.paymentStub = paymentStub;
        this.inventoryStub = inventoryStub;
    }

    /**
     * 创建订单 - 包含超时传播逻辑
     * 总超时时间5秒,分配给下游服务
     */
    public OrderResult createOrder(CreateOrderRequest request) {
        long startTime = System.nanoTime();
        long totalTimeoutMs = 5000;

        // 第一步:检查库存(分配1秒超时)
        long inventoryTimeout = Math.min(1000, totalTimeoutMs - elapsed(startTime));
        try {
            InventoryResponse invResponse = inventoryStub
                .withDeadlineAfter(inventoryTimeout, TimeUnit.MILLISECONDS)
                .checkStock(request.getProductId());
        } catch (StatusRuntimeException e) {
            if (e.getStatus().getCode() == Status.Code.DEADLINE_EXCEEDED) {
                return OrderResult.failure("库存查询超时,请稍后重试");
            }
            throw e;
        }

        // 第二步:支付处理(分配剩余时间)
        long paymentTimeout = totalTimeoutMs - elapsed(startTime);
        try {
            PaymentResponse payResponse = paymentStub
                .withDeadlineAfter(paymentTimeout, TimeUnit.MILLISECONDS)
                .processPayment(request.getPaymentInfo());
        } catch (StatusRuntimeException e) {
            if (e.getStatus().getCode() == Status.Code.DEADLINE_EXCEEDED) {
                return OrderResult.failure("支付处理超时,请检查支付状态");
            }
            throw e;
        }

        return OrderResult.success("订单创建成功");
    }

    /**
     * 计算已消耗的时间(毫秒)
     */
    private long elapsed(long startTime) {
        return (System.nanoTime() - startTime) / 1_000_000;
    }
}

三、异常处理的完整方案

3.1 异常分类与处理方式

在RPC调用中,异常可以分为几大类:

  • 超时异常:调用时间超过预设的超时时间
  • 网络异常:连接不上服务端,DNS解析失败等
  • 业务异常:服务端返回的业务错误,比如用户不存在、余额不足
  • 序列化异常:请求或响应数据格式不对,无法解析
  • 未知异常:其他未预见的异常

不同的异常类型需要不同的处理策略。比如网络异常可以重试,但业务异常重试也没用。

/**
 * 异常分类处理示例
 * 技术栈:Java 17 + Spring Boot 3.2 + gRPC 1.59
 */
public class RpcExceptionClassifier {

    /**
     * 根据gRPC状态码分类异常,并返回对应的处理策略
     */
    public static ExceptionStrategy classify(StatusRuntimeException e) {
        Status.Code code = e.getStatus().getCode();

        switch (code) {
            case DEADLINE_EXCEEDED:
                return new ExceptionStrategy(true, "请求超时,正在重试...", ExceptionType.TIMEOUT);

            case UNAVAILABLE:
                return new ExceptionStrategy(true, "服务暂时不可用,请稍后重试", ExceptionType.UNAVAILABLE);

            case NOT_FOUND:
                return new ExceptionStrategy(false, "请求的资源不存在", ExceptionType.NOT_FOUND);

            case INVALID_ARGUMENT:
                return new ExceptionStrategy(false, "请求参数错误:" + e.getStatus().getDescription(), ExceptionType.INVALID_ARGUMENT);

            case PERMISSION_DENIED:
                return new ExceptionStrategy(false, "权限不足,无法访问该资源", ExceptionType.PERMISSION_DENIED);

            case INTERNAL:
                return new ExceptionStrategy(true, "服务内部错误,正在重试...", ExceptionType.INTERNAL);

            default:
                return new ExceptionStrategy(false, "未知错误:" + code.name(), ExceptionType.UNKNOWN);
        }
    }
}

/**
 * 异常处理策略
 */
public class ExceptionStrategy {
    private final boolean retryable;
    private final String userMessage;
    private final ExceptionType type;

    public ExceptionStrategy(boolean retryable, String userMessage, ExceptionType type) {
        this.retryable = retryable;
        this.userMessage = userMessage;
        this.type = type;
    }

    public boolean isRetryable() { return retryable; }
    public String getUserMessage() { return userMessage; }
    public ExceptionType getType() { return type; }
}

/**
 * 异常类型枚举
 */
public enum ExceptionType {
    TIMEOUT,
    UNAVAILABLE,
    NOT_FOUND,
    INVALID_ARGUMENT,
    PERMISSION_DENIED,
    INTERNAL,
    UNKNOWN
}

3.2 重试机制

重试是处理临时性故障最有效的手段。但重试不是随便重试,需要遵循一些原则:

  • 指数退避:第一次等1秒,第二次等2秒,第三次等4秒……这样不会把所有请求集中在一起
  • 最大重试次数:最多重试3次,防止无限循环
  • 幂等性检查:只有幂等操作才重试,比如查询可以重试,但"扣款"这种操作不能随便重试
/**
 * 带重试机制的RPC调用工具类
 * 技术栈:Java 17 + Spring Boot 3.2 + gRPC 1.59
 */
public class RetryableRpcClient {

    private static final int MAX_RETRIES = 3;
    private static final long INITIAL_DELAY_MS = 1000;
    private static final double BACKOFF_MULTIPLIER = 2.0;
    private static final long MAX_DELAY_MS = 10000;

    /**
     * 带重试的RPC调用方法
     */
    public <T> T callWithRetry(Supplier<T> call, String operationName, boolean isIdempotent) {
        int attempt = 0;
        long delay = INITIAL_DELAY_MS;

        while (true) {
            attempt++;
            try {
                return call.get();

            } catch (StatusRuntimeException e) {
                ExceptionStrategy strategy = RpcExceptionClassifier.classify(e);

                if (!strategy.isRetryable() || !isIdempotent || attempt > MAX_RETRIES) {
                    throw new RpcCallException(operationName, strategy, e);
                }

                log.warn("RPC调用失败,正在第{}次重试: operation={}, error={}, delay={}ms",
                    attempt, operationName, strategy.getUserMessage(), delay);

                try {
                    Thread.sleep(Math.min(delay, MAX_DELAY_MS));
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RpcCallException(operationName, strategy, ie);
                }

                delay = (long) (delay * BACKOFF_MULTIPLIER);

            } catch (Exception e) {
                throw new RpcCallException(operationName, 
                    new ExceptionStrategy(false, "系统错误", ExceptionType.UNKNOWN), e);
            }
        }
    }
}

/**
 * RPC调用异常
 */
public class RpcCallException extends RuntimeException {
    private final String operationName;
    private final ExceptionStrategy strategy;

    public RpcCallException(String operationName, ExceptionStrategy strategy, Throwable cause) {
        super(operationName + " failed: " + strategy.getUserMessage(), cause);
        this.operationName = operationName;
        this.strategy = strategy;
    }

    public String getOperationName() { return operationName; }
    public ExceptionStrategy getStrategy() { return strategy; }
}

3.3 熔断与降级

熔断机制的思想是:当一个服务连续失败多次后,不再调用它,直接返回错误或者降级结果。这样可以避免在下游服务已经明显有问题的情况下继续浪费资源。

/**
 * 熔断器实现 - 基于滑动窗口的熔断策略
 * 技术栈:Java 17 + Spring Boot 3.2 + gRPC 1.59
 */
public class CircuitBreaker {

    /** 熔断状态枚举 */
    public enum State {
        CLOSED,
        OPEN,
        HALF_OPEN
    }

    private volatile State state = State.CLOSED;
    private final AtomicInteger failureCount = new AtomicInteger(0);
    private final AtomicInteger successCount = new AtomicInteger(0);
    private volatile long lastFailureTime = 0;
    private volatile long lastOpenTime = 0;

    private final int failureThreshold;
    private final long recoveryTimeoutMs;
    private final int halfOpenMaxCalls;
    private final AtomicInteger halfOpenCalls = new AtomicInteger(0);

    public CircuitBreaker(int failureThreshold, long recoveryTimeoutMs, int halfOpenMaxCalls) {
        this.failureThreshold = failureThreshold;
        this.recoveryTimeoutMs = recoveryTimeoutMs;
        this.halfOpenMaxCalls = halfOpenMaxCalls;
    }

    /**
     * 执行受熔断保护的RPC调用
     */
    public <T> T execute(Supplier<T> call, Supplier<T> fallback) {
        if (state == State.OPEN) {
            if (System.currentTimeMillis() - lastOpenTime >= recoveryTimeoutMs) {
                transitionToHalfOpen();
            } else {
                log.warn("熔断器打开,执行降级逻辑");
                return fallback.get();
            }
        }

        if (state == State.HALF_OPEN) {
            if (halfOpenCalls.incrementAndGet() > halfOpenMaxCalls) {
                halfOpenCalls.decrementAndGet();
                return fallback.get();
            }
        }

        try {
            T result = call.get();
            onSuccess();
            return result;

        } catch (Exception e) {
            onFailure();
            throw e;
        }
    }

    private void onSuccess() {
        int success = successCount.incrementAndGet();
        if (state == State.HALF_OPEN && success >= halfOpenMaxCalls) {
            transitionToClosed();
        }
    }

    private void onFailure() {
        int failure = failureCount.incrementAndGet();
        lastFailureTime = System.currentTimeMillis();

        if (failure >= failureThreshold) {
            transitionToOpen();
        }
    }

    private void transitionToHalfOpen() {
        state = State.HALF_OPEN;
        halfOpenCalls.set(0);
        log.info("熔断器进入半开状态,开始试探调用");
    }

    private void transitionToClosed() {
        state = State.CLOSED;
        failureCount.set(0);
        successCount.set(0);
        log.info("熔断器关闭,恢复正常调用");
    }

    private void transitionToOpen() {
        state = State.OPEN;
        lastOpenTime = System.currentTimeMillis();
        log.warn("熔断器打开,拒绝新的调用");
    }
}

/**
 * 熔断器使用示例
 */
@Service
public class PaymentService {

    private final CircuitBreaker circuitBreaker;
    private final PaymentServiceGrpc.PaymentServiceBlockingStub paymentStub;

    public PaymentService(PaymentServiceGrpc.PaymentServiceBlockingStub paymentStub) {
        this.paymentStub = paymentStub;
        this.circuitBreaker = new CircuitBreaker(5, 30000, 3);
    }

    /**
     * 带熔断保护的支付处理
     */
    public PaymentResult processPayment(PaymentRequest request) {
        return circuitBreaker.execute(
            () -> {
                PaymentResponse response = paymentStub
                    .withDeadlineAfter(3000, TimeUnit.MILLISECONDS)
                    .processPayment(convertToProto(request));
                return PaymentResult.success(response.getTransactionId());
            },
            () -> {
                log.warn("支付服务熔断,执行降级逻辑");
                return PaymentResult.pending("支付处理中,请稍后查询结果");
            }
        );
    }
}

四、完整实战示例

4.1 技术栈说明

以下示例统一使用以下技术栈:

  • Java 17
  • Spring Boot 3.2
  • gRPC 1.59
  • Protobuf 3.24
  • Lombok

4.2 Protobuf 定义

/**
 * 用户服务 gRPC 接口定义
 * 文件:proto/user_service.proto
 * 技术栈:Java 17 + Spring Boot 3.2 + gRPC 1.59 + Protobuf 3.24
 */
syntax = "proto3";

package user_service;

option java_package = "com.example.user.grpc";
option java_outer_classname = "UserServiceProto";

message User {
    int64 user_id = 1;
    string username = 2;
    string email = 3;
    string phone = 4;
    bool enabled = 5;
}

message GetUserRequest {
    int64 user_id = 1;
}

message GetUserResponse {
    User user = 1;
}

message CreateUserRequest {
    string username = 1;
    string email = 2;
    string phone = 3;
}

message CreateUserResponse {
    int64 user_id = 1;
    string message = 2;
}

service UserService {
    rpc GetUser(GetUserRequest) returns (GetUserResponse);
    rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
}

4.3 服务端完整实现

/**
 * 用户服务 gRPC 服务端实现
 * 技术栈:Java 17 + Spring Boot 3.2 + gRPC 1.59 + Protobuf 3.24
 */
@GrpcService
@Slf4j
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {

    private final UserRepository userRepository;
    private final ObjectMapper objectMapper;

    public UserServiceImpl(UserRepository userRepository, ObjectMapper objectMapper) {
        this.userRepository = userRepository;
        this.objectMapper = objectMapper;
    }

    /**
     * 获取用户信息
     * 
     * 超时处理:
     * - 检查客户端设置的deadline
     * - 处理数据库查询超时的情况
     * - 处理业务异常(用户不存在)
     */
    @Override
    public void getUser(GetUserRequest request, StreamObserver<GetUserResponse> responseObserver) {
        long startTime = System.currentTimeMillis();
        log.info("收到获取用户请求, userId={}, traceId={}", request.getUserId(), TraceIdUtils.get());

        try {
            // 1. 参数校验
            if (request.getUserId() <= 0) {
                responseObserver.onError(Status.INVALID_ARGUMENT
                    .withDescription("用户ID必须大于0")
                    .asRuntimeException());
                return;
            }

            // 2. 检查是否超时(客户端设置的deadline)
            long deadlineNanos = responseObserver.getDeadline();
            if (deadlineNanos != 0) {
                long remainingMs = (deadlineNanos - System.nanoTime()) / 1_000_000;
                if (remainingMs < 100) {
                    throw Status.DEADLINE_EXCEEDED
                        .withDescription("剩余时间不足,请求被拒绝")
                        .asRuntimeException();
                }
            }

            // 3. 查询数据库
            User user = userRepository.findById(request.getUserId())
                .orElse(null);

            // 4. 处理业务逻辑
            if (user == null) {
                responseObserver.onError(Status.NOT_FOUND
                    .withDescription("用户不存在: userId=" + request.getUserId())
                    .asRuntimeException());
                return;
            }

            // 5. 构建响应
            UserProto userProto = UserProto.newBuilder()
                .setUserId(user.getId())
                .setUsername(user.getUsername())
                .setEmail(user.getEmail())
                .setPhone(user.getPhone())
                .setEnabled(user.isEnabled())
                .build();

            GetUserResponse response = GetUserResponse.newBuilder()
                .setUser(userProto)
                .build();

            // 6. 发送响应
            responseObserver.onNext(response);
            responseObserver.onCompleted();

            log.info("获取用户成功, userId={}, 耗时={}ms", 
                request.getUserId(), System.currentTimeMillis() - startTime);

        } catch (StatusRuntimeException e) {
            log.error("获取用户失败, userId={}, error={}", 
                request.getUserId(), e.getStatus().getDescription());
            responseObserver.onError(e);

        } catch (DataAccessException e) {
            log.error("数据库查询失败, userId={}, error={}", 
                request.getUserId(), e.getMessage());
            responseObserver.onError(Status.UNAVAILABLE
                .withDescription("数据库服务暂时不可用,请稍后重试")
                .withCause(e)
                .asRuntimeException());

        } catch (Exception e) {
            log.error("获取用户发生未知异常, userId={}", 
                request.getUserId(), e);
            responseObserver.onError(Status.INTERNAL
                .withDescription("系统内部错误")
                .withCause(e)
                .asRuntimeException());
        }
    }

    /**
     * 创建用户 - 非幂等操作,不应该随意重试
     */
    @Override
    public void createUser(CreateUserRequest request, StreamObserver<CreateUserResponse> responseObserver) {
        long startTime = System.currentTimeMillis();
        log.info("收到创建用户请求, username={}, traceId={}", 
            request.getUsername(), TraceIdUtils.get());

        try {
            // 1. 参数校验
            validateCreateRequest(request);

            // 2. 检查用户名是否已存在
            Optional<User> existing = userRepository.findByUsername(request.getUsername());
            if (existing.isPresent()) {
                responseObserver.onError(Status.ALREADY_EXISTS
                    .withDescription("用户名已存在: " + request.getUsername())
                    .asRuntimeException());
                return;
            }

            // 3. 保存用户
            User user = new User();
            user.setUsername(request.getUsername());
            user.setEmail(request.getEmail());
            user.setPhone(request.getPhone());
            user.setEnabled(true);
            userRepository.save(user);

            // 4. 构建响应
            CreateUserResponse response = CreateUserResponse.newBuilder()
                .setUserId(user.getId())
                .setMessage("用户创建成功")
                .build();

            responseObserver.onNext(response);
            responseObserver.onCompleted();

            log.info("创建用户成功, userId={}, username={}, 耗时={}ms",
                user.getId(), user.getUsername(), System.currentTimeMillis() - startTime);

        } catch (StatusRuntimeException e) {
            log.error("创建用户失败, username={}, error={}", 
                request.getUsername(), e.getStatus().getDescription());
            responseObserver.onError(e);

        } catch (Exception e) {
            log.error("创建用户发生未知异常, username={}", 
                request.getUsername(), e);
            responseObserver.onError(Status.INTERNAL
                .withDescription("系统内部错误")
                .withCause(e)
                .asRuntimeException());
        }
    }

    /**
     * 校验创建用户请求参数
     */
    private void validateCreateRequest(CreateUserRequest request) {
        if (request.getUsername() == null || request.getUsername().trim().isEmpty()) {
            throw Status.INVALID_ARGUMENT
                .withDescription("用户名不能为空")
                .asRuntimeException();
        }
        if (request.getUsername().length() > 50) {
            throw Status.INVALID_ARGUMENT
                .withDescription("用户名长度不能超过50个字符")
                .asRuntimeException();
        }
        if (!request.getEmail().matches("^[\\w.-]+@[\\w.-]+\\.\\w+$")) {
            throw Status.INVALID_ARGUMENT
                .withDescription("邮箱格式不正确")
                .asRuntimeException();
        }
    }
}

4.4 客户端完整实现

/**
 * 用户服务 gRPC 客户端实现
 * 技术栈:Java 17 + Spring Boot 3.2 + gRPC 1.59 + Protobuf 3.24
 */
@Service
@Slf4j
public class UserServiceClient {

    private final UserServiceGrpc.UserServiceBlockingStub blockingStub;
    private final CircuitBreaker circuitBreaker;
    private final RetryableRpcClient retryClient;

    private static final long DEFAULT_TIMEOUT_MS = 5000;
    private static final long READ_TIMEOUT_MS = 3000;
    private static final long WRITE_TIMEOUT_MS = 2000;

    public UserServiceClient(
            ManagedChannel channel,
            CircuitBreaker circuitBreaker,
            RetryableRpcClient retryClient) {
        this.blockingStub = UserServiceGrpc.newBlockingStub(channel);
        this.circuitBreaker = circuitBreaker;
        this.retryClient = retryClient;
    }

    /**
     * 获取用户信息 - 带完整的超时控制和异常处理
     */
    public UserProto getUser(long userId) {
        return circuitBreaker.execute(
            () -> retryClient.callWithRetry(
                () -> {
                    long startTime = System.currentTimeMillis();
                    log.debug("调用获取用户, userId={}", userId);

                    GetUserRequest request = GetUserRequest.newBuilder()
                        .setUserId(userId)
                        .build();

                    GetUserResponse response = blockingStub
                        .withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
                        .getUser(request);

                    log.debug("获取用户成功, userId={}, 耗时={}ms", 
                        userId, System.currentTimeMillis() - startTime);

                    return response.getUser();
                },
                "GetUser",
                true
            ),
            () -> {
                log.warn("获取用户熔断,返回降级结果, userId={}", userId);
                return UserProto.newBuilder()
                    .setUserId(userId)
                    .setUsername("未知用户")
                    .setEnabled(false)
                    .build();
            }
        );
    }

    /**
     * 创建用户 - 不重试(非幂等操作)
     */
    public CreateUserResponse createUser(String username, String email, String phone) {
        return circuitBreaker.execute(
            () -> {
                long startTime = System.currentTimeMillis();
                log.debug("调用创建用户, username={}", username);

                CreateUserRequest request = CreateUserRequest.newBuilder()
                    .setUsername(username)
                    .setEmail(email)
                    .setPhone(phone)
                    .build();

                CreateUserResponse response = blockingStub
                    .withDeadlineAfter(WRITE_TIMEOUT_MS, TimeUnit.MILLISECONDS)
                    .createUser(request);

                log.debug("创建用户成功, 耗时={}ms", System.currentTimeMillis() - startTime);
                return response;
            },
            () -> {
                throw new RpcCallException("CreateUser", 
                    new ExceptionStrategy(false, "用户创建服务暂时不可用,请稍后重试", 
                        ExceptionType.UNAVAILABLE),
                    new RuntimeException("熔断器打开"));
            }
        );
    }

    /**
     * 批量获取用户 - 并行调用优化
     */
    public List<UserProto> getUserBatch(List<Long> userIds, long timeoutMs) {
        long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs);
        
        List<Future<UserProto>> futures = new ArrayList<>();
        for (long userId : userIds) {
            futures.add(CompletableFuture.supplyAsync(() -> {
                GetUserRequest request = GetUserRequest.newBuilder()
                    .setUserId(userId)
                    .build();

                GetUserResponse response = blockingStub
                    .withDeadline(deadline, TimeUnit.NANOSECONDS)
                    .getUser(request);

                return response.getUser();
            }));
        }

        List<UserProto> results = new ArrayList<>();
        for (Future<UserProto> future : futures) {
            try {
                UserProto user = future.get(timeoutMs, TimeUnit.MILLISECONDS);
                if (user != null) {
                    results.add(user);
                }
            } catch (TimeoutException e) {
                log.warn("批量获取用户超时, future已取消");
                future.cancel(true);
            } catch (Exception e) {
                log.warn("批量获取用户失败", e);
            }
        }
        return results;
    }
}

4.5 配置文件

# application.yaml - gRPC 超时和重试配置
# 技术栈:Java 17 + Spring Boot 3.2 + gRPC 1.59

grpc:
  server:
    port: 9090
    max-inbound-message-size: 10485760
    max-inbound-metadata-size: 8192

  client:
    default-timeout: 5s
    connect-timeout: 3s
    deadline: 10s
    
    retry:
      max-attempts: 3
      initial-backoff: 1s
      max-backoff: 30s
      backoff-multiplier: 2.0
      
    circuit-breaker:
      failure-threshold: 5
      recovery-timeout: 30s
      half-open-max-calls: 3

logging:
  level:
    com.example.user: INFO
    io.grpc: WARN
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

五、应用场景分析

5.1 微服务间调用

在微服务架构中,每个服务都是独立部署的。当订单服务调用库存服务时,如果库存服务响应慢,订单服务不应该一直等下去。通过设置合理的超时时间(比如2秒),订单服务可以在超时后执行降级逻辑,返回"库存查询中"的友好提示,而不是让整个订单流程卡死。

实际案例:一个电商系统的下单流程需要调用库存服务、支付服务、物流服务。如果每个调用设置2秒超时,整个流程最多等6秒。如果某个服务超时,返回降级结果,用户依然可以看到订单状态,而不是看到"系统错误"。

5.2 第三方接口调用

调用第三方API时,对方服务的稳定性你控制不了。比如调用地图服务获取地址,如果地图服务挂了,你的系统不应该崩溃。通过超时控制,你可以快速判断第三方服务不可用,然后切换备用方案或者提示用户稍后重试。

实际案例:外卖平台需要调用地图服务计算配送距离。设置3秒超时,如果超时则使用缓存的距离数据或者提示用户"正在计算距离,请稍后查看"。

5.3 内部RPC通信

即使是在内网环境中,RPC调用也可能因为网络抖动、GC停顿等原因超时。比如数据库连接池满了,查询排队等了很久。通过超时控制,可以避免线程被长时间占用,导致线程池耗尽。

六、技术优缺点分析

6.1 超时控制的优缺点

优点:

  • 防止请求无限等待,保护系统资源
  • 让调用方快速失败,而不是默默等待
  • 可以通过监控超时次数来判断服务健康状况
  • 为降级和熔断提供触发条件

缺点:

  • 超时时间设置需要平衡,太短会导致误判,太长会占用资源
  • 超时后请求可能还在执行,需要处理超时后的状态不一致
  • 在链路中传递超时需要额外的心跳机制
  • 超时异常需要仔细区分是网络问题还是服务问题

6.2 异常处理的优缺点

优点:

  • 让系统在面对故障时依然可用
  • 重试机制可以自动恢复临时性故障
  • 熔断器可以防止故障蔓延到整个系统
  • 分类处理可以让不同异常有不同的应对策略

缺点:

  • 实现复杂度较高,需要维护状态机
  • 重试可能造成重复操作,需要幂等性保证
  • 熔断器的配置需要经验积累
  • 降级逻辑需要额外开发和维护

七、注意事项

7.1 超时时间设置原则

超时时间不是拍脑袋决定的,需要根据实际场景来设置。一般来说:

  • 快速接口(简单查询):1-3秒
  • 普通接口(涉及数据库操作):3-5秒
  • 复杂接口(涉及多服务调用):5-10秒
  • 超时时间应该是服务端P99响应时间的1.5-2倍

不要设置太短的超时(比如100毫秒),因为网络抖动、GC停顿等都可能导致请求在100毫秒内无法完成,这样会导致大量误判超时。

7.2 重试次数控制

重试次数不是越多越好。每次重试都会带来额外的资源消耗,而且如果服务真的出了问题,重试只会加重负担。建议:

  • 最多重试3次:经验值,大多数临时故障3次内可以恢复
  • 使用指数退避:避免所有重试同时发生
  • 只对幂等操作重试:写操作不要重试,除非有幂等性保证
  • 设置最大重试时间:整体重试时间不超过原始超时时间的2倍

7.3 线程池管理

超时控制离不开线程池。每个RPC调用都会占用一个线程,如果大量调用超时但线程没有及时释放,线程池就会耗尽。

/**
 * 线程池配置示例 - 为RPC调用配置独立的线程池
 * 技术栈:Java 17 + Spring Boot 3.2 + gRPC 1.59
 */
@Configuration
public class ThreadPoolConfig {

    @Bean("grpcExecutor")
    public ExecutorService grpcExecutor() {
        return new ThreadPoolExecutor(
            20,
            50,
            60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100),
            new ThreadFactory() {
                private final AtomicInteger counter = new AtomicInteger(0);
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r, "grpc-call-" + counter.incrementAndGet());
                    t.setDaemon(true);
                    t.setUncaughtExceptionHandler((thread, ex) -> 
                        log.error("RPC线程异常, thread={}", thread.getName(), ex));
                    return t;
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }
}

7.4 监控与告警

超时和异常处理需要配合监控使用。你需要关注这些指标:

  • 超时次数和比例:判断服务是否响应变慢
  • 重试次数:判断是否有大量临时故障
  • 熔断器状态变化:判断是否有服务异常
  • 降级触发次数:判断降级逻辑是否合理

建议将这些指标上报到监控系统(比如Prometheus + Grafana),设置告警规则。当超时比例超过阈值时,及时通知开发团队排查问题。

八、文章总结

RPC调用的超时控制和异常处理是分布式系统稳定性的基石。通过合理设置超时时间,你可以防止系统资源被慢请求耗尽;通过完善的异常处理机制,你可以让系统在面对故障时优雅降级,而不是直接崩溃。

核心要点:

  1. 超时时间要合理:根据接口的实际响应时间设置,不要拍脑袋
  2. 异常要分类处理:不同异常类型有不同的处理策略
  3. 重试要有策略:只对幂等操作重试,使用指数退避
  4. 熔断要配合使用:连续失败后熔断,防止故障蔓延
  5. 监控要到位:超时、重试、熔断等指标都要监控

在实际开发中,建议将这些机制封装成通用的工具类或框架组件,让业务开发者可以轻松使用,而不需要每次都重新实现。这样既能保证系统的稳定性,又能提高开发效率。