寫 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/ 可以公开的配置示例
這是一種組織方式,不是所有專案都必須照著建。只有一個簡單介面時,也不需要為每個函式都造一層介面。重點是業務規則不要被某個資料庫客戶端綁死,資源的建立和關閉也要能找到。
上線之前過一遍
我更傾向於按實際請求去檢查:錯誤輸入有沒有被攔住,資料庫慢下來之後有沒有超時,程式退出時有沒有停止接收請求,備份能不能恢復,出了問題能不能從日誌定位。
元件名字認識得再多,如果這些沒有做,服務還是不穩。先把一個小服務做完整,再逐步加元件,更容易理解每一步為什麼要這麼做。