返回博客

为什么我们选 gRPC 而不是 REST?内部通信设计决策

TL;DR

Coomia发布于 2025年6月25日18 分钟阅读
分享本文Twitter / X

为什么我们选 gRPC 而不是 REST?内部通信设计决策

系列:S2 架构全景 · 第 2 篇 | 难度:中级 | 阅读时间:18 分钟

TL;DR

  • 在内部通信基准测试中,gRPC 相比 REST/JSON 实现了 3-10 倍的吞吐量提升和 60-70% 的延迟下降,Protobuf 序列化体积仅为 JSON 的 30-50%。
  • 平台 64 个 proto 文件按 Layer 命名空间组织,通过统一的代码生成管线为 Java(gRPC-Java)和 Python(grpcio)生成类型安全的客户端存根,形成契约驱动开发工作流。
  • REST 仅作为外部门面存在(59 个端点),通过 Spring Cloud Gateway 实现 REST→gRPC 的协议转换,严格禁止内部服务使用 REST/JSON。

#1. 决策背景:一个技术红线的诞生

在 coomia-dip 的技术红线列表中,有一条很醒目:

禁止内部服务之间使用 REST/JSON(必须使用 gRPC)

这不是一个随意的偏好,而是经过严格基准测试和架构分析后的理性决策。本文将完整展开这个决策的推理过程。

#2. gRPC vs REST 基准测试

#2.1 测试环境

Code
硬件: 4C8G Linux VM (Intel Xeon E5-2680)
网络: 同一数据中心,延迟 < 0.1ms
场景: Ontology 实例 CRUD 操作
数据: 典型 Ontology 实例(15 个属性,3 个关系)

框架对比:
  REST: Spring Boot 3.x + Jackson + HTTP/1.1
  gRPC: gRPC-Java 1.60 + Protobuf 3 + HTTP/2

#2.2 延迟对比

Code
操作类型          REST (P50/P99)       gRPC (P50/P99)      改善
-----------     ----------------     ----------------     ------
GetInstance       2.1ms / 8.3ms       0.6ms / 2.1ms       71% / 75%
CreateInstance    3.8ms / 15.2ms      1.2ms / 4.8ms       68% / 68%
BatchGet(100)     18ms / 52ms          5ms / 14ms          72% / 73%
QueryFederation  45ms / 180ms        12ms / 48ms          73% / 73%
StreamSubscribe    N/A                 0.3ms/msg            N/A

#2.3 吞吐量对比

Code
并发连接数     REST (req/s)     gRPC (req/s)     倍数
-----------   ------------     ------------     -----
    10           4,200            14,500          3.5x
    50           8,100            38,000          4.7x
   100          10,200            62,000          6.1x
   500          11,500           105,000          9.1x
  1000          10,800           118,000         10.9x

#2.4 载荷体积对比

Code
数据对象                    JSON (bytes)     Protobuf (bytes)     压缩比
---------------------     ------------     ----------------     ------
OntologyInstance (小)         512               185               36%
OntologyInstance (中)        2,048              680               33%
OntologyInstance (大)        8,192             2,870              35%
BatchResponse (100条)      204,800            68,000              33%
WorldContext                  256                48               19%

#2.5 为什么差距这么大?

Code
HTTP/1.1 + JSON (REST):
+------+------------------+------+------------------+------+
| Conn | Headers (500B)   | Body | Headers (500B)   | Body |
| Est  | Content-Type     | JSON | Content-Type     | JSON |
|      | Accept           |      | Accept           |      |
|      | Authorization    |      | Authorization    |      |
|      | X-Request-Id     |      | X-Request-Id     |      |
+------+------------------+------+------------------+------+
  每个请求独立的 TCP 连接或 Keep-Alive 复用(串行)
  文本序列化,人类可读但冗余

HTTP/2 + Protobuf (gRPC):
+------+--------+--------+--------+--------+--------+
| Conn | Frame1 | Frame2 | Frame3 | Frame4 | Frame5 |
| Est  | (Req)  | (Req)  | (Resp) | (Req)  | (Resp) |
|      | 48B    | 185B   | 680B   | 48B    | 185B   |
+------+--------+--------+--------+--------+--------+
  单连接多路复用(并行)
  二进制序列化,紧凑高效
  Header 压缩 (HPACK)

关键差异因素

因素REST/JSONgRPC/Protobuf
传输协议HTTP/1.1HTTP/2 多路复用
序列化JSON(文本)Protobuf(二进制)
Header每请求完整 HeaderHPACK 压缩
连接管理Keep-Alive 串行单连接并行流
流式不原生支持原生双向流
类型安全运行时校验编译时检查

#3. Protobuf 契约驱动开发工作流

#3.1 Proto-First 开发流程

在 coomia-dip 中,proto 文件是一切的起点:

Code
Step 1: 定义 Proto
  architect writes .proto file
         |
         v
Step 2: 团队评审
  proto PR review (breaking change check)
         |
         v
Step 3: 代码生成
  buf generate (Java stubs + Python stubs)
         |
         +---> Java: control-Layer/src/gen/
         |     com.onto.{Layer}.api.v1.*
         |
         +---> Python: intelligence-Layer/gen/
         |     onto_{Layer}_v1_pb2.py
         |     onto_{Layer}_v1_pb2_grpc.py
         |
         +---> SDK: python-sdk/ontology_sdk/gen/
               (gRPC client wrappers)
         |
         v
Step 4: 实现服务端
  Java: implements XxxServiceGrpc.XxxServiceImplBase
  Python: class XxxServicer(xxx_pb2_grpc.XxxServicer)
         |
         v
Step 5: SDK 封装
  Python SDK wraps gRPC stubs into Pythonic API
         |
         v
Step 6: REST Facade 映射
  Spring Cloud Gateway maps REST endpoints to gRPC calls

#3.2 实际 Proto 示例

OntologyRuntimeService 为例,这是平台最核心的服务:

PROTOBUF
// proto/plane_c/ontology_runtime.proto
syntax = "proto3";
package com.onto.data.v1;

option java_package = "com.onto.data.api.v1";
option java_outer_classname = "OntologyRuntimeProto";
option java_multiple_files = true;

import "common/common.proto";
import "common/errors.proto";
import "google/protobuf/struct.proto";
import "google/protobuf/timestamp.proto";

service OntologyRuntimeService {
  // Instance CRUD
  rpc CreateInstance(CreateInstanceRequest)
      returns (CreateInstanceResponse);
  rpc GetInstance(GetInstanceRequest)
      returns (GetInstanceResponse);
  rpc UpdateInstance(UpdateInstanceRequest)
      returns (UpdateInstanceResponse);
  rpc PatchInstance(PatchInstanceRequest)
      returns (PatchInstanceResponse);
  rpc DeleteInstance(DeleteInstanceRequest)
      returns (DeleteInstanceResponse);

  // Batch Operations
  rpc BatchCreateInstances(BatchCreateInstancesRequest)
      returns (BatchCreateInstancesResponse);
  rpc BatchGetInstances(BatchGetInstancesRequest)
      returns (BatchGetInstancesResponse);

  // Relation Management
  rpc CreateRelation(CreateRelationRequest)
      returns (CreateRelationResponse);
  rpc GetRelations(GetRelationsRequest)
      returns (GetRelationsResponse);
  rpc BatchCreateRelations(BatchCreateRelationsRequest)
      returns (BatchCreateRelationsResponse);

  // Time Travel
  rpc GetInstanceAtVersion(GetInstanceAtVersionRequest)
      returns (GetInstanceResponse);
}

message CreateInstanceRequest {
  com.onto.common.v1.RequestContext context = 1;
  string object_type_id = 2;
  google.protobuf.Struct attributes = 3;
  repeated RelationInput relations = 4;
}

message GetInstanceRequest {
  com.onto.common.v1.RequestContext context = 1;
  string instance_id = 2;
  repeated string select_properties = 3;  // 字段投影
  bool include_relations = 4;
  bool include_derived = 5;               // 是否包含派生属性
}

#3.3 共享类型:WorldContext 和 RequestContext

PROTOBUF
// proto/common/common.proto
message RequestContext {
  string request_id = 1;
  string user_id = 2;
  repeated string roles = 3;
  com.onto.common.v1.WorldContext world_context = 4;
  string trace_id = 5;
  string span_id = 6;
  map<string, string> headers = 7;
}

所有 gRPC 请求的第一个参数都是 RequestContext,它内嵌 WorldContext。这保证了:

  1. 租户隔离tenant_id + org_id 确保数据不泄漏
  2. 分支隔离world_id + branch_name 确保操作在正确的世界分支
  3. 可追踪性trace_id + span_id 支持 OpenTelemetry 分布式追踪
  4. 权限校验user_id + roles 供 Policy Engine 鉴权

#4. 64 个 Proto 文件的命名空间组织

#4.1 按 Layer 划分的 Proto 分布

Code
proto/
├── common/                         [3 files]
│   ├── common.proto                # 基础共享类型
│   ├── common_b.proto              # Control Layer 扩展类型
│   └── errors.proto                # 统一错误定义
│
├── plane_b/                        [16 files]
│   ├── authentication_service.proto    # 认证服务
│   ├── classification_service.proto    # 数据分类
│   ├── connection_registry.proto       # 连接注册
│   ├── dashboard_service.proto         # 仪表盘
│   ├── interface_registry.proto        # 接口类型注册
│   ├── ontology_proposal.proto         # 本体变更提案
│   ├── policy_engine.proto             # 策略引擎
│   ├── property_access.proto           # 属性访问控制
│   ├── scenario_service.proto          # 场景管理
│   ├── schema_registry.proto           # Schema 注册
│   ├── struct_type.proto               # 结构体类型
│   ├── user_management.proto           # 用户管理
│   └── world_manager.proto             # 世界管理
│
├── plane_c/                        [15 files]
│   ├── data_ingestion.proto            # 数据接入
│   ├── materialized_view.proto         # 物化视图
│   ├── metric_registry.proto           # 指标注册
│   ├── object_set.proto                # 对象集
│   ├── ontology_change_event.proto     # 本体变更事件
│   ├── ontology_edit.proto             # 本体编辑
│   ├── ontology_onboarding.proto       # 本体上线
│   ├── ontology_runtime.proto          # 本体运行时(核心)
│   ├── query_federation.proto          # 联邦查询
│   ├── resource_provisioning.proto     # 资源供给
│   ├── storage_sync.proto              # 存储同步
│   ├── temporal_query.proto            # 时间查询
│   ├── time_series_store.proto         # 时序存储
│   ├── timeseries_query.proto          # 时序查询
│   └── vector_service.proto            # 向量服务
│
├── plane_d/                        [14 files]
│   ├── approval_service.proto          # 审批服务
│   ├── decision_engine.proto           # 决策引擎
│   ├── derived_property.proto          # 派生属性
│   ├── function_runtime.proto          # 函数运行时
│   ├── impact_assessment.proto         # 影响评估
│   ├── lowcode_rule.proto              # 低代码规则
│   ├── ml_model_service.proto          # ML 模型服务
│   ├── oag.proto                       # OAG 图
│   ├── problem_type.proto              # 问题类型
│   ├── rag_service.proto               # RAG 服务
│   ├── reasoning_engine.proto          # 推理引擎
│   ├── reasoning_subscription.proto    # 推理订阅
│   ├── rule_script.proto               # 规则脚本
│   ├── solver.proto                    # 求解器
│   └── temporal_query.proto            # 时间查询
│
├── plane_e/                        [5 files]
│   ├── action_engine.proto             # 动作引擎
│   ├── aip_logic_workflow.proto        # AIP 逻辑工作流
│   ├── approval_workflow.proto         # 审批工作流
│   ├── mutation_rules.proto            # 变更规则
│   └── notification.proto              # 通知服务
│
├── plane_f/                        [8 files]
│   ├── automate.proto                  # 自动化
│   ├── connection_management.proto     # 连接管理
│   ├── dolphinscheduler_project.proto  # DS 项目管理
│   ├── lineage.proto                   # 血缘追踪
│   ├── pipeline_engine.proto           # 管道引擎
│   ├── scheduler.proto                 # 调度器
│   ├── transform_executor.proto        # 转换执行器
│   └── trigger_rule.proto              # 触发规则
│
└── plane_g/                        [5 files]
    ├── audit_service.proto             # 审计服务
    ├── lineage_service.proto           # 血缘服务
    ├── metadata_service.proto          # 元数据服务
    ├── replay_service.proto            # 回放服务
    └── workflow_lineage.proto          # 工作流血缘

合计: 3 + 16 + 15 + 14 + 5 + 8 + 5 = 66 files
(含 common 的 3 个共享文件)

#4.2 命名约定

Code
包命名: com.onto.{Layer}.v1
Java 包: com.onto.{Layer}.api.v1
文件命名: {service_name}.proto (小写下划线)
服务命名: {ServiceName}Service (大驼峰)
方法命名: {VerbNoun} (大驼峰,如 CreateInstance)
消息命名: {MethodName}Request / {MethodName}Response

#5. 代码生成管线

#5.1 Buf 构建配置

YAML
# buf.yaml
version: v1
name: buf.build/onto/platform
breaking:
  use:
    - FILE
lint:
  use:
    - DEFAULT
  except:
    - PACKAGE_VERSION_SUFFIX

# buf.gen.yaml
version: v1
plugins:
  # Java gRPC stubs
  - plugin: buf.build/protocolbuffers/java
    out: control-Layer/src/main/java
  - plugin: buf.build/grpc/java
    out: control-Layer/src/main/java

  # Python gRPC stubs
  - plugin: buf.build/protocolbuffers/python
    out: intelligence-Layer/gen
  - plugin: buf.build/grpc/python
    out: intelligence-Layer/gen

  # Python type stubs (mypy)
  - plugin: buf.build/community/nipunn1313-mypy
    out: intelligence-Layer/gen

#5.2 生成产物示例

Java 侧(control-Layer/data-Layer)

Java
// 自动生成的 gRPC Service 基类
public abstract class OntologyRuntimeServiceGrpc
    .OntologyRuntimeServiceImplBase {

  public void createInstance(
      CreateInstanceRequest request,
      StreamObserver<CreateInstanceResponse> responseObserver) {
    // 待实现
  }

  public void getInstance(
      GetInstanceRequest request,
      StreamObserver<GetInstanceResponse> responseObserver) {
    // 待实现
  }
}

// 实现类
@GrpcService
public class OntologyRuntimeServiceImpl
    extends OntologyRuntimeServiceGrpc
        .OntologyRuntimeServiceImplBase {

  @Override
  public void createInstance(
      CreateInstanceRequest request,
      StreamObserver<CreateInstanceResponse> response) {

    WorldContext world = request.getContext()
        .getWorldContext();

    // 1. 验证 WorldContext
    worldValidator.validate(world);

    // 2. Schema 校验
    schemaRegistry.validateAttributes(
        world, request.getObjectTypeId(),
        request.getAttributes());

    // 3. 写入 Doris
    OntologyInstance instance = ontologyStore
        .create(world, request);

    // 4. 发布变更事件
    eventPublisher.publish(
        OntologyChangeEvent.of(instance));

    response.onNext(CreateInstanceResponse.newBuilder()
        .setInstance(instance.toProto())
        .build());
    response.onCompleted();
  }
}

Python 侧(intelligence-Layer / python-sdk)

Python
# 自动生成的 gRPC Client Stub
class OntologyRuntimeServiceStub:
    def __init__(self, channel: grpc.Channel):
        self.CreateInstance = channel.unary_unary(
            '/com.onto.data.v1.OntologyRuntimeService/'
            'CreateInstance',
            request_serializer=CreateInstanceRequest
                .SerializeToString,
            response_deserializer=CreateInstanceResponse
                .FromString,
        )

# SDK 封装层
class GrpcOntologyClient:
    """Python SDK 中的 Ontology 客户端"""

    def __init__(self, channel: grpc.Channel):
        self._stub = OntologyRuntimeServiceStub(channel)

    def get_instance(
        self,
        instance_id: str,
        *,
        select: list[str] | None = None,
        include_relations: bool = False,
        include_derived: bool = False,
    ) -> OntologyInstance:
        request = GetInstanceRequest(
            context=self._build_context(),
            instance_id=instance_id,
            select_properties=select or [],
            include_relations=include_relations,
            include_derived=include_derived,
        )
        response = self._stub.GetInstance(request)
        return OntologyInstance.from_proto(
            response.instance)

#6. 版本管理与向后兼容

#6.1 Proto 版本策略

Code
v1 = 稳定版本(当前所有服务使用)
v2 = 下一代版本(规划中)

兼容性规则:
  OK:  添加新字段(field number 不冲突即可)
  OK:  添加新 RPC 方法
  OK:  添加新枚举值
  BAD: 删除或重命名字段
  BAD: 更改字段类型
  BAD: 更改字段编号
  BAD: 删除 RPC 方法

#6.2 Buf Breaking Change 检测

Bash
# CI 流程中自动检测破坏性变更
$ buf breaking --against proto-baseline.binpb

# 如果通过,更新基线
$ buf build -o proto-baseline.binpb

#6.3 字段演进示例

PROTOBUF
// v1.0 - 初始版本
message WorldContext {
  string world_id = 1;
  string branch_name = 2;
  WorldType type = 5;
}

// v1.1 - 添加多租户支持(兼容)
message WorldContext {
  string world_id = 1;
  string branch_name = 2;
  optional string commit_hash = 3;        // 新增
  optional Timestamp as_of_timestamp = 4;  // 新增
  WorldType type = 5;
  map<string, string> metadata = 6;       // 新增
  string tenant_id = 7;                   // 新增
  string org_id = 8;                      // 新增
  string project_id = 9;                  // 新增
  WorldBranchType world_branch_type = 10; // 新增
  string manifest_id = 11;               // 新增
  optional string compute_version = 12;  // 新增
}

#7. gRPC 错误处理

#7.1 统一错误码体系

PROTOBUF
// proto/common/errors.proto
enum ErrorCode {
  ERROR_CODE_UNSPECIFIED = 0;

  // 通用错误 (1xxx)
  ERROR_CODE_INTERNAL = 1000;
  ERROR_CODE_INVALID_ARGUMENT = 1001;
  ERROR_CODE_NOT_FOUND = 1002;
  ERROR_CODE_ALREADY_EXISTS = 1003;
  ERROR_CODE_PERMISSION_DENIED = 1004;
  ERROR_CODE_UNAUTHENTICATED = 1005;

  // World 错误 (2xxx)
  ERROR_CODE_WORLD_NOT_FOUND = 2001;
  ERROR_CODE_BRANCH_NOT_FOUND = 2002;
  ERROR_CODE_BRANCH_CONFLICT = 2003;
  ERROR_CODE_WORLD_LOCKED = 2004;

  // Ontology 错误 (3xxx)
  ERROR_CODE_SCHEMA_VIOLATION = 3001;
  ERROR_CODE_INSTANCE_NOT_FOUND = 3002;
  ERROR_CODE_RELATION_INVALID = 3003;
  ERROR_CODE_DERIVED_COMPUTATION_FAILED = 3004;

  // Pipeline 错误 (4xxx)
  ERROR_CODE_PIPELINE_FAILED = 4001;
  ERROR_CODE_TRANSFORM_ERROR = 4002;
  ERROR_CODE_SCHEDULER_CONFLICT = 4003;
}

#7.2 错误传播机制

Code
Java (Server) -> gRPC Status -> Python (Client)

Java:
  throw new StatusRuntimeException(
      Status.NOT_FOUND
          .withDescription("Instance not found")
          .augmentDescription("instance_id=" + id));

Python:
  try:
      response = stub.GetInstance(request)
  except grpc.RpcError as e:
      if e.code() == grpc.StatusCode.NOT_FOUND:
          raise InstanceNotFoundError(
              e.details()) from e
      elif e.code() == grpc.StatusCode.PERMISSION_DENIED:
          raise PermissionDeniedError(
              e.details()) from e

#8. 流式 RPC:订阅与实时推送

#8.1 服务端流式(Server Streaming)

用于订阅本体变更事件:

PROTOBUF
// proto/plane_d/reasoning_subscription.proto
service ReasoningSubscriptionService {
  // 订阅推理结果变更
  rpc SubscribeReasoningResults(SubscribeRequest)
      returns (stream ReasoningEvent);

  // 订阅派生属性变更
  rpc SubscribeDerivedPropertyChanges(
      DerivedPropertySubscribeRequest)
      returns (stream DerivedPropertyChangeEvent);
}

message SubscribeRequest {
  RequestContext context = 1;
  repeated string object_type_ids = 2;  // 关注的类型
  repeated string instance_ids = 3;     // 关注的实例
  EventFilter filter = 4;              // 事件过滤
}

#8.2 双向流式(Bidirectional Streaming)

用于交互式推理对话:

PROTOBUF
service RagService {
  // 交互式 RAG 对话
  rpc Chat(stream ChatRequest)
      returns (stream ChatResponse);
}

#8.3 与 WebSocket 的关系

Code
外部客户端 (浏览器/SDK)
     |
     | WebSocket
     v
+------------------+
| REST Facade      |
| (Spring Gateway) |
+------------------+
     |
     | gRPC Server Streaming
     v
+------------------+
| Reasoning & Decision Layer + Agent Runtime Layer        |
| (Intelligence)   |
+------------------+

WebSocket 是面向外部客户端的推送协议
gRPC Streaming 是内部 Layer 间的推送协议
REST Facade 负责协议转换

#9. REST Gateway:外部门面设计

#9.1 为什么需要 REST?

gRPC 对浏览器不友好(需要 gRPC-Web 代理),REST 仍然是外部 API 的事实标准。因此我们保留 REST 作为纯粹的外部门面

#9.2 REST 端点映射

Code
REST Facade 端点分布 (59 endpoints):

Ontology 操作     : 15 endpoints
  POST   /api/v1/ontology/instances
  GET    /api/v1/ontology/instances/{id}
  PUT    /api/v1/ontology/instances/{id}
  PATCH  /api/v1/ontology/instances/{id}
  DELETE /api/v1/ontology/instances/{id}
  POST   /api/v1/ontology/instances/batch
  GET    /api/v1/ontology/instances/{id}/relations
  ...

Schema 管理       : 8 endpoints
  GET    /api/v1/schemas
  POST   /api/v1/schemas
  GET    /api/v1/schemas/{id}
  PUT    /api/v1/schemas/{id}/publish
  ...

World 管理        : 10 endpoints
  GET    /api/v1/worlds
  POST   /api/v1/worlds
  POST   /api/v1/worlds/{id}/branch
  POST   /api/v1/worlds/{id}/merge
  ...

推理与决策        : 8 endpoints
查询与分析        : 6 endpoints
Pipeline          : 5 endpoints
用户与权限        : 4 endpoints
审计与治理        : 3 endpoints

#9.3 REST→gRPC 转换实现

Java
// REST Controller (Spring Boot)
@RestController
@RequestMapping("/api/v1/ontology")
public class OntologyRestController {

    private final OntologyRuntimeServiceGrpc
        .OntologyRuntimeServiceBlockingStub stub;

    @GetMapping("/instances/{id}")
    public ResponseEntity<JsonNode> getInstance(
            @PathVariable String id,
            @RequestHeader("X-World-Id") String worldId,
            @RequestHeader("X-Branch") String branch) {

        // 1. 构建 gRPC Request
        GetInstanceRequest request = GetInstanceRequest
            .newBuilder()
            .setContext(RequestContext.newBuilder()
                .setWorldContext(WorldContext.newBuilder()
                    .setWorldId(worldId)
                    .setBranchName(branch)
                    .build())
                .build())
            .setInstanceId(id)
            .build();

        // 2. 调用 gRPC
        GetInstanceResponse response =
            stub.getInstance(request);

        // 3. Proto -> JSON
        return ResponseEntity.ok(
            ProtoJsonConverter.toJson(
                response.getInstance()));
    }
}

#10. 跨语言调用的实际路径

#10.1 Python SDK -> Java 服务的完整调用链

Code
Python SDK (用户代码)
     |
     | ontology.get_instance("obj_123")
     v
GrpcOntologyClient (Python SDK 封装层)
     |
     | 构建 GetInstanceRequest Protobuf
     v
grpcio Channel (Python gRPC 客户端)
     |
     | HTTP/2 + Protobuf 二进制
     | 目标: onto-data:6668
     v
Quarkus gRPC Server (onto-data.jar)
     |
     | 反序列化 Protobuf
     v
OntologyRuntimeServiceImpl (Java 实现)
     |
     | 业务逻辑 + Doris 查询
     v
GetInstanceResponse (Protobuf 响应)
     |
     | HTTP/2 + Protobuf 二进制
     v
Python SDK (反序列化为 Pydantic 模型)
     |
     | OntologyInstance 返回给用户
     v
用户代码

#10.2 Java -> Python 的调用(推理触发)

Code
onto-data.jar (Ontology 变更触发)
     |
     | DerivedPropertyService.compute()
     v
grpc-java Channel
     |
     | HTTP/2 + Protobuf
     | 目标: onto-intelligence:6670
     v
FastAPI gRPC Server (onto-intelligence)
     |
     | Python 推理引擎执行计算
     v
DerivedPropertyResponse
     |
     | HTTP/2 + Protobuf
     v
onto-data.jar (回写结果到 Doris)

#11. 性能优化实践

#11.1 连接池管理

Java
// Java 侧 gRPC 连接池
@Configuration
public class GrpcChannelConfig {
    @Bean
    public ManagedChannel intelligenceChannel() {
        return ManagedChannelBuilder
            .forAddress("onto-intelligence", 6670)
            .usePlaintext()       // 内网通信
            .maxInboundMessageSize(16 * 1024 * 1024)
            .keepAliveTime(30, TimeUnit.SECONDS)
            .keepAliveTimeout(10, TimeUnit.SECONDS)
            .build();
    }
}
Python
# Python 侧 gRPC 连接池
import grpc

channel = grpc.insecure_channel(
    'onto-data:6668',
    options=[
        ('grpc.max_receive_message_length',
         16 * 1024 * 1024),
        ('grpc.keepalive_time_ms', 30000),
        ('grpc.keepalive_timeout_ms', 10000),
        ('grpc.http2.max_pings_without_data', 0),
    ]
)

#11.2 批量操作优化

Code
单条操作 vs 批量操作性能对比 (100 条记录):

模式                   耗时        网络往返
---                   ----        --------
100 x GetInstance     180ms        100 次
1 x BatchGetInstances  5ms          1 次

结论: 批量 RPC 减少 97% 的延迟

#11.3 Deadline 传播

Java
// 设置 3 秒超时
stub.withDeadlineAfter(3, TimeUnit.SECONDS)
    .getInstance(request);

// 如果超时,gRPC 自动取消下游所有调用
// 避免雪崩效应

#12. 与其他内部通信方案的对比

方案延迟吞吐量类型安全流式跨语言生态
REST/JSON优秀最广
gRPC/Protobuf原生良好广泛
Thrift有限良好收窄
GraphQL订阅良好Web向
NATS/消息队列原生良好异步

为什么选 gRPC 而非 Thrift?

  • Google 背书,社区活跃度远超 Thrift
  • 原生 HTTP/2,与云原生生态完美集成
  • buf 工具链成熟(lint、breaking change 检测、代码生成)
  • Kubernetes 服务网格原生支持 gRPC 负载均衡

#Key Takeaways

  1. gRPC 不仅是性能选择,更是架构选择。Protobuf 契约文件就是 Layer 之间的"接口法典"——它们定义了数据结构、服务方法、错误码、版本演进规则。64 个 proto 文件覆盖了平台 100% 的内部交互。

  2. Proto-First 开发工作流消除了跨语言的隐式约定。先定义 proto,再生成代码,最后实现——这个流程保证了 Java 服务端和 Python 客户端对同一接口有完全一致的理解,编译时就能发现不兼容。

  3. REST 是门面,gRPC 是骨架。59 个 REST 端点不承载任何业务逻辑,它们只是 gRPC 调用的 HTTP 包装。这个分层让内部通信获得最大性能,同时对外保持 REST 的易用性。

下一篇预告: [S2-03] 存储架构演进:从 9 个组件到 5 个的统一之路——深入 Apache Doris 如何同时承担 OLAP、向量搜索和全文检索,以及 Nessie+Iceberg 如何实现全量版本化。

Tags: #grpc #protobuf #rest #api-design #contract-driven #code-generation #coomia-dip #智策平台