← 返回文章列表

基础设施(golang)

梳理 Go 服务开发涉及的基础设施与常见组件。

写 Go 服务的时候,业务代码只是一部分。数据存在哪里,配置怎么读,出问题之后去哪找日志,这些也得提前想好。这里把这些基础组件放到一条请求里看,方便后面做项目时对照。

基础设施是什么?

简单理解,就是业务背后那些反复会用到的支撑能力。比如做一个任务清单,创建任务是业务;保存任务的数据库、登录状态、日志、部署和备份,则是在支撑业务运行。

基础设施和微服务框架也不是一回事。Kratos、go-zero 主要组织服务代码和接入组件;MySQL、Redis、消息队列是独立的系统。用了框架,还是得理解这些组件各自负责什么。

一个请求会经过哪些地方?

以“新增任务”为例:

浏览器 → HTTP 接口 → 参数校验 → 业务逻辑 → 数据库 → 响应
                           ↓
                    日志、指标、链路记录

最小版本可以只有一个 Go 进程和一个数据库。先把参数校验、错误返回、超时和数据保存做对。缓存、队列、服务发现,要等出现明确需求再接。

常见组件怎么分工?

组件解决什么问题接入时先看什么
配置区分本地、测试和生产环境默认值、必填项、敏感配置来源
数据库保存需要长期保留的数据约束、索引、事务、连接池
缓存减少重复查询,保存可丢失的临时结果过期时间、失效时机、数据一致性
消息队列把较慢的工作从请求中拆出去消费重试、重复消息、积压
服务发现根据服务名找到可用实例健康状态、地址变化、调用超时
可观测性看清服务是否正常,以及哪里出了问题日志、指标、链路之间的关联

数据库:先把正确性做出来

任务标题不能是空的,任务 ID 不能重复,这类约束尽量也放到数据库里。一次业务操作涉及多次写入时,要考虑能否用事务保持一致。MySQL 的事务与锁可以从 InnoDB 文档开始看。

另外,数据库连接不是开得越多越好。连接池上限需要结合数据库容量和请求耗时设置,查询也要有超时。Go 中可以把请求的 context.Context 传给 QueryContext、ExecContext,具体用法见 取消数据库操作。

缓存:别把它当成第二份随便写的数据库

例如任务详情采用“先读缓存,未命中再查数据库”的方式,那么更新任务之后,旧缓存就要处理。先把缓存失效的规则写清楚,再考虑性能。

Redis 的 Go 客户端可以看 go-redis 官方用法。客户端应按应用生命周期复用,别每个 HTTP 请求都重新创建一个。

消息队列:异步以后,问题并没有消失

发通知、生成报告之类的工作,可以先记录任务,再交给后台消费。这里最容易漏掉的是重复执行:同一条消息因为重试被消费两次,业务还能不能正确?一般需要业务 ID、状态约束或其他幂等措施。

如果只是一个低频的小工具,同步执行或者一个简单的后台任务可能就够了。多引入一个组件,也就多一份部署、监控和故障处理工作。

日志、指标和链路,各看什么?

  • 日志:这次请求发生了什么,错误在哪里。
  • 指标:最近错误率有没有上升,耗时和队列长度有没有异常。
  • 链路:一次请求跨了哪些调用,时间主要花在哪里。

这几类信息可以用 request ID 或 trace ID 联系起来。它们的用途可以参考 OpenTelemetry 的信号说明。日志里记录必要的上下文,不要直接把密码、令牌或完整用户数据打出来。

在项目里怎么组织?

一个小项目可以先这样分:

cmd/server/           程序入口,组装依赖,启动和退出
internal/service/     接口层,转换请求和响应
internal/biz/         业务规则
internal/data/        数据库、缓存等访问实现
configs/              可以公开的配置示例

这是一种组织方式,不是所有项目都必须照着建。只有一个简单接口时,也不需要为每个函数都造一层接口。重点是业务规则不要被某个数据库客户端绑死,资源的创建和关闭也要能找到。

上线之前过一遍

我更倾向于按实际请求去检查:错误输入有没有被拦住,数据库慢下来之后有没有超时,程序退出时有没有停止接收请求,备份能不能恢复,出了问题能不能从日志定位。

组件名字认识得再多,如果这些没有做,服务还是不稳。先把一个小服务做完整,再逐步加组件,更容易理解每一步为什么要这么做。

相关记录

Consul:服务注册与发现、微服务框架、Kratos、go-zero。

关于这篇文章的日期

旧日期 2025-01-16 与文章发布记录不符,现按旧站可确认的首次发布日 2026-01-20 整理。实际创作日期尚无法确认。 旧页仅有占位标题,正文于 2026-10-06 补充。