Skip to content

Go 后端面试复盘:语言、并发与服务实践 · 23. 工程化与项目设计至24. 微服务、Kratos 与 gRPC ​

返回分篇目录

前置:Go 系统学习,并完成一个HTTP 工程练习。本页用于面试前查漏补缺,不按章节替代语言课程。

23. 工程化与项目设计 ​

23.1 Go 项目目录应该怎么设计? ​

没有唯一标准。常见思路:

text
project/
├── api/                 # Proto/OpenAPI 等协议
├── cmd/
│   └── server/          # 程序入口
├── configs/             # 配置模板
├── internal/
│   ├── biz/             # 领域/业务逻辑
│   ├── data/            # 数据访问实现
│   ├── service/         # transport service 实现
│   ├── server/          # HTTP/gRPC server 组装
│   └── conf/            # 配置生成代码
├── pkg/                 # 确实要供外部项目复用的包
├── test/                # 集成测试资源
├── go.mod
└── Makefile

原则:

  • 按变化原因和依赖方向组织。
  • internal 防止外部包误用。
  • 不要创建没有边界价值的 util 大杂烩。
  • 避免过早拆成大量小包造成循环依赖和导航困难。

23.2 package 设计原则是什么? ​

  • 名称简短、明确,避免 util、common、base。
  • 包围绕单一概念组织。
  • API 尽量小。
  • 减少全局状态。
  • 避免 import cycle。Modules 的 require 声明模块依赖,不会自动拆解包循环导入;调整职责与依赖方向,见模块实践。
  • 错误和接口放在最能表达所有权的位置。

23.3 Go Modules 解决什么? ​

  • 模块版本管理。
  • 最小版本选择。
  • 校验依赖内容。
  • 可重复构建。
  • 支持 replace、exclude 等开发能力。

常用命令:

bash
go mod init example.com/project
go mod tidy
go mod download
go list -m all
go mod graph
go mod why module/path

go mod tidy 会根据所有构建标签相关包和测试依赖调整 go.mod/go.sum,提交前应检查 diff。

23.4 go.sum 是什么? ​

记录模块内容和 go.mod 文件的哈希,用于验证下载内容的一致性。它不是“当前真正使用所有依赖”的简单列表,也不应随意删除。

23.5 依赖注入有哪些方式? ​

  • 构造函数手动注入:最直观,推荐默认选择。
  • 代码生成式依赖注入:大型项目减少装配代码。
  • 运行时容器:灵活但类型和调用链可能更隐晦。
go
func NewUserService(repo UserRepo, logger *slog.Logger) *UserService {
	return &UserService{repo: repo, logger: logger}
}

避免服务内部随意读取全局单例,这会降低测试性。

23.6 配置管理要注意什么? ​

  • 配置与代码分离。
  • 密钥不要进入仓库或普通日志。
  • 启动时校验必填字段和范围。
  • 明确默认值。
  • 动态配置使用不可变快照和原子发布。
  • 配置变更应有版本、审计和回滚。
  • 不要在业务热路径反复解析配置。

23.7 日志应该如何设计? ​

结构化字段示例:

  • timestamp。
  • level。
  • service、instance、environment。
  • trace_id、request_id。
  • operation。
  • user_id(注意隐私)。
  • duration。
  • error code。

原则:

  • 不记录密码、token、完整银行卡号等。
  • 错误尽量在边界记录一次。
  • 高频路径避免打印巨大对象。
  • 使用日志采样和级别控制。
  • 日志不是指标的替代品。

23.8 API 版本如何设计? ​

  • 兼容新增字段。
  • 不改变已有字段语义。
  • 删除字段先弃用,再经过迁移期。
  • 重大不兼容变化使用新版本。
  • 服务内部与外部 API 版本策略可以不同。
  • 数据库字段、领域模型和 API 模型不要强绑定。

23.9 如何做优雅启动和关闭? ​

启动:

  1. 加载并校验配置。
  2. 初始化日志、指标、追踪。
  3. 连接必要依赖。
  4. 注册服务。
  5. 启动监听。
  6. 上报 readiness。

关闭:

  1. 关闭 readiness/服务发现。
  2. 停止接收新请求。
  3. 等待在途请求。
  4. 停止后台任务。
  5. flush 数据、日志和 trace。
  6. 关闭连接。
  7. 在总超时内退出。

23.10 什么是 readiness 和 liveness? ​

  • readiness:实例是否准备好接收流量。依赖未就绪、正在关闭时应失败。
  • liveness:进程是否需要被重启。不要因短暂下游故障就让 liveness 失败,否则可能引发重启风暴。

24. 微服务、Kratos 与 gRPC ​

24.1 微服务的优缺点是什么? ​

优点:

  • 独立部署和扩缩容。
  • 团队边界清晰。
  • 故障隔离潜力。
  • 可按业务选择技术和资源。

代价:

  • 网络不可靠。
  • 分布式事务和数据一致性复杂。
  • 部署、监控、追踪和治理成本高。
  • 接口演进与跨团队协调复杂。
  • 本地调试和测试链路更长。

不是系统越复杂越应该拆;合理的模块化单体往往是更好的起点。

24.2 Kratos 常见分层如何理解? ​

常见结构:

  • service:实现由 Proto 生成的服务接口,负责传输层参数转换。
  • biz:业务用例、领域逻辑和仓储接口。
  • data:仓储接口实现,访问 DB、Redis、外部服务。
  • server:组装 HTTP/gRPC server 和 middleware。
  • conf:配置结构。

依赖方向大致为:

查看图表代码

关键是 biz 不依赖具体数据库实现,data 实现由 biz 定义的行为接口。

24.3 Proto 字段设计要注意什么? ​

  • 字段号发布后不要复用。
  • enum 0 值用 UNSPECIFIED。
  • 时间优先使用明确时间类型或约定。
  • 金额不要使用 float,使用最小货币单位整数或 decimal 表达。
  • ID 类型跨前端时注意 int64 精度。
  • 列表接口定义分页信息。
  • 错误通过统一错误模型表达。
  • 不要把所有字段都设计成 string。

24.4 gRPC 的四种调用模式是什么? ​

  • Unary:一请求一响应。
  • Server Streaming:一请求多响应。
  • Client Streaming:多请求一响应。
  • Bidirectional Streaming:双向流。

流式调用需要更认真处理:

  • 背压。
  • 心跳和超时。
  • 半关闭。
  • 并发读写约束。
  • 消息大小。
  • 重连后的业务语义。

24.5 gRPC interceptor 有什么用途? ​

类似 HTTP middleware,可统一处理:

  • 日志。
  • tracing。
  • metrics。
  • 认证鉴权。
  • 限流。
  • panic recover。
  • 超时。
  • 错误转换。

顺序很重要。例如 recover 应覆盖后续链路,trace ID 应在日志前注入。

24.6 gRPC deadline 为什么重要? ​

没有 deadline 的调用可能无限等待并占用连接、goroutine 和下游资源。服务应:

  • 客户端传 deadline。
  • 服务端把 context 继续传给 DB 和下游。
  • 为不同操作设置符合 SLO 的预算。
  • 保留上游处理和网络返回余量。

不要每一层都机械设置相同固定超时,可能导致总链路预算失控。

24.7 重试应该放在哪一层? ​

通常由最了解调用语义的一层控制。客户端超时时服务端可能已经成功,重试非幂等操作会重复扣款、创建订单或发奖。要求:

  • 操作幂等,或有幂等键。
  • 仅重试明确的瞬时错误。
  • 指数退避加随机抖动。
  • 设置最大次数和总时间预算。
  • 避免多层同时重试产生乘法放大。

24.8 服务发现和负载均衡是什么关系? ​

  • 服务发现:找到当前可用实例地址。
  • 负载均衡:从地址集合中选择一个实例。

客户端需要处理实例增删、健康状态、连接复用和调用失败。常见算法包括轮询、加权、最少请求、一致性哈希等。

24.9 Kratos middleware 顺序怎么设计? ​

示意顺序:

  1. recovery。
  2. request ID / tracing。
  3. logging。
  4. metrics。
  5. authentication。
  6. authorization。
  7. rate limit。
  8. validation。
  9. business handler。

没有绝对固定顺序。例如希望记录鉴权失败就必须让 logging/metrics 覆盖鉴权中间件。应明确每层输入、输出和异常是否能被外层观察。

24.10 gRPC 与 HTTP 如何共同维护一套接口? ​

使用 Proto 作为接口源:

  1. 定义 request/response 和 RPC。
  2. 通过 HTTP annotation 映射 REST 路由。
  3. 生成 gRPC 和 HTTP 相关代码。
  4. service 实现一套业务入口。
  5. 内部 transport 共享 biz 层。

要避免让 HTTP 特有语义污染领域层,也要对错误码、分页和字段 presence 做统一约定。


继续阅读