4. 定义搜索接口契约

在分布式搜索系统中,接口契约是客户端与服务端之间的“法律条文”。本章定义 SearchService 的 gRPC/Protobuf 接口、请求/响应结构体以及全局错误码规范,确保前后端、多语言 SDK 之间的通信行为可预测、可测试、可演进。

设计原则:契约先行,代码后写。先定义 .proto 文件,再生成 Go 代码,避免实现过程中随意修改接口导致上下游断裂。

4.1 SearchService 接口定义

采用 gRPC 作为 RPC 框架,Protobuf 作为 IDL。核心服务只暴露一个 RPC 方法 Search,保持接口简洁,便于后续扩展流式或双向流版本。

// search.proto
syntax = "proto3";

package search.v1;

option go_package = "github.com/example/search/api/v1;searchv1";

service SearchService {
  // 单次搜索请求,返回搜索结果及性能元数据
  rpc Search(SearchRequest) returns (SearchResponse);
}
为什么只定义一个 RPC? 搜索系统的核心语义就是“查询→返回结果”。将自动补全、推荐、统计等能力拆分为独立服务或独立 RPC,避免单个接口膨胀,降低尾延迟排查难度。

4.2 请求结构体设计

请求结构体需要携带查询文本、分页参数、排序规则以及可选的追踪标识。所有字段均使用小写蛇形命名,符合 Protobuf 风格。

message SearchRequest {
  // 查询字符串,必填,最大长度 1024 字符
  string query = 1;

  // 页码,从 1 开始,默认 1
  int32 page = 2;

  // 每页条数,默认 20,最大 100
  int32 page_size = 3;

  // 排序字段,可选:relevance / date / price
  string sort_by = 4;

  // 排序方向,true 表示降序
  bool sort_desc = 5;

  // 请求追踪 ID,用于全链路日志关联
  string trace_id = 6;

  // 超时控制(毫秒),客户端建议值,服务端可忽略
  int32 timeout_ms = 7;
}
字段 类型 必填 校验规则
query string 是 1 ≤ len ≤ 1024
page int32 否 ≥ 1,默认 1
page_size int32 否 1 ≤ value ≤ 100,默认 20
sort_by string 否 枚举值之一
trace_id string 否 UUID 格式建议

4.3 响应结构体设计

响应结构体包含结果列表、分页元数据、性能指标以及错误信息。性能指标字段用于客户端感知尾延迟和服务端排查慢查询。

message SearchResponse {
  // 搜索结果条目列表
  repeated SearchResult results = 1;

  // 分页信息
  Pagination pagination = 2;

  // 性能元数据
  PerformanceMeta performance = 3;

  // 业务错误码,0 表示成功
  int32 code = 4;

  // 人类可读的错误消息
  string message = 5;
}

message SearchResult {
  string doc_id = 1;
  string title = 2;
  string snippet = 3;
  double score = 4;
  map<string, string> fields = 5;
}

message Pagination {
  int32 page = 1;
  int32 page_size = 2;
  int32 total_results = 3;
  int32 total_pages = 4;
}

message PerformanceMeta {
  // 服务端实际处理耗时(毫秒)
  int64 server_cost_ms = 1;

  // 命中缓存层数:0=未命中,1=本地缓存,2=分布式缓存
  int32 cache_hit_level = 2;

  // 参与计算的 shard 数量
  int32 shard_count = 3;
}
注意:响应中的 code 和 message 用于业务层错误,不要与 gRPC 状态码混淆。gRPC 状态码只表示传输层成功/失败,业务错误必须通过响应体传递。

4.4 错误码规范

错误码采用三段式数字编码:服务域(2位) + 错误类型(2位) + 具体错误(2位)。例如 010201 表示搜索服务(01)的查询错误(02)中的查询超时(01)。

错误码 名称 说明 建议客户端行为
000000 SUCCESS 请求成功 正常展示结果
010101 QUERY_EMPTY 查询字符串为空 提示用户输入
010102 QUERY_TOO_LONG 查询超过 1024 字符 截断后重试
010201 QUERY_TIMEOUT 服务端处理超时 降级展示或重试
010301 SHARD_UNAVAILABLE 部分分片不可用 展示部分结果
019999 INTERNAL_ERROR 服务端未知错误 展示错误页
// Go 错误码常量定义
const (
    CodeSuccess           = 0
    CodeQueryEmpty        = 10101
    CodeQueryTooLong      = 10102
    CodeQueryTimeout      = 10201
    CodeShardUnavailable  = 10301
    CodeInternalError     = 19999
)

4.5 接口契约与尾延迟的关系

明确的接口契约是尾延迟治理的第一道防线:

  • 超时字段传递:请求中的 timeout_ms 让服务端提前感知客户端容忍度,避免无限制等待。
  • 性能元数据回传:响应中的 server_cost_ms 和 cache_hit_level 帮助客户端判断延迟来源。
  • 错误码分级:客户端根据错误码决定是否重试、降级或报错,避免盲目重试加剧尾延迟。
接口契约与尾延迟治理关系图 客户端 发送 SearchRequest 搜索服务端 返回 SearchResponse timeout_ms / trace_id server_cost_ms / code 尾延迟治理决策 重试 / 降级 / 熔断 / 超时控制 契约字段如何影响尾延迟? timeout_ms → 服务端提前终止慢查询,避免资源堆积 server_cost_ms → 客户端感知真实延迟,辅助熔断阈值计算

4.6 契约演进策略

接口契约一旦发布,必须遵循向后兼容原则。以下策略确保服务端升级不影响已有客户端:

  • 字段只能新增,不能删除或修改类型。新增字段使用 optional 或设置合理的默认值。
  • 错误码只能追加,不能回收。废弃的错误码保留定义,标记为 deprecated。
  • RPC 方法签名不变。如需新语义,定义新的 RPC 方法(如 SearchV2),而不是修改现有方法。
实战建议:在 .proto 文件中使用 reserved 关键字预留字段编号和枚举值,防止未来冲突。每次修改后运行 buf breaking 检查兼容性。
接口契约演进策略流程 发布 v1 契约 新增字段 / 枚举值 标记 optional / 默认值 运行兼容性检查 buf breaking / 单元测试 通过? 是 发布 否 回退设计,重新评审