Kratos不支持开箱即用的读写分离,因其设计哲学是专注通信与治理、不侵入数据访问层,既无read/write配置项,也不解析SQL或自动路由;推荐用GORM自定义Resolver实现主从分发,并需手动处理强一致性场景与连接池差异。

Kratos 没有内置读写分离能力,必须靠外部数据源或中间件配合实现。 它本身是轻量级微服务框架,数据库层完全交由开发者选型(如 sqlx、gorm、ent),不提供自动 SQL 解析路由或多数据源切换逻辑。
为什么 Kratos 不支持开箱即用的读写分离
Kratos 的设计哲学是“专注通信与治理,不侵入数据访问层”。它不封装 ORM,也不接管 database/sql 的连接池管理。这意味着:
- 没有
read/write配置项,config/database.yaml里无法像 Laravel 那样写read: { host: ... } - 没有自动 SQL 类型识别(比如检测
SELECT就切从库),框架不会帮你解析语句或重写连接 - 事务内强制走主库、读写一致性兜底等逻辑,需你自行控制
推荐方案:用 GORM + 自定义 Resolver 实现路由
GORM 是 Kratos 生态中最常搭配的 ORM,它支持 Resolver 接口,可动态返回不同连接。这是目前最可控、侵入性较低的方式。
关键步骤如下:
- 在
internal/data/data.go中初始化两个*gorm.DB实例:masterDB和slaveDB,分别指向主库和从库地址 - 实现
gorm.Resolve接口,例如:type DBResolver struct { master *gorm.DB slave *gorm.DB } func (r *DBResolver) Resolve(ctx context.Context, call gorm.Call) gorm.ConnPool { if call == gorm.Read { return r.slave.Statement.ConnPool } return r.master.Statement.ConnPool } - 将该 resolver 注册到
masterDB:masterDB = masterDB.Clauses(clause.Resolver{Resolver: &DBResolver{...}}) - 后续所有通过
masterDB发起的查询(包括Find、First)会按call类型自动分发
⚠️ 注意:call == gorm.Read 并非 100% 精确——GORM 在 Count、Joins、预加载等场景也可能标记为 Read,但 UPDATE 或事务内操作一定走 Write,这点可靠。
强一致性场景必须手动指定主库连接
主从延迟下,“刚写完立刻查”极易读到旧数据。Kratos 场景中常见于订单创建后立即查状态、用户资料更新后刷新展示等。
此时不能依赖自动路由,而应显式使用主库实例:
- 不要写:
userRepo.FindByID(ctx, id)(可能走从库) - 要写:
data.masterDB.WithContext(ctx).First(&u, id)或封装一个FindOnMaster()方法 - 事务内所有操作天然绑定主库,无需额外处理:
tx := data.masterDB.Begin()
更稳妥的做法是把「强读」逻辑下沉到 Repository 层,并加注释说明原因,避免后续维护者误用。
别忽略连接池与健康检查的差异
主库和从库的负载特征不同,连接池参数不能共用:
- 主库:并发低、事务长、连接数少,建议
SetMaxOpenConns(20)、SetMaxIdleConns(10) - 从库:并发高、短连接多,建议
SetMaxOpenConns(100)、SetMaxIdleConns(50) - 从库故障时,
slaveDB查询失败不应导致整个接口 500;需加errors.Is(err, gorm.ErrRecordNotFound)之外的兜底,比如降级为主库重试(仅限非核心读)或返回缓存值
真实线上环境里,连接健康探测比路由逻辑更难调试——建议在启动时对每个 DB 实例执行 PING,并在日志中标记主/从连接池状态,否则故障时很难区分是路由错了,还是从库连不上。


















