Aegis 分散式鎖服務 (Distributed Lock Service)
Aegis 是一個基於 Go 語言開發的輕量級分散式鎖服務 (Distributed Lock Service),提供簡單易用的 RESTful API 來進行鎖的獲取、釋放與展期。系統設計支援可插拔的儲存後端,目前已實作 Redis、DynamoDB、PostgreSQL 與 MySQL 四種儲存引擎。
🌟 主要功能
- 標準 HTTP API:提供單一且標準的 RESTful API 溝通介面。
- 多儲存後端支援:
- Redis:適用於高併發、低延遲的快取鎖場景。
- DynamoDB:適用於需要高可用性與持久化儲存的雲原生環境。
- PostgreSQL:適用於需要關聯式資料庫整合、交易能力與持久化鎖管理的場景。
- MySQL:適用於既有 MySQL 生態系中,需要以資料庫實作分散式鎖的場景。
- 鎖生命週期管理:支援獲取鎖 (Acquire)、釋放鎖 (Release) 以及鎖展期 (Extend/Renew)。
🚀 快速開始
使用 Docker Compose 啟動 Redis 與 Aegis 服務
docker-compose up -d redis aegis
docker-compose.yml 會自動掛載 config.docker.yaml,讓 Aegis 在容器內使用 redis:6379 連線。
啟動依賴服務
or 啟動單一依賴服務 Redis
docker-compose up -d redis
執行服務
使用 Go 執行服務
go run main.go
預設伺服器會依照設定連接至對應的 backend 並在本地特定的 port 啟動監聽。
若要切換後端,可在 config.yaml 中設定 backend 為 redis、dynamodb、postgres 或 mysql;使用 SQL 後端時,再搭配設定 sql.dialect 與 sql.dsn。
使用 Docker Image 啟動服務
docker pull ghcr.io/walnut-almonds/aegis:latest
docker run -d -p 8080:8080 --name aegis ghcr.io/walnut-almonds/aegis:latest
分別啟動 Redis 與 Aegis 容器
當需要獨立啟動 Redis 與 Aegis 容器時,需要確保它們在同一 Docker 網路上。
先在本地建置最新 image(確保可使用 REDIS_ADDR):
docker build -t aegis-server:local .
方式 1:創建共同網路(推薦)
# 建立 Docker 網路
docker network create aegis-net
# 啟動 Redis
docker run -d --name redis --network aegis-net redis:7-alpine
# 啟動 Aegis(透過環境變數指定 Redis 位址)
docker run -d -p 8080:8080 -p 50051:50051 \
--network aegis-net \
-e REDIS_ADDR=redis:6379 \
--name aegis aegis-server:local
方式 2:透過主機轉發連線
# 啟動 Redis(port 6379)
docker run -d -p 6379:6379 --name redis redis:7-alpine
# 啟動 Aegis(從容器連回主機上的 Redis)
docker run -d -p 8080:8080 -p 50051:50051 \
-e REDIS_ADDR=host.docker.internal:6379 \
--name aegis aegis-server:local
Linux 若無 host.docker.internal,可加上 --add-host host.docker.internal:host-gateway。
方式 3:指定 Redis 容器 IP
# 啟動 Redis,記下容器 IP
docker run -d --name redis redis:7-alpine
REDIS_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis)
# 啟動 Aegis,直接連接到 Redis 容器 IP
docker run -d -p 8080:8080 -p 50051:50051 \
-e REDIS_ADDR=${REDIS_IP}:6379 \
--name aegis aegis-server:local
📚 API 文件
HTTP API 由 Huma 自動註冊並產生 OpenAPI 文件。
- OpenAPI JSON:
/openapi.json
- OpenAPI YAML:
/openapi.yaml
- 互動文件 UI:
/docs
若要匯出可版控的靜態 OpenAPI 檔案:
go run . --export-openapi --openapi-out openapi/openapi.yaml
所有的 API 請求皆採用 JSON 格式,核心的資料模型如下:
{
"key": "resource:123", // 鎖的唯一鍵值 (Resource ID)
"token": "uuid-xxxx", // 用戶端/擁有者的唯一識別碼
"ttl_sec": 30 // 鎖定存活時間 (秒)
}
1. 獲取鎖 (Acquire)
- Method:
POST /api/v1/lock/acquire
- 說明: 嘗試獲取指定
key 的鎖。若鎖已被其他 token 持有,將回傳 409 Conflict。
- Success Response:
201 Created
2. 釋放鎖 (Release)
- Method:
POST /api/v1/lock/release
- 說明: 使用獲取鎖時的
token 來釋放鎖。若 token 不符或鎖已過期,則無法刪除(避免誤刪別人的鎖)。
- Success Response:
200 OK
3. 鎖展期 (Extend)
- Method:
POST /api/v1/lock/extend
- 說明: 當現有操作尚未完成,但鎖即將過期時,可以帶上正確的
token 來延長 ttl_sec。
- Success Response:
200 OK
4. 等待鎖 (WaitLock / Long-polling)
- Method:
POST /api/v1/lock/wait
- 說明: 嘗試取得鎖;若目前被持有,伺服器會在
wait_timeout_sec 內持續等待並重試(long-polling),直到取得鎖或逾時。
- Success Response:
201 Created
- Timeout Response:
408 Request Timeout
請求範例:
{
"key": "resource:123",
"token": "uuid-xxxx",
"ttl_sec": 30,
"wait_timeout_sec": 10,
"poll_interval_ms": 100
}
🛠️ 開發與測試
本專案測試整合了 testcontainers-go 與 miniredis:
執行單元測試 (Unit Tests)
go test -v ./... ./sdk/go/...
執行 Benchmark 測試
Benchmark 測試涵蓋 Redis、PostgreSQL、MySQL 三個後端,分為循序吞吐量、高並發吞吐量、鎖競爭三種情境。完整的測試結果與分析請參閱 benchmark.md。
跑全部 Benchmark
go test -bench=. -benchtime=3s -benchmem ./...
只跑指定後端
# 只跑 Redis
go test -bench=^BenchmarkRedis_ -benchtime=5s -benchmem ./...
# 只跑 PostgreSQL
go test -bench=^BenchmarkPostgres_ -benchtime=5s -benchmem ./...
# 只跑 MySQL
go test -bench=^BenchmarkMySQL_ -benchtime=5s -benchmem ./...
只跑特定情境
# 高並發吞吐量(各後端 goroutine=1/4/16/64)
go test -bench=Concurrent -benchtime=3s -benchmem ./...
# 鎖競爭率(多 goroutine 搶同一把鎖)
go test -bench=Contention -benchtime=3s -benchmem ./...
延遲百分位測試(p50 / p95 / p99)
go test -run=TestLatencyPercentile -v ./...
加上 -short 旗標可略過延遲測試:go test -short ./...
執行 Lint 檢查
確保已安裝 golangci-lint,再執行以下指令:
golangci-lint run ./...
若需同時對 SDK 模組進行 lint:
golangci-lint run ./...
cd sdk/go && golangci-lint run ./...
or
# (似乎在某些環境下不支援)
golangci-lint run ./... sdk/go/...
授權
本專案採用 Apache License 2.0 授權 - 詳見 LICENSE 檔案