在项目宪章中新增第九条原则"数据库设计原则",明确禁止使用数据库外键约束和ORM关联标签。 主要变更: - 新增原则IX:数据库设计原则(Database Design Principles) - 强制要求:数据库表不得使用外键约束 - 强制要求:GORM模型不得使用ORM关联标签(foreignKey、hasMany等) - 强制要求:表关系必须通过ID字段手动维护 - 强制要求:关联数据查询必须显式编写,避免ORM魔法 - 强制要求:时间字段由GORM处理,不使用数据库触发器 设计理念: - 提升业务逻辑灵活性(无数据库约束限制) - 优化高并发性能(无外键检查开销) - 增强代码可读性(显式查询,无隐式预加载) - 简化数据库架构和迁移流程 - 支持分布式和微服务场景 版本升级:2.3.0 → 2.4.0(MINOR)
6.9 KiB
6.9 KiB
数据持久化与异步任务处理集成 - 架构说明
功能编号: 002-gorm-postgres-asynq
更新日期: 2025-11-13
系统架构概览
┌─────────────────┐
│ Load Balancer │
│ (Nginx) │
└────────┬────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌─────────▼────────┐ ┌───▼──────────┐ ┌──▼─────────┐
│ API Server 1 │ │ API Server 2 │ │ API N │
│ (Fiber:8080) │ │(Fiber:8080) │ │(Fiber:8080)│
└─────────┬────────┘ └───┬──────────┘ └──┬─────────┘
│ │ │
└──────────────┼───────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼────┐ ┌───▼───────┐ ┌───▼─────────┐
│PostgreSQL│ │ Redis │ │Worker Cluster│
│ (Primary)│ │ (Queue) │ │ (Asynq) │
└────┬─────┘ └───────────┘ └─────────────┘
│
┌──────▼──────┐
│ PostgreSQL │
│ (Replica) │
└─────────────┘
双服务架构
API 服务 (cmd/api/)
职责:
- HTTP 请求处理
- 业务逻辑执行
- 数据库 CRUD 操作
- 任务提交到队列
特点:
- 无状态设计,支持水平扩展
- RESTful API 设计
- 统一错误处理和响应格式
- 集成认证、限流、日志中间件
Worker 服务 (cmd/worker/)
职责:
- 从队列消费任务
- 执行后台异步任务
- 任务重试管理
- 幂等性保障
特点:
- 多实例部署,自动负载均衡
- 支持多优先级队列
- 优雅关闭(等待任务完成)
- 可配置并发数
分层架构
Handler 层 (internal/handler/)
职责: HTTP 请求处理
- 请求参数验证
- 调用 Service 层
- 响应封装
- 错误处理
设计原则:
- 不包含业务逻辑
- 薄层设计
- 统一使用 pkg/response/
Service 层 (internal/service/)
职责: 业务逻辑
- 业务规则实现
- 跨模块协调
- 事务管理
- 错误转换
设计原则:
- 可复用的业务逻辑
- 支持依赖注入
- 使用 pkg/errors/ 错误码
Store 层 (internal/store/)
职责: 数据访问
- CRUD 操作
- 查询构建
- 事务封装
- 数据库交互
设计原则:
- 只返回 GORM 原始错误
- 不包含业务逻辑
- 支持事务传递
Model 层 (internal/model/)
职责: 数据模型定义
- 实体定义
- DTO 定义
- 验证规则
数据流
CRUD 操作流程
HTTP Request
↓
Handler (参数验证)
↓
Service (业务逻辑)
↓
Store (数据访问)
↓
PostgreSQL
↓
Store (返回数据)
↓
Service (转换)
↓
Handler (响应)
↓
HTTP Response
异步任务流程
HTTP Request (任务提交)
↓
Handler
↓
Service (构造 Payload)
↓
Queue Client (Asynq)
↓
Redis (持久化)
↓
Worker (消费任务)
↓
Task Handler (执行任务)
↓
PostgreSQL/外部服务
核心设计决策
1. 为什么使用 GORM?
优势:
- Go 生态最成熟的 ORM
- 自动参数化查询(防 SQL 注入)
- 预编译语句缓存
- 软删除支持
- 钩子函数支持
2. 为什么使用 golang-migrate?
理由:
- 版本控制: 每个迁移有版本号
- 可回滚: up/down 脚本
- 团队协作: 迁移文件可 review
- 生产安全: 明确的 SQL 语句
不用 GORM AutoMigrate:
- 无法回滚
- 无法删除列
- 生产环境风险高
3. 为什么使用 Asynq?
优势:
- 基于 Redis,无需额外中间件
- 任务持久化(系统重启自动恢复)
- 自动重试(指数退避)
- Web UI 监控(asynqmon)
- 分布式锁支持
关键技术实现
幂等性设计
方案 1: Redis 锁
key := constants.RedisTaskLockKey(requestID)
if exists, _ := rdb.SetNX(ctx, key, "1", 24*time.Hour).Result(); !exists {
return nil // 跳过重复任务
}
方案 2: 数据库唯一约束
CREATE UNIQUE INDEX idx_order_id ON tb_order(order_id);
方案 3: 状态机
if order.Status != "pending" {
return nil // 状态不匹配,跳过
}
事务管理
func (s *Store) Transaction(ctx context.Context, fn func(*Store) error) error {
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
txStore := &Store{db: tx, logger: s.logger}
return fn(txStore)
})
}
连接池配置
PostgreSQL:
- MaxOpenConns: 25(最大连接)
- MaxIdleConns: 10(空闲连接)
- ConnMaxLifetime: 5m(连接生命周期)
Redis:
- PoolSize: 10
- MinIdleConns: 5
扩展性设计
水平扩展
API 服务:
- 无状态设计
- 通过负载均衡器分发请求
- 自动扩缩容(K8s HPA)
Worker 服务:
- 多实例连接同一 Redis
- Asynq 自动负载均衡
- 按队列权重分配任务
数据库扩展
读写分离:
Primary (写) → Replica (读)
分库分表:
- 按业务模块垂直分库
- 按数据量水平分表
监控与可观测性
健康检查
- PostgreSQL Ping
- Redis Ping
- 连接池状态
日志
- 访问日志: 所有 HTTP 请求
- 错误日志: 错误详情
- 慢查询日志: > 100ms
- 任务日志: 提交/执行/失败
指标(建议)
- API 响应时间
- 数据库连接数
- 任务队列长度
- 任务失败率
安全设计
数据安全
- SQL 注入防护(GORM 参数化)
- 密码哈希(bcrypt)
- 敏感字段不返回(
json:"-")
配置安全
- 生产环境使用环境变量
- 数据库 SSL 连接
- Redis 密码认证
性能优化
数据库
- 适当索引
- 批量操作
- 分页查询
- 慢查询监控
任务队列
- 优先级队列
- 并发控制
- 超时设置
- 幂等性保障