You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Spring Boot+io.grpc的gRPC服务结构合理性咨询及拦截器疑问

gRPC服务结构合理性分析与拦截器指南

一、当前实现的问题与优化建议

1. 启动类(DemoApplication)

当前代码直接new HelloServiceImpl()添加到gRPC Server,导致@Service注解完全失效,该类无法被Spring容器管理,后续若需依赖注入其他Bean会彻底无法工作。同时server.awaitTermination()会阻塞ApplicationRunner的执行,导致Spring上下文初始化完成后主线程被持续占用,应用关闭时无法优雅停止gRPC Server。

优化方案:
将gRPC Server交由Spring管理,通过Bean生命周期控制启停:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean(destroyMethod = "shutdown")
    public Server grpcServer(HelloServiceImpl helloService) {
        Server server = ServerBuilder.forPort(31217)
                .addService(helloService)
                .build();
        try {
            server.start();
            server.awaitTermination();
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException("Failed to start gRPC server", e);
        }
        return server;
    }
}

此方案既利用Spring依赖注入管理Bean,又能在容器销毁时自动调用shutdown()优雅停止服务。

2. Controller层

每次请求都新建ManagedChannel是严重的性能问题——gRPC Channel是重量级资源,包含连接池、线程池等组件,频繁创建销毁会极大消耗系统资源。另外,channel.shutdown()后未等待关闭完成,可能导致资源泄漏。

优化方案:
将ManagedChannel和HelloServiceBlockingStub配置为单例Bean,注入到Controller中复用:

@Configuration
public class GrpcClientConfig {
    @Bean
    public ManagedChannel helloServiceChannel() {
        return ManagedChannelBuilder.forAddress("springboot", 31217)
                .usePlaintext()
                .build();
    }

    @Bean
    public HelloServiceGrpc.HelloServiceBlockingStub helloServiceBlockingStub(ManagedChannel channel) {
        return HelloServiceGrpc.newBlockingStub(channel);
    }

    @PreDestroy
    public void shutdownChannel() {
        helloServiceChannel().shutdown();
        try {
            helloServiceChannel().awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

修改Controller:

@RestController
public class ItemController {
    private final HelloServiceGrpc.HelloServiceBlockingStub helloStub;

    public ItemController(HelloServiceGrpc.HelloServiceBlockingStub helloStub) {
        this.helloStub = helloStub;
    }

    @GetMapping(path = "/item", produces = MediaType.APPLICATION_JSON_VALUE)
    public String printMessage(@RequestParam("name") String name) {
        HelloResponse helloResponse = helloStub.hello(HelloRequest.newBuilder()
                .setFirstName("Rafael")
                .setLastName("Fernando")
                .build());
        return helloResponse.getGreeting();
    }
}

3. gRPC服务实现类(HelloServiceImpl)

每次请求都新建ObjectMapper会重复消耗资源;异常处理直接抛出RuntimeException,会导致gRPC客户端收到默认的UNKNOWN错误状态,无法感知具体异常信息。

优化方案:
注入Spring管理的ObjectMapper,并通过responseObserver.onError()传递标准化错误:

@Service
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
    private static final Logger logger = LoggerFactory.getLogger(HelloServiceImpl.class);
    private final ObjectMapper objectMapper;

    public HelloServiceImpl(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void hello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
        try {
            Map<String, Object> map = new HashMap<>();
            map.put("name", request.getFirstName());
            map.put("lastName", request.getLastName());
            String jsonString = objectMapper.writeValueAsString(map);

            HelloResponse response = HelloResponse.newBuilder()
                    .setGreeting(jsonString)
                    .build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        } catch (JsonProcessingException e) {
            logger.error("Failed to process request", e);
            responseObserver.onError(Status.INTERNAL.withDescription("Failed to serialize response").asRuntimeException());
        }
    }
}

4. 配置类(AppConfig)

ProtobufJsonFormatHttpMessageConverter主要用于HTTP接口与Protobuf对象的转换,当前Controller返回的是String,这个Bean暂时未发挥作用。如果后续需要提供直接返回Protobuf对象的HTTP接口,该配置有效,可保留。

二、gRPC拦截器详解

gRPC拦截器分为服务端拦截器和客户端拦截器,用于在请求前后插入通用逻辑,比如日志、认证、监控、重试等。

1. 服务端拦截器

用于拦截服务端收到的请求,可实现权限校验、请求日志、耗时统计等功能。

实现示例:

public class ServerLoggingInterceptor implements ServerInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(ServerLoggingInterceptor.class);

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        String methodName = call.getMethodDescriptor().getFullMethodName();
        logger.info("Received gRPC request: {}", methodName);

        return new ForwardingServerCallListener.SimpleForwardingServerCallListener<>(next.startCall(call, headers)) {
            @Override
            public void onComplete() {
                logger.info("gRPC request completed: {}", methodName);
                super.onComplete();
            }

            @Override
            public void onCancel() {
                logger.warn("gRPC request cancelled: {}", methodName);
                super.onCancel();
            }
        };
    }
}

添加到gRPC Server:

Server server = ServerBuilder.forPort(31217)
        .addService(helloService)
        .intercept(new ServerLoggingInterceptor())
        .build();

2. 客户端拦截器

用于拦截客户端发起的请求,可实现请求重试、超时设置、请求日志等功能。

实现示例:

public class ClientLoggingInterceptor implements ClientInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(ClientLoggingInterceptor.class);

    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
        logger.info("Sending gRPC request: {}", method.getFullMethodName());

        return new ForwardingClientCall.SimpleForwardingClientCall<>(next.newCall(method, callOptions)) {
            @Override
            public void start(Listener<RespT> responseListener, Metadata headers) {
                super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener<>(responseListener) {
                    @Override
                    public void onMessage(RespT message) {
                        logger.info("Received gRPC response for: {}", method.getFullMethodName());
                        super.onMessage(message);
                    }

                    @Override
                    public void onClose(Status status, Metadata trailers) {
                        if (status.isOk()) {
                            logger.info("gRPC request succeeded: {}", method.getFullMethodName());
                        } else {
                            logger.error("gRPC request failed: {}, status: {}", method.getFullMethodName(), status);
                        }
                        super.onClose(status, trailers);
                    }
                }, headers);
            }
        };
    }
}

添加到gRPC Channel:

ManagedChannel channel = ManagedChannelBuilder.forAddress("springboot", 31217)
        .usePlaintext()
        .intercept(new ClientLoggingInterceptor())
        .build();

3. 常用拦截器场景

  • 认证拦截器:服务端校验请求头中的Token,客户端自动添加认证信息
  • 监控拦截器:统计请求耗时、成功率、QPS等指标
  • 重试拦截器:客户端对失败的请求进行重试(需结合gRPC重试配置)
  • 限流拦截器:服务端对请求进行流量控制,防止过载

内容的提问来源于stack exchange,提问作者Rafael Souza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 15:50:26