本文档详细说明 QuantMesh 如何解决多实例部署中的两个核心问题:
- 分布式协调:避免多台机器重复下单
- 数据库抽象:统一支持 SQLite、PostgreSQL、MySQL
场景:
时间 T: 价格到达 1850.50
实例 A: 检测到价格,准备下单
实例 B: 同时检测到价格,也准备下单
结果: 两个实例都下单,导致重复订单
风险:
- 超出预期的仓位
- 增加交易成本
- 风险敞口翻倍
场景:
- 单实例使用 SQLite(简单,无需额外服务)
- 多实例需要共享数据库(PostgreSQL/MySQL)
- 代码耦合,难以切换
// 1. 定义锁接口
type DistributedLock interface {
Lock(ctx context.Context, key string, ttl time.Duration) error
TryLock(ctx context.Context, key string, ttl time.Duration) (bool, error)
Unlock(ctx context.Context, key string) error
Extend(ctx context.Context, key string, ttl time.Duration) error
}
// 2. 单实例模式:空实现(无锁开销)
type NopLock struct{}
// 3. 多实例模式:Redis 实现
type RedisLock struct {
client *redis.Client
prefix string
}// 在下单前获取锁
lockKey := fmt.Sprintf("order:%s:%s:%.2f", exchange, symbol, price)
// 尝试获取锁(非阻塞)
acquired, err := lock.TryLock(ctx, lockKey, 5*time.Second)
if err != nil {
return fmt.Errorf("lock error: %w", err)
}
if !acquired {
// 其他实例正在处理,跳过
logger.Debug("价格位 %.2f 已被其他实例锁定,跳过", price)
return nil
}
defer lock.Unlock(ctx, lockKey)
// 执行下单逻辑
order, err := executor.PlaceOrder(req)-
自动过期
- TTL 默认 5 秒
- 避免死锁
- 实例崩溃时自动释放
-
原子操作
-- Lua 脚本保证原子性 if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end
-
唯一标识
- 每个锁有唯一 token
- 只有持有者能释放
- 防止误释放
// 粗粒度:整个交易对(并发度低)
lockKey := fmt.Sprintf("order:%s:%s", exchange, symbol)
// 中粒度:价格区间(推荐)
priceLevel := math.Floor(price / priceInterval) * priceInterval
lockKey := fmt.Sprintf("order:%s:%s:%.2f", exchange, symbol, priceLevel)
// 细粒度:精确价格(并发度高)
lockKey := fmt.Sprintf("order:%s:%s:%.8f", exchange, symbol, price)| 锁实现 | 延迟 | 吞吐量 | 可用性 | 复杂度 |
|---|---|---|---|---|
| 无锁(单实例) | 0ms | 无限制 | ⭐⭐⭐ | ⭐ |
| Redis | 1-3ms | 10k+ ops/s | ⭐⭐⭐⭐ | ⭐⭐ |
| etcd | 5-10ms | 1k+ ops/s | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 数据库 | 10-50ms | 100+ ops/s | ⭐⭐⭐⭐ | ⭐⭐ |
// 1. 定义数据库接口
type Database interface {
SaveTrade(ctx context.Context, trade *Trade) error
GetTrades(ctx context.Context, filter *TradeFilter) ([]*Trade, error)
SaveOrder(ctx context.Context, order *Order) error
// ... 其他方法
}
// 2. GORM 统一实现
type GormDatabase struct {
db *gorm.DB
}
// 3. 根据配置创建实例
func NewDatabase(config *DBConfig) (Database, error) {
switch config.Type {
case "sqlite":
dialector = sqlite.Open(config.DSN)
case "postgres":
dialector = postgres.Open(config.DSN)
case "mysql":
dialector = mysql.Open(config.DSN)
}
// ...
}# 单实例:SQLite
database:
type: "sqlite"
dsn: "./data/quantmesh.db"
# 多实例:PostgreSQL
database:
type: "postgres"
dsn: "host=localhost user=quantmesh password=secret dbname=quantmesh"
max_open_conns: 100
max_idle_conns: 10
# 多实例:MySQL
database:
type: "mysql"
dsn: "quantmesh:secret@tcp(localhost:3306)/quantmesh?charset=utf8mb4"-
多数据库支持
- SQLite、PostgreSQL、MySQL、SQL Server
- 统一的 API
- 自动迁移
-
性能优化
- 预编译语句
- 批量操作
- 连接池
-
开发友好
- 链式 API
- 自动关联
- Hooks 支持
# 方法 1: 使用 pgloader
pgloader data/quantmesh.db postgresql://user:pass@localhost/quantmesh
# 方法 2: 导出导入
sqlite3 data/quantmesh.db .dump > dump.sql
# 编辑 dump.sql 适配 PostgreSQL 语法
psql -U quantmesh -d quantmesh -f dump.sql
# 方法 3: 使用 GORM 迁移
go run migrate.go┌──────────────┐
│ QuantMesh │
│ Instance │
│ │
│ - NopLock │
│ - SQLite │
└──────────────┘
特点:
- ✅ 部署简单
- ✅ 无额外依赖
- ✅ 成本低
- ❌ 无高可用
- ❌ 性能受限
┌─────────────────────────────────────┐
│ Nginx (负载均衡) │
└──────────────┬──────────────────────┘
│
┌──────────┼──────────┐
│ │ │
┌───▼────┐ ┌──▼─────┐ ┌──▼─────┐
│实例 1 │ │实例 2 │ │实例 3 │
│ETH/BTC │ │BNB/SOL │ │(热备) │
└───┬────┘ └──┬─────┘ └──┬─────┘
│ │ │
└─────────┼──────────┘
│
┌─────────▼──────────┐
│ Redis Cluster │
│ (分布式锁) │
└─────────┬──────────┘
│
┌─────────▼──────────┐
│ PostgreSQL HA │
│ (主从复制) │
└────────────────────┘
特点:
- ✅ 高可用(99.9%+)
- ✅ 高性能(水平扩展)
- ✅ 故障自愈
- ❌ 部署复杂
- ❌ 成本较高
原理:每个实例负责固定的交易对
配置:
# 实例 1
trading:
symbols:
- symbol: "ETHUSDT"
- symbol: "BTCUSDT"
# 实例 2
trading:
symbols:
- symbol: "BNBUSDT"
- symbol: "SOLUSDT"优点:
- 简单可靠
- 无需协调
- 故障隔离
缺点:
- 手动分配
- 负载不均衡
- 扩容需要重新配置
原理:基于一致性哈希自动分配
func (s *Service) AllocateSymbols() []string {
// 获取所有活跃实例
instances := s.etcd.GetInstances()
// 一致性哈希分配
mySymbols := []string{}
for _, symbol := range allSymbols {
hash := hashSymbol(symbol)
if hash % len(instances) == s.instanceIndex {
mySymbols = append(mySymbols, symbol)
}
}
return mySymbols
}优点:
- 自动分配
- 负载均衡
- 自动故障转移
缺点:
- 需要配置中心(etcd)
- 实现复杂
- 可能短暂重复
原理:一个主实例,多个从实例热备
# 主实例
instance:
role: "master"
# 从实例
instance:
role: "standby"优点:
- 无重复下单
- 故障快速切换
- 简单可靠
缺点:
- 资源利用率低
- 性能无提升
// 锁获取次数
quantmesh_lock_acquire_total{key="order:binance:ETHUSDT",status="success"} 1234
// 锁冲突次数
quantmesh_lock_conflict_total{key="order:binance:ETHUSDT"} 56
// 锁持有时长
quantmesh_lock_hold_duration_seconds{key="order:binance:ETHUSDT",quantile="0.99"} 0.123// 连接池状态
quantmesh_db_connections{state="open"} 50
quantmesh_db_connections{state="idle"} 10
// 查询延迟
quantmesh_db_query_duration_seconds{operation="insert",quantile="0.99"} 0.05
// 错误率
quantmesh_db_errors_total{operation="insert",error="timeout"} 3影响:无法获取分布式锁
应对:
- 自动降级为单实例模式
- 停止其他实例(手动或自动)
- 修复 Redis 后恢复
配置:
distributed_lock:
fallback_mode: "single_instance" # Redis 故障时降级
health_check_interval: 10 # 健康检查间隔(秒)影响:无法保存交易记录
应对:
- 内存缓冲队列(最多 10000 条)
- 故障恢复后批量写入
- 主从切换(如果配置)
配置:
database:
buffer_enabled: true
buffer_size: 10000
retry_interval: 10影响:部分交易对停止交易
应对:
- 其他实例自动接管(动态分配模式)
- 热备实例激活(主从模式)
- 告警通知运维人员
影响:实例间无法通信
应对:
- 分布式锁自动过期(5秒)
- 短暂重复后自动恢复
- 监控告警
| 项目 | 单实例 | 3实例高可用 |
|---|---|---|
| 应用服务器 | 1台 (2核4G) | 3台 (2核4G) |
| Redis | 不需要 | 1台 (1G) |
| 数据库 | SQLite | PostgreSQL (2核4G) |
| 负载均衡 | 不需要 | Nginx (1台) |
| 月成本 | ¥100 | ¥650 |
| 可用性 | 99% | 99.9% |
| 性能 | 基准 | 3倍 |
假设:
- 每天交易 1000 笔
- 平均每笔盈利 $1
- 单实例故障率 1%,多实例 0.1%
单实例:
- 年收入:1000 × $1 × 365 × 0.99 = $361,350
- 年成本:¥100 × 12 = ¥1,200 ≈ $170
- 年净利润:$361,180
多实例:
- 年收入:1000 × $1 × 365 × 0.999 = $364,635
- 年成本:¥650 × 12 = ¥7,800 ≈ $1,100
- 年净利润:$363,535
增量收益:$363,535 - $361,180 = $2,355 投资回收期:($1,100 - $170) / ($2,355 / 12) ≈ 4.7 个月
// ✅ 推荐:价格区间锁
priceLevel := math.Floor(price / 10) * 10 // 每 10 美元一个锁
lockKey := fmt.Sprintf("order:%s:%s:%.0f", exchange, symbol, priceLevel)
// ❌ 避免:全局锁(并发度低)
lockKey := fmt.Sprintf("order:%s", exchange)
// ⚠️ 谨慎:精确价格锁(可能过细)
lockKey := fmt.Sprintf("order:%s:%s:%.8f", exchange, symbol, price)// ✅ 推荐:根据操作时间设置
下单操作: 5秒
取消订单: 3秒
对账操作: 30秒
// ❌ 避免:过长(影响故障恢复)
ttl := 60 * time.Second
// ❌ 避免:过短(操作未完成就过期)
ttl := 1 * time.Second# ✅ 推荐:根据实例数和负载调整
database:
max_open_conns: 100 # 3个实例 × 30 并发 = 90,留 10 余量
max_idle_conns: 10 # 10% 的最大连接数
conn_max_lifetime: 1800 # 30分钟
# ❌ 避免:过大(浪费资源)
max_open_conns: 1000
# ❌ 避免:过小(连接不足)
max_open_conns: 10// ✅ 推荐:优雅降级
if err := lock.TryLock(ctx, key, ttl); err != nil {
if errors.Is(err, redis.Nil) {
// Redis 故障,降级为本地锁
return localLock.TryLock(ctx, key, ttl)
}
return err
}
// ❌ 避免:直接失败
if err := lock.TryLock(ctx, key, ttl); err != nil {
return err
}-
分布式锁
- 使用 Redis 实现
- 支持自动过期
- 原子操作保证正确性
- 单实例模式零开销
-
数据库抽象
- 使用 GORM 统一接口
- 支持 SQLite/PostgreSQL/MySQL
- 自动迁移和连接池
- 配置驱动切换
-
部署策略
- 单实例:简单快速
- 多实例:高可用高性能
- 静态分配:简单可靠
- 动态分配:自动化
阶段 1: 基础架构(已完成)
- ✅ 分布式锁接口和实现
- ✅ 数据库抽象层
- ✅ 配置支持
阶段 2: 集成测试(下一步)
- 集成到现有代码
- 单元测试
- 集成测试
阶段 3: 生产部署(未来)
- Docker 镜像构建
- 多实例部署
- 监控和告警