删除 ARCHITECTURE.md
This commit is contained in:
143
ARCHITECTURE.md
143
ARCHITECTURE.md
@@ -1,143 +0,0 @@
|
||||
# M:N 架构升级与性能优化说明书
|
||||
|
||||
## 1. 引言与背景
|
||||
|
||||
### 1.1 初始状态
|
||||
|
||||
项目初期,API 密钥(`APIKey`)与密钥组(`KeyGroup`)之间采用的是 **一对多 (1:N)** 的数据模型。在这种设计下,一个 `APIKey` 实体通过 `group_id` 字段直接隶属于一个 `KeyGroup`。
|
||||
|
||||
### 1.2 核心痛点
|
||||
|
||||
随着业务发展,`1:N` 模型的局限性日益凸显:
|
||||
|
||||
1. **数据冗余**: 同一个 API 密钥若需在不同场景(分组)下使用,必须在数据库中创建多条重复的记录,造成存储浪费。
|
||||
2. **管理复杂**: 更新一个通用密钥的状态需要在多个副本之间同步,操作繁琐且容易出错。
|
||||
3. **统计失真**: 无法准确统计一个物理密钥的真实使用情况,因为它在系统中表现为多个独立实体。
|
||||
|
||||
### 1.3 重构目标
|
||||
|
||||
为解决上述问题,我们启动了本次架构升级,核心目标是将数据模型从 `1:N` 重构为 **多对多 (M:N)**。这将允许一个 `APIKey` 实体被多个 `KeyGroup` 共享和复用,从根本上解决数据冗余和管理复杂性的问题。
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心设计:M:N 架构
|
||||
|
||||
### 2.1 新数据模型
|
||||
|
||||
新的 `M:N` 模型通过引入一个中间关联表来解除 `APIKey` 和 `KeyGroup` 之间的直接耦合。
|
||||
|
||||
* **`apikeys` 表**: 只存储 `APIKey` 的唯一信息(如 `api_key` 值、状态、错误统计等),不再包含 `group_id` 字段。
|
||||
* **`key_groups` 表**: 保持不变,存储分组信息。
|
||||
* **`keygroup_apikey_mappings` 表 (新增)**: 这是实现 `M:N` 关系的核心。它包含 `key_group_id` 和 `api_key_id` 两个外键,用于记录 `APIKey` 和 `KeyGroup` 之间的关联关系。
|
||||
|
||||
### 2.2 GORM 模型调整
|
||||
|
||||
我们对 `internal/models/models.go` 中的 GORM 模型进行了相应调整:
|
||||
|
||||
```go
|
||||
// APIKey 模型移除了 GroupID 字段
|
||||
type APIKey struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
APIKey string `gorm:"column:api_key;uniqueIndex;not null"`
|
||||
KeyGroups []*KeyGroup `gorm:"many2many:keygroup_apikey_mappings;"` // 新增 M:N 关系定义
|
||||
}
|
||||
|
||||
// KeyGroup 模型新增了 M:N 关系定义
|
||||
type KeyGroup struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
Name string `gorm:"uniqueIndex;not null"`
|
||||
APIKeys []*APIKey `gorm:"many2many:keygroup_apikey_mappings;"`
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 高性能缓存架构
|
||||
|
||||
为了在 `M:N` 模型下依然保持极高的读写性能,我们设计了一套三层复合缓存结构,存储在 Redis (或内存 Store) 中。
|
||||
|
||||
### 3.1 对象缓存 (HASH)
|
||||
|
||||
* **键 (Key)**: `key:{id}`
|
||||
* **值 (Value)**: `APIKey` 实体的序列化数据 (如 `api_key` 值, `status` 等)。
|
||||
* **作用**: 提供对单个 `APIKey` 详细信息的 `O(1)` 快速访问。
|
||||
|
||||
### 3.2 正向索引 (LIST)
|
||||
|
||||
* **键 (Key)**: `group:{id}:keys:active`
|
||||
* **值 (Value)**: 一个 `APIKey` ID 的列表。
|
||||
* **作用**: 这是 API 代理服务的核心读路径。通过对该列表执行 `LMOVE` (由 `store.Rotate` 封装) 原子操作,我们可以实现高效的、轮询式的 (`Round-Robin`) 活跃密钥选择。
|
||||
|
||||
### 3.3 反向索引 (SET)
|
||||
|
||||
* **键 (Key)**: `key:{id}:groups`
|
||||
* **值 (Value)**: 一个包含所有关联 `KeyGroup` ID 的集合。
|
||||
* **作用**: 这是 `M:N` 架构下的关键设计。当一个 `APIKey` 的状态发生变化时(例如,从 `active` 变为 `disabled`),我们能通过此索引在 `O(1)` 时间内找到所有受影响的 `KeyGroup`,进而精确地更新相关的 `group:{id}:keys:active` 列表,实现了高效的缓存同步。
|
||||
|
||||
这套缓存架构确保了核心的密钥读取操作性能不受影响,同时优雅地解决了 `M:N` 模型下状态变更的“扇出”(fan-out) 更新难题。
|
||||
|
||||
---
|
||||
|
||||
## 4. 分层架构重构
|
||||
|
||||
为适配新的数据和缓存模型,我们对应用各层进行了自底向上的重构。
|
||||
|
||||
### 4.1 Repository 层
|
||||
|
||||
`internal/repository/repository.go` 作为数据访问层,改动最为核心:
|
||||
|
||||
* **查询变更**: 所有原先基于 `group_id` 的查询,现在都改为通过 `JOIN` `keygroup_apikey_mappings` 关联表来完成。
|
||||
* **新增 M:N 操作**: 增加了如 `LinkKeysToGroup`, `UnlinkKeysFromGroup`, `GetGroupsForKey` 等直接操作关联关系的方法。
|
||||
* **缓存逻辑更新**: `LoadAllKeysToStore`, `updateStoreCacheForKey`, `removeStoreCacheForKey` 等缓存辅助函数被重写,以正确地维护上述三种缓存结构。
|
||||
|
||||
### 4.2 Service 层
|
||||
|
||||
`internal/service/*.go` 作为业务逻辑层,主要改动集中在事件处理和状态同步:
|
||||
|
||||
* **事件驱动**: 当一个 `APIKey` 状态变更时,`APIKeyService` 会发布一个 `KeyStatusChangedEvent` 事件。
|
||||
* **状态同步**: `StatsService` 等其他服务会订阅此事件,并根据事件内容更新自身的统计数据(如 `group_stats`),实现了服务间的解耦。
|
||||
* **扇出通知**: `publishStatusChangeEvents` 辅助函数会利用缓存中的“反向索引”(`key:{id}:groups`),为所有受影响的 `KeyGroup` 发布状态变更事件。
|
||||
|
||||
### 4.3 Handler & Router 层
|
||||
|
||||
`internal/handlers/*.go` 和 `internal/router/router.go` 的改动相对较小,主要是适配 `Service` 层接口的变更,确保 API 的输入输出符合新的业务逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据迁移方案
|
||||
|
||||
从 `1:N` 迁移到 `M:N` 需要一次性的数据迁移。我们为此编写了 `scripts/migrate_mn.go` 脚本。
|
||||
|
||||
* **核心逻辑**:
|
||||
1. **创建新表**: 自动创建 `keygroup_apikey_mappings` 关联表。
|
||||
2. **去重 `apikeys`**: 遍历旧的 `apikeys` 表,将重复的 `api_key` 值合并为单个实体,并记录旧 ID 到新 ID 的映射。
|
||||
3. **填充关联表**: 根据旧 `apikeys` 表中的 `group_id` 和 `api_key` 值,在新 `keygroup_apikey_mappings` 表中创建正确的关联关系。
|
||||
4. **清理旧数据**: 删除旧的、有冗余的 `apikeys` 表,并将去重后的新表重命名。
|
||||
* **健壮性**: 脚本被设计为幂等的,并包含多重检查,以防止在已迁移或部分迁移的数据库上重复执行。
|
||||
* **数据库兼容性**: 脚本使用 GORM 的 `clause` 来构建插入语句,避免了因原生 SQL 语法差异导致的数据库兼容性问题(如 SQLite 和 PostgreSQL)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 最终性能与健壮性优化
|
||||
|
||||
在完成核心重构后,我们借鉴业界最佳实践,实施了以下两项关键优化:
|
||||
|
||||
### 6.1 Redis Pipeline 优化
|
||||
|
||||
* **问题**: 在 `LoadAllKeysToStore` 或更新一个 Key 状态时,需要执行多个独立的 Redis 命令,导致多次网络往返(RTT)。
|
||||
* **解决方案**: 我们在 `store` 抽象层引入了 `Pipeliner` 接口。在 `repository` 中,所有多步缓存操作(如 `HSet` + `SAdd` + `LRem` + `LPush`)都被重构为使用 `Pipeline`。这会将多个命令打包成一次网络请求,显著降低了批量操作的延迟。
|
||||
|
||||
### 6.2 异步状态更新与事务重试
|
||||
|
||||
* **问题**:
|
||||
1. 来自管理后台的手动状态更新是同步阻塞的,影响 UI 响应速度。
|
||||
2. 高并发下,数据库事务可能因 `database is locked` 等瞬时错误而失败。
|
||||
* **解决方案**:
|
||||
1. **异步化**: 我们将 `APIKeyService` 中的 `UpdateAPIKey` 方法的核心逻辑移入一个后台 `goroutine`,使其成为一个“即发即忘”的非阻塞操作。
|
||||
2. **事务重试**: 我们在 `repository` 层实现了一个 `executeTransactionWithRetry` 辅助函数,当事务遇到可重试的锁错误时,会进行带**随机抖动 (Jitter)** 的延时重试,极大地增强了系统的健壮性。
|
||||
|
||||
---
|
||||
|
||||
## 7. 总结
|
||||
|
||||
本次架构升级成功地将系统从受限的 `1:N` 模型演进为灵活、可扩展的 `M:N` 模型。通过精心设计的缓存架构、清晰的分层重构、可靠的数据迁移方案以及最终的性能与健壮性优化,新系统不仅解决了旧架构的核心痛点,还在性能、稳定性和可维护性方面达到了更高的标准。
|
||||
Reference in New Issue
Block a user