rqlite不能直接当作主数据库服务,因其设计取舍导致不支持写事务(commit/rollback)、无用户权限系统、不兼容join或子查询等复杂sql,仅适用于配置中心、元数据同步等读多写少且接受最终一致性的场景。

为什么不能直接用 rqlite 当作主数据库服务?
它不支持写事务(COMMIT/ROLLBACK)、没有用户权限系统、不兼容 JOIN 或子查询等复杂 SQL——这些不是 bug,是设计取舍。如果你的 Go 服务依赖 ACID 或需要高并发写入,rqlite 只适合做配置中心、元数据同步、审计日志归档这类「读多写少 + 最终一致」场景。
常见错误现象:rqlite 节点间同步延迟导致 SELECT 立即查不到刚 INSERT 的记录;客户端直连 follower 节点执行写操作返回 500 error: not leader。
- 始终通过 leader 节点写入:用
GET /status查is_leader,或更稳妥地走负载均衡器(如 nginx)配proxy_next_upstream http_500自动重试 - 读请求可发任意节点,但需接受最终一致性 —— 若业务要求强一致读,必须显式加
?consistent=true查询参数(会走 raft log 提交后才返回) - Go 客户端别用原生
database/sql驱动:它假设后端支持连接池、预编译、事务,而rqlite是 HTTP 接口,要用net/http直调或封装简单 client
Go 中调用 rqlite 的最小可行封装
不用第三方 ORM 或重型 SDK,几行代码就能跑通。核心是把 SQL 请求转成 JSON POST,解析 {"results": [...]} 结构。
示例:插入一条配置记录并检查是否成功
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
func execRqlite(url, stmt string) error {
body, _ := json.Marshal(map[string]interface{}{"statements": []string{stmt}})
resp, err := http.Post(url+"/db/execute", "application/json", bytes.NewBuffer(body))
if err != nil {
return err
}
defer resp.Body.Close()
var result struct {
Results []struct {
LastInsertID int `json:"last_insert_id"`
RowsAffected int `json:"rows_affected"`
} `json:"results"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return err
}
if len(result.Results) == 0 || result.Results[0].RowsAffected == 0 {
return errors.New("no rows affected")
}
return nil
}
// 使用
err := execRqlite("http://localhost:4001", "INSERT INTO config (key, value) VALUES ('timeout', '30')")
-
/db/execute用于写操作,/db/query用于读操作(GET 或 POST 均可) - 批量语句用
"statements": ["...", "..."]数组,但注意:所有语句在同一个 raft log entry 中提交,不保证原子性(某条失败其余仍可能成功) - 返回的
last_insert_id是自增主键值,但仅当表有INTEGER PRIMARY KEY时有效;无主键表返回0
rqlite 集群启动与 Go 服务容错联动
单节点起手容易,但生产必须考虑 leader 切换、网络分区、节点宕机。Go 服务不能假定某个 rqlite 地址永远可用。
- 启动集群示例(3 节点):
rqlited -http-addr=10.0.1.10:4001 -raft-addr=10.0.1.10:4002 ~/node1,另两节点用-join http://10.0.1.10:4001加入 - Go 里维护一个节点地址 slice,每次请求前随机选一个,失败则轮询下一个;不要用固定 fallback 顺序,避免雪崩
- 对
503 Service Unavailable(raft 不可用)和400 Bad Request(SQL 错误)要区分处理:前者重试,后者直接返回错误 - 务必设置
http.Client.Timeout(建议 ≤5s),否则 raft 同步卡住会拖垮整个 Go HTTP handler
哪些 SQL 功能在 rqlite 里实际能用?
它基于 SQLite,但只暴露了子集。别被文档里「支持标准 SQL」误导,很多语法解析会直接报错。
- 支持:
CREATE TABLE(含PRIMARY KEY、NOT NULL)、INSERT/UPDATE/DELETE、SELECT(带WHERE、ORDER BY、LIMIT)、PRAGMA table_info - 不支持:
ALTER TABLE ... ADD COLUMN(SQLite 3.35+ 才支持,而 rqlite 默认绑旧版)、WITH RECURSIVE、任何触发器或视图定义、FOREIGN KEY约束(虽能建表但不生效) - 字符串函数只有
lower()/upper()/length(),没有substring()或正则;时间函数只认datetime('now')这种字面量,不支持strftime()
真正麻烦的是 schema 变更 —— 没法在线加字段,只能导出数据、重建表、再导入。这决定了它只适合 schema 稳定、变更极少的服务。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










