121 lines
5.2 KiB
Markdown
121 lines
5.2 KiB
Markdown
# FIX Engine 高可用 (HA) 架构方案说明文档
|
||||
|
|
|
|||
|
|
## 1. 架构演进概述
|
|||
|
|
|
|||
|
|
### 1.1 原单实例架构
|
|||
|
|
- **消息存储**: 使用本地文件系统(FileStore),消息持久化在本地磁盘。
|
|||
|
|
- **灾备能力**: 无自动切换机制。若实例宕机,需手动启动备机,且由于文件存储不共享,消息序列号(Sequence Number)难以同步。
|
|||
|
|
- **会话管理**: 启动即连接,缺乏灵活性。
|
|||
|
|
|
|||
|
|
### 1.2 现高可用 (HA) 架构
|
|||
|
|
- **消息存储**: 迁移至 **MySQL 数据库**(JdbcStore)。多实例共享同一数据库,确保消息和序列号在节点切换后保持一致。
|
|||
|
|
- **领导权选举**: 引入 **Redis 分布式锁**(Redisson)。以 Session 为粒度进行选举,确保每个 FIX 会话在全网只有一个活动实例。
|
|||
|
|
- **自动灾备**: 备机定时检查锁状态,主节点宕机后锁释放,备机自动接管并恢复连接。
|
|||
|
|
- **动态会话**: 结合 DynamicSession 机制,仅在获得领导权后才创建并启动 FIX 连接。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 核心组件分析
|
|||
|
|
|
|||
|
|
### 2.1 MySQL 消息持久化 (JdbcStore)
|
|||
|
|
通过 `QuickFixJConfig` 配置,系统支持将消息存储从文件切换到 MySQL。
|
|||
|
|
|
|||
|
|
- **核心类**: `quickfix.JdbcStoreFactory`
|
|||
|
|
- **配置触发**: 当 `StorageType=mysql` 时启用。
|
|||
|
|
- **数据表要求**: 需要在数据库中创建 QuickFIX/J 标准表(如 `messages`, `sessions` 等)。
|
|||
|
|
- **优势**:
|
|||
|
|
- **共享状态**: 所有实例访问同一份数据,切换节点无需手动同步序列号。
|
|||
|
|
- **可靠性**: 数据库事务保障消息持久化。
|
|||
|
|
|
|||
|
|
### 2.2 Redis 分布式锁选举 (LeaderElectionService)
|
|||
|
|
使用 Redisson 实现 Session 级别的分布式锁管理。
|
|||
|
|
|
|||
|
|
- **锁 Key 格式**: `g3fo:fix-engine:session-lock:{SenderCompID}@{TargetCompID}`
|
|||
|
|
- **看门狗机制**: 租约时间设为 -1,Redisson 自动续期。只要进程存活,锁就不会过期。
|
|||
|
|
- **检查机制**: 定时线程(默认 10 秒)遍历所有配置的 Session。
|
|||
|
|
- **宕机延迟 (Failover Delay)**: 检测到锁释放后,等待 5 秒再尝试抢占,防止因网络抖动引起的频繁切换。
|
|||
|
|
|
|||
|
|
### 2.3 动态会话控制 (Dynamic Session)
|
|||
|
|
解决 QuickFIX/J 在 `start()` 时会自动连接所有会话的问题。
|
|||
|
|
|
|||
|
|
- **机制**: 在配置文件中将 Session 标记为 Dynamic(不预先加载)。
|
|||
|
|
- **流程**:
|
|||
|
|
1. `LeaderElectionService` 获得 Redis 锁。
|
|||
|
|
2. 调用 `quickFixJConfig.startSession(sessionCode)`。
|
|||
|
|
3. 内部通过 `socketInitiator.createDynamicSession(sessionId)` 动态创建会话对象。
|
|||
|
|
4. 调用 `session.logon()` 触发连接。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 完整业务流程
|
|||
|
|
|
|||
|
|
### 3.1 服务启动流程
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
Start([服务启动]) --> InitInitiator[初始化 QuickFIX Initiator]
|
|||
|
|
InitInitiator --> StartElection[启动 LeaderElectionService]
|
|||
|
|
StartElection --> LoadSessions[加载 enabledSession 配置]
|
|||
|
|
LoadSessions --> LoopCheck{遍历每个 Session}
|
|||
|
|
|
|||
|
|
LoopCheck --> TryLock[尝试获取 Redis 分布式锁]
|
|||
|
|
|
|||
|
|
TryLock -- 成功获得锁 --> StartComp[启动会话组件]
|
|||
|
|
StartComp --> CreateDynamic[创建 DynamicSession 对象]
|
|||
|
|
CreateDynamic --> FixLogon[发送 FIX Logon]
|
|||
|
|
FixLogon --> StartMQ[启动对应的 RocketMQ Listener]
|
|||
|
|
|
|||
|
|
TryLock -- 失败 --> Standby[进入待命状态]
|
|||
|
|
Standby --> Wait[等待下一个检查周期]
|
|||
|
|
Wait --> LoopCheck
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 宕机自动切换流程 (Failover)
|
|||
|
|
1. **主节点 (Node A)** 持有 `A@OCG` 的 Redis 锁,正常运行。
|
|||
|
|
2. **主节点 (Node A)** 宕机或网络断开。
|
|||
|
|
3. **Redis 锁失效**: 经过看门狗租约超时,Redis 中的锁 Key 消失。
|
|||
|
|
4. **备节点 (Node B)** 定时任务检测到锁已释放。
|
|||
|
|
5. **进入延迟等待**: 备节点等待 `failoverDelay` (如 5秒)。
|
|||
|
|
6. **抢占锁**: 备节点尝试 `tryLock`,成功获得领导权。
|
|||
|
|
7. **恢复连接**: 备节点动态创建 Session 并在 MySQL 中读取最新的序列号,发送 Logon。
|
|||
|
|
8. **流量接管**: 启动 MQ Listener,开始处理业务消息。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 关键配置指南 (Nacos)
|
|||
|
|
|
|||
|
|
在 `quick-fix-client.yml` 中进行如下配置:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
quickfixj:
|
|||
|
|
client:
|
|||
|
|
# 启用会话列表
|
|||
|
|
enabledSession: A@OCG,A@OSL
|
|||
|
|
|
|||
|
|
# 选举参数
|
|||
|
|
sessionElection:
|
|||
|
|
checkInterval: 10 # 检查周期(秒)
|
|||
|
|
failoverDelay: 5 # 接管延迟(秒)
|
|||
|
|
|
|||
|
|
# 存储配置(需配合数据源)
|
|||
|
|
# 确保 SessionSettings 中的 StorageType=mysql
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 日志监控与诊断
|
|||
|
|
|
|||
|
|
通过监控日志可以实时掌握 HA 状态:
|
|||
|
|
|
|||
|
|
| 日志内容 | 含义 | 级别 |
|
|||
|
|
| :--- | :--- | :--- |
|
|||
|
|
| `>>> 获得Session A@OCG 领导权 <<<` | 当前实例成功竞争到主节点 | INFO |
|
|||
|
|
| `!!! Session A@OCG 领导权锁意外丢失 !!!` | 锁异常丢失,可能是 Redis 连接断开 | ERROR |
|
|||
|
|
| `正在启动Session A@OCG 组件...` | 获得锁后开始加载 FIX 和 MQ | INFO |
|
|||
|
|
| `✅ Session A@OCG 组件启动完成` | 成功恢复服务 | INFO |
|
|||
|
|
| `未获得领导权,作为备用实例运行中...` | 当前为备份节点,正常待命 | DEBUG |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 总结
|
|||
|
|
本方案通过 **MySQL 共享存储 + Redis 分布式选举 + 动态会话加载** 的组合,解决了 FIX 引擎单点故障问题。它不仅保证了消息的连续性(序列号一致),还实现了会话级别的细粒度高可用,能够灵活应对多种部署场景。
|