在微服务架构盛行的今天,服务间通信的效率直接影响着系统整体性能。传统基于HTTP的RESTful API虽然简单易用,但在高并发场景下往往成为性能瓶颈。我曾参与的一个电商项目中,高峰期订单服务的RestTemplate调用延迟高达300ms,而切换到gRPC后直接降到了15ms。这种性能提升不是偶然——gRPC基于HTTP/2协议和Protobuf二进制编码,天生就为高性能RPC设计。
首先使用Spring Initializr创建一个标准的Spring Boot项目,选择Maven作为构建工具,添加Web基础依赖:
bash复制curl https://start.spring.io/starter.zip \
-d dependencies=web \
-d type=maven-project \
-d language=java \
-d bootVersion=3.1.0 \
-d groupId=com.example \
-d artifactId=grpc-demo \
-o grpc-demo.zip
解压后,我们需要在pom.xml中添加gRPC相关依赖。这里特别要注意版本兼容性问题——Spring Boot 3.x需要gRPC 1.50+版本才能完美兼容。
在pom.xml中添加以下关键配置:
xml复制<properties>
<grpc.version>1.54.0</grpc.version>
<protobuf.version>3.22.0</protobuf.version>
</properties>
<dependencies>
<!-- gRPC核心依赖 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Protobuf编译插件 -->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
提示:建议使用Maven的dependencyManagement统一管理gRPC相关依赖版本,避免潜在的版本冲突问题。
在src/main/proto目录下创建product_service.proto文件:
protobuf复制syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.example.grpc";
option java_outer_classname = "ProductProto";
service ProductService {
rpc GetProduct (ProductRequest) returns (ProductResponse) {}
rpc BatchGetProducts (stream ProductRequest) returns (ProductList) {}
}
message ProductRequest {
string product_id = 1;
}
message ProductResponse {
string id = 1;
string name = 2;
double price = 3;
int32 stock = 4;
}
message ProductList {
repeated ProductResponse products = 1;
}
这个定义包含两个RPC方法:
执行Maven编译命令自动生成代码:
bash复制mvn clean compile
生成的代码会出现在target/generated-sources/protobuf目录下,包含:
创建ProductServiceImpl类实现核心业务逻辑:
java复制@GrpcService
public class ProductServiceImpl extends ProductServiceGrpc.ProductServiceImplBase {
private final ProductRepository productRepo;
@Autowired
public ProductServiceImpl(ProductRepository productRepo) {
this.productRepo = productRepo;
}
@Override
public void getProduct(ProductRequest request,
StreamObserver<ProductResponse> responseObserver) {
Product product = productRepo.findById(request.getProductId());
ProductResponse response = ProductResponse.newBuilder()
.setId(product.getId())
.setName(product.getName())
.setPrice(product.getPrice())
.setStock(product.getStock())
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
@Override
public StreamObserver<ProductRequest> batchGetProducts(
StreamObserver<ProductList> responseObserver) {
return new StreamObserver<>() {
final List<ProductResponse> products = new ArrayList<>();
@Override
public void onNext(ProductRequest request) {
Product product = productRepo.findById(request.getProductId());
products.add(ProductResponse.newBuilder()
.setId(product.getId())
.setName(product.getName())
.build());
}
@Override
public void onError(Throwable t) {
log.error("Batch get products error", t);
}
@Override
public void onCompleted() {
responseObserver.onNext(ProductList.newBuilder()
.addAllProducts(products)
.build());
responseObserver.onCompleted();
}
};
}
}
创建gRPC服务器配置类:
java复制@Configuration
public class GrpcConfig {
@Bean
public GrpcServerFactoryCustomizer customizer() {
return factory -> {
factory.addService(ProductServiceImpl.class);
factory.setPort(9090);
};
}
}
Spring Boot会自动检测@GrpcService注解的类并注册到gRPC服务器。这种集成方式相比原生gRPC更加简洁,充分利用了Spring的依赖注入特性。
java复制@Service
public class ProductClient {
private final ProductServiceGrpc.ProductServiceBlockingStub blockingStub;
private final ProductServiceGrpc.ProductServiceStub asyncStub;
public ProductClient(@Value("${grpc.server.host:localhost}") String host,
@Value("${grpc.server.port:9090}") int port) {
ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.build();
this.blockingStub = ProductServiceGrpc.newBlockingStub(channel);
this.asyncStub = ProductServiceGrpc.newStub(channel);
}
public Product getProduct(String id) {
ProductRequest request = ProductRequest.newBuilder()
.setProductId(id)
.build();
ProductResponse response = blockingStub.getProduct(request);
return convertToDomain(response);
}
public List<Product> batchGetProducts(List<String> ids) {
final CountDownLatch latch = new CountDownLatch(1);
final List<Product> results = new ArrayList<>();
StreamObserver<ProductList> responseObserver = new StreamObserver<>() {
@Override
public void onNext(ProductList list) {
results.addAll(list.getProductsList().stream()
.map(this::convertToDomain)
.collect(Collectors.toList()));
}
@Override
public void onError(Throwable t) {
latch.countDown();
}
@Override
public void onCompleted() {
latch.countDown();
}
};
StreamObserver<ProductRequest> requestObserver = asyncStub.batchGetProducts(responseObserver);
ids.forEach(id -> requestObserver.onNext(
ProductRequest.newBuilder().setProductId(id).build()));
requestObserver.onCompleted();
try {
latch.await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return results;
}
}
在实际项目中,我们通过以下优化手段将gRPC性能提升了40%:
连接池管理:避免每次调用都创建新连接
java复制@Bean(destroyMethod = "shutdown")
public ManagedChannel managedChannel() {
return ManagedChannelBuilder.forTarget("localhost:9090")
.usePlaintext()
.defaultLoadBalancingPolicy("round_robin")
.build();
}
压缩传输:对大数据量启用gRPC压缩
java复制blockingStub.withCompression("gzip").getProduct(request);
Deadline设置:防止调用长时间阻塞
java复制blockingStub.withDeadlineAfter(500, TimeUnit.MILLISECONDS)
.getProduct(request);
异步调用:高并发场景使用非阻塞stub
java复制asyncStub.getProduct(request, new StreamObserver<>() {
// 处理响应回调
});
使用grpc-testing库编写集成测试:
java复制@SpringBootTest
class ProductServiceTest {
@Autowired
private ProductServiceImpl productService;
private Server grpcServer;
private ManagedChannel channel;
@BeforeEach
void setup() throws Exception {
grpcServer = ServerBuilder.forPort(0)
.addService(productService)
.build()
.start();
channel = ManagedChannelBuilder.forAddress("localhost", grpcServer.getPort())
.usePlaintext()
.build();
}
@Test
void testGetProduct() {
ProductServiceGrpc.ProductServiceBlockingStub stub =
ProductServiceGrpc.newBlockingStub(channel);
ProductResponse response = stub.getProduct(
ProductRequest.newBuilder().setProductId("123").build());
assertEquals("123", response.getId());
}
@AfterEach
void tearDown() {
channel.shutdown();
grpcServer.shutdown();
}
}
集成Micrometer收集gRPC指标:
java复制@Bean
public GrpcServerInterceptor metricsInterceptor(MeterRegistry registry) {
return new MetricCollectingServerInterceptor(registry);
}
// 在application.properties中配置
management.endpoints.web.exposure.include=metrics
management.metrics.tags.application=grpc-demo
这样可以在/metrics端点查看以下关键指标:
在实际迁移过程中,我们总结了以下经验:
渐进式迁移:可以先在非关键路径试点,逐步替换RestTemplate调用
API网关兼容:在网关层同时支持HTTP和gRPC入口
错误处理转换:将gRPC状态码转换为业务友好的错误信息
java复制try {
return client.getProduct(id);
} catch (StatusRuntimeException e) {
if (e.getStatus().getCode() == Status.Code.NOT_FOUND) {
throw new ProductNotFoundException(id);
}
throw new ServiceException("Product service error", e);
}
文档生成:使用protoc-gen-doc插件从proto文件生成API文档
bash复制protoc --doc_out=html,index.html:. *.proto
性能对比测试:在我们的测试环境中,相同硬件配置下:
TLS加密:生产环境务必启用TLS
java复制ManagedChannelBuilder.forAddress(host, port)
.useTransportSecurity()
.build();
健康检查:实现gRPC健康检查协议
java复制@GrpcService
public class HealthCheckService extends HealthGrpc.HealthImplBase {
@Override
public void check(HealthCheckRequest request,
StreamObserver<HealthCheckResponse> responseObserver) {
responseObserver.onNext(HealthCheckResponse.newBuilder()
.setStatus(ServingStatus.SERVING)
.build());
responseObserver.onCompleted();
}
}
负载均衡:与服务发现组件集成
java复制ManagedChannelBuilder.forTarget("dns:///product-service")
.defaultLoadBalancingPolicy("round_robin")
.build();
链路追踪:集成OpenTelemetry
java复制@Bean
public GrpcServerInterceptor tracingInterceptor(Tracer tracer) {
return new TracingServerInterceptor(tracer);
}
在Kubernetes环境中部署时,还需要注意:
问题1:Proto文件修改后代码未更新
解决方案:执行
mvn clean compile强制重新生成
问题2:Spring注入gRPC服务失败
java复制// 确保添加了扫描注解
@SpringBootApplication
@GrpcServiceScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
问题3:流式调用内存泄漏
onBackpressureBuffer控制流速java复制ServerBuilder.forPort(port)
.maxInboundMessageSize(10 * 1024 * 1024) // 10MB
.addService(new ProductServiceImpl())
.build();
问题4:高并发下的线程阻塞
java复制@Bean
public ExecutorService grpcExecutor() {
return Executors.newFixedThreadPool(20);
}
@Bean
public GrpcServerFactoryCustomizer customizer(ExecutorService executor) {
return factory -> {
factory.setExecutor(executor);
};
}
问题5:与Spring Web共存
properties复制# 修改gRPC服务端口避免冲突
grpc.server.port=9090
server.port=8080