查询接口设计:HTTP/gRPC 双协议支持
基于当前实现梳理 Seastar Log Engine 的查询服务,包括 HTTP / gRPC 接口、状态字段、记录查询语义与 Prometheus 暴露方式。
为什么需要独立查询服务
Seastar Log Engine 的写入链路和查询链路是分开的。
写入侧关注:
- 路由
- 批量
- DMA 对齐写入
- rotate / archive / checkpoint
查询侧关注:
- 当前路由配置是否符合预期
- active log / archive 是否能被正确读取
- 读路径是否出现损坏 segment、坏 gzip、恢复回退等问题
- 运维侧能否通过状态接口和指标快速判断系统健康度
因此项目当前提供了独立 log_engine_query_server。
当前实际提供的接口
HTTP
当前 HTTP 端点共有 4 个:
GET /healthz
GET /v1/status
GET /v1/route
GET /v1/records
gRPC
当前 proto 只定义了 3 个 unary RPC:
service QueryService {
rpc GetStatus(Empty) returns (StatusReply);
rpc Route(RouteRequest) returns (RouteReply);
rpc QueryRecords(QueryRecordsRequest) returns (QueryRecordsReply);
}
这里要明确两点:
- 当前没有 gRPC streaming 接口。
- 当前没有额外的查询缓存、游标分页、服务端索引层或访问控制层实现。
这些都可以是后续演进方向,但不属于当前仓库事实。
查询服务启动方式
典型启动命令如下:
./build/log_engine_query_server \
--config ./config/engine.conf \
--routing-strategy consistent_hashing \
--routing-virtual-nodes 256 \
--http-address 0.0.0.0 \
--http-port 18080 \
--grpc-address 0.0.0.0 \
--grpc-port 19090 \
--metrics-address 0.0.0.0 \
--metrics-port 19181
这里的 Prometheus 指标不是作为 /metrics 挂在主 HTTP 服务上,而是通过单独的 metrics server 暴露在 metrics-port。
1. Status 接口
HTTP
curl http://127.0.0.1:18080/v1/status
gRPC
./build/log_engine_query_client --target 127.0.0.1:19090 --method status
当前返回的信息
状态接口会返回几类信息:
-
路由配置
routing_strategyrouting_shardsrouting_virtual_nodesring_size
-
存储路径
log_dirarchive_dirshard_file_prefix
-
Reader 统计
segments_readarchive_segments_readactive_segments_readrecords_returnedcorrupted_segmentscorrupted_linesgzip_read_errors
-
LogManager 统计
rotate_operationscheckpoint_write_successescheckpoint_write_failuresrecovery_fallbacksrecovery_fallback_incomplete_checkpointrecovery_fallback_stale_checkpointgzip_archive_successesgzip_archive_failures
-
健康度汇总
healthhealth_reasonhealth_reason_basisrecovery_fallback_reasonhealth_recent_errors
health 字段的真实含义
当前实现里健康度取值不是 healthy,而是:
okdegradedunhealthy
无异常时也不是 no_recent_errors,而是:
health = "ok"health_reason = "none"health_reason_basis = "none"
这来自当前 health_monitor 的状态机逻辑。
2. Route 接口
HTTP
curl "http://127.0.0.1:18080/v1/route?key=route-a"
gRPC
./build/log_engine_query_client --target 127.0.0.1:19090 --method route --route-key route-a
返回字段
{
"route_key": "route-a",
"shard": 1,
"hash": 123456789,
"token": 987654321,
"used_local_fallback": false
}
字段含义:
shard当前 route key 应该落到的 shardhashroute key 的稳定哈希值token一致性哈希命中的 ring token;hash_modulo下通常等于 hashused_local_fallback是否命中了空 key 本地回退路径
一个容易忽略的细节
query_server 的 route 查询使用的是独立 ShardRouter 配置,并不依赖正在运行的 writer 实例。
默认情况下:
routing-shards=0会回落到当前seastar::smp::count
所以 route 查询是一个“按当前配置推导路由结果”的接口,而不是写入面实时状态探针。
3. Records 接口
HTTP
curl "http://127.0.0.1:18080/v1/records?limit=10"
gRPC
./build/log_engine_query_client --target 127.0.0.1:19090 --method records --limit 10 --include-archive true
支持的查询参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
shard | int | 否 | 指定 shard |
seq_from | uint64 | 否 | 起始 sequence |
seq_to | uint64 | 否 | 结束 sequence |
time_from | string | 否 | 起始时间字符串 |
time_to | string | 否 | 结束时间字符串 |
limit | int | 否 | 默认 100 |
include_archive | bool | 否 | 默认 true |
这里要特别强调两点:
limit在当前实现里不是必填,默认值为100。time_from/time_to当前只是对记录里的timestamp字符串做比较,不是 RFC3339 解析器。
时间过滤的真实语义
项目当前记录时间格式来自 writer:
YYYY-MM-DD HH:MM:SS.ffffff
例如:
2026-05-15 10:30:00.123456
因此在实际使用中,time_from/time_to 最安全的写法应与记录格式一致,例如:
curl "http://127.0.0.1:18080/v1/records?time_from=2026-05-15 10:00:00.000000&time_to=2026-05-15 11:00:00.000000&limit=100"
如果直接传 RFC3339 形式的 2026-05-15T10:00:00Z,当前实现并不会做时间解析或时区转换。
返回字段
每条记录返回:
crctimestampshardhas_sequencesequencelevelpayloadraw_line
其中:
level当前输出为INFO / WARN / ERROR- 若该记录没有结构化 sequence,
has_sequence=false raw_line是原始日志行,适合做问题排查
4. 读路径的容错语义
当前查询链路不是“遇到坏数据直接失败”,而是尽量保守读取。
损坏处理策略
-
普通 segment 中出现坏行
- 截断到第一条坏行之前
- 当前 segment 记一次损坏
- 继续读后续 segment
-
gzip archive 读错
- 跳过整个 gzip segment
- 计入
gzip_read_errors - 继续读后续 segment
-
文件在读取过程中消失
- 视为并发 cleanup
- warn,但不当作损坏
这个语义和当前 log_reader.cc 的实现一致,目的是避免“一段坏 archive 把整条查询链路拖死”。
5. Proto 结构
当前 proto 里最需要注意的是 StatusReply 字段已经比较完整:
message StatusReply {
string routing_strategy = 1;
uint32 routing_shards = 2;
uint64 routing_virtual_nodes = 3;
uint64 ring_size = 4;
string log_dir = 5;
string archive_dir = 6;
string shard_file_prefix = 7;
uint64 reader_segments_read = 8;
uint64 reader_archive_segments_read = 9;
uint64 reader_active_segments_read = 10;
uint64 reader_records_returned = 11;
uint64 reader_corrupted_segments = 12;
uint64 reader_corrupted_lines = 13;
uint64 reader_gzip_read_errors = 14;
string health = 15;
...
}
换句话说,当前 gRPC status 已经不是一个只有路由配置的轻量接口,而是和 HTTP /v1/status 对齐的综合状态接口。
6. Prometheus 指标暴露
查询服务启动时会注册:
log_engine_reader_*log_engine_health_*
如果同时还有 writer 进程暴露 writer metrics,则整体观测可拼出:
- 写入吞吐和背压情况
- 查询返回量与读路径损坏情况
- 最近 5 分钟健康窗口内的异常分布
典型查看方式:
curl http://127.0.0.1:19181/metrics
7. 当前接口边界
为了避免把“设计想法”误写成“现状”,这里把尚未落地的能力明确列出来:
- 没有 gRPC streaming query
- 没有内建分页 cursor
- 没有查询结果缓存
- 没有索引加速层
- 没有内建 IP 白名单 / ACL
- 没有查询审计日志
- 没有统一的 query rate limit
这些都值得做,但不属于当前代码。
总结
当前 query_server 的定位很明确:
- 暴露 route / records / status 三类核心能力
- 保持 HTTP 与 gRPC 的结果结构一致
- 用保守读语义处理坏 segment / 坏 gzip
- 通过独立 metrics 端口提供 Reader 与 Health 指标
如果把它描述成一句话:
它不是一个“重型日志检索系统”,而是当前单机日志引擎的运维排障与一致性观测接口。
下一篇:《故障注入与容错设计:系统健壮性验证》