4.6 系统架构
一句话总结:架构决定系统的上限——5 层架构,Factory 模式,链上链下分工。
📊 学习进度
- 状态:⬜ 未开始
- 预计时长:3-4 小时
- 已完成:0/3 个模块
- 在整体流程中的位置:预测市场实战·第 3 阶段
📍 本章定位
- 服务方案:方案 3(核心 90%)
- 学习方式:⭐ 必学
- 在流程中的作用:确定技术选型和系统架构
- 核心知识点:技术选型、5 层架构设计、链上链下分工
- 预计时长:3-4 小时
- 完成后能做什么:能输出完整的系统架构文档
人机分工
| 环节 | 谁做 | 重要度 | 说明 |
|---|---|---|---|
| 架构设计 | 🧑 人 | ⭐⭐⭐⭐⭐ | 决定 5 层架构 |
| 技术选型 | 🧑 人 + 🤖 AI | ⭐⭐⭐⭐ | 人决策,AI 对比 |
| 文档输出 | 🤖 AI | ⭐⭐⭐ | AI 生成文档 |
1. 技术选型
1.1 推荐技术栈(2025)
| 层级 | 推荐技术 | 备选 | 选择理由 |
|---|---|---|---|
| 链 | Base / Arbitrum | Optimism | 低 Gas、高吞吐、生态成熟 |
| 合约 | Solidity + Hardhat | Foundry | 插件丰富、社区大 |
| 后端 | Node.js + TypeScript | Go | 快速开发、生态丰富 |
| 前端 | Next.js + wagmi + viem | React | DApp 标准、类型安全 |
| 数据库 | PostgreSQL | MongoDB | 事务性、可靠性 |
| 链上数据 | The Graph | 自建索引 | 去中心化、实时 |
| AI | Claude API + LangGraph | OpenAI | 长上下文、安全 |
| 做市 | 自定义策略引擎 | — | 无标准方案 |
1.2 技术栈选型对比
1.3 各层级技术对比详情
区块链层对比:
| 链 | Gas 费 | TPS | 生态 | EVM 兼容 | 推荐度 |
|---|---|---|---|---|---|
| Base | $0.01-0.05 | 2000+ | Coinbase 支持 | ✅ | ⭐⭐⭐⭐⭐ |
| Arbitrum | $0.02-0.10 | 4000+ | 最大 L2 | ✅ | ⭐⭐⭐⭐⭐ |
| Optimism | $0.03-0.15 | 2000+ | OP Stack 生态 | ✅ | ⭐⭐⭐⭐ |
| Polygon | $0.01-0.05 | 7000+ | Polymarket 选择 | ✅ | ⭐⭐⭐⭐ |
| Ethereum | $1-50 | 15 | 最安全 | ✅ | ⭐⭐ |
数据来源:L2Beat,2025 年 Q1 数据
开发框架对比:
| 框架 | 语言 | 测试 | 部署 | 学习曲线 | 社区 |
|---|---|---|---|---|---|
| Hardhat | JavaScript | 内置 | 插件 | 低 | 最大 |
| Foundry | Solidity | forge test | forge | 中 | 增长快 |
| Truffle | JavaScript | 内置 | 内置 | 中 | 老牌 |
2. 5 层架构设计
2.1 架构图
2.2 各层职责详解
| 层级 | 职责 | 核心组件 | 数据流 |
|---|---|---|---|
| Layer 1: 事件池 | 事件供给 | AI-Gateway, 规则引擎 | 信息 → 事件 |
| Layer 2: 业务中台 | 业务逻辑 | 撮合引擎, API | 事件 → 交易 |
| Layer 3: 链上合约 | 资产确权 | Factory 模式 | 交易 → 链上 |
| Layer 4: 预言机 | 结果验证 | Oracle Nodes | 结果 → 验证 |
| Layer 5: 做市 | 流动性 | 做市策略 | 流动性 → 市场 |
2.3 架构设计原则
| 原则 | 说明 | 实现方式 |
|---|---|---|
| 链上链下分工 | 链上做确权,链下做业务 | 事件推送+撮合+统计 |
| Factory 模式 | 批量生产市场 | 5 个 Factory 依次产出 Pod |
| 多租户隔离 | DApp 独立空间 | 每个 DApp 独立 Pod 集合 |
| 模块化 | 组件可替换 | 每个 Pod 独立合约 |
| 去中心化验证 | 结果可信 | zk-verifier + Staking |
3. 链上链下分工
3.1 分工矩阵
| 层级 | 职责 | 技术 | 为什么放在这层 |
|---|---|---|---|
| 链上 | 资产确权 | Solidity | 不可篡改、可验证 |
| 链上 | 交易记录 | Solidity | 透明、可审计 |
| 链上 | 结算分配 | Solidity | 公正、自动执行 |
| 链上 | 预言机验证 | Solidity | 去中心化信任 |
| 链下 | 订单撮合 | Node.js | 高性能、低延迟 |
| 链下 | 数据统计 | Node.js | 灵活、高效 |
| 链下 | 事件生成 | AI | 智能、持续 |
| 链下 | 做市策略 | Node.js | 复杂计算、实时调整 |
3.2 关键交互点
3.3 数据一致性保障
| 场景 | 问题 | 解决方案 |
|---|---|---|
| 链下撮合结果 | 可能与链上不一致 | 定期对账+异常告警 |
| 事件状态同步 | 链上状态变更 | 监听合约事件 |
| 预言机结果 | 可能延迟 | 超时机制+重试 |
| 做市策略 | 可能过期 | 实时数据刷新 |
4. API 接口设计
4.1 核心 API 列表
| 接口 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 获取事件列表 | GET | /api/events | 分页、筛选、排序 |
| 获取事件详情 | GET | /api/events/:id | 含交易数据 |
| 创建事件 | POST | /api/events | 需要认证 |
| 下单 | POST | /api/orders | 市价/限价 |
| 取消订单 | DELETE | /api/orders/:id | 需要签名 |
| 获取订单簿 | GET | /api/orderbook/:eventId | 实时深度 |
| 获取交易记录 | GET | /api/trades/:eventId | 历史成交 |
| 获取用户仓位 | GET | /api/positions | 当前持仓 |
| 获取价格历史 | GET | /api/prices/:eventId | K 线数据 |
4.2 API 响应格式
// 统一响应格式
interface ApiResponse<T> {
code: number; // 0=成功, 非0=错误
message: string; // 描述
data: T; // 数据
timestamp: number; // 时间戳
}
// 事件列表响应
interface EventListResponse {
events: Event[];
total: number;
page: number;
pageSize: number;
}
// 订单簿响应
interface OrderBookResponse {
eventId: string;
bids: OrderLevel[]; // 买单,价格从高到低
asks: OrderLevel[]; // 卖单,价格从低到高
lastPrice: number;
timestamp: number;
}
interface OrderLevel {
price: number; // 价格 (0.01-0.99)
quantity: number; // 数量
orderCount: number; // 订单数
}4.3 API 认证与限流
| 机制 | 说明 | 配置 |
|---|---|---|
| 认证 | JWT + 钱包签名 | 24h 过期 |
| 限流 | 每用户 100 req/min | Redis 计数 |
| 签名 | 交易需要 EIP-712 签名 | 防重放 |
5. 数据库设计
5.1 核心表结构
-- 事件表
CREATE TABLE events (
id UUID PRIMARY KEY,
title VARCHAR(256) NOT NULL,
description TEXT,
category VARCHAR(32) NOT NULL,
resolution_source VARCHAR(256),
end_time TIMESTAMP NOT NULL,
status VARCHAR(16) DEFAULT 'active', -- active | resolved | cancelled
yes_price DECIMAL(5,4) DEFAULT 0.5000,
no_price DECIMAL(5,4) DEFAULT 0.5000,
total_volume DECIMAL(18,2) DEFAULT 0,
chain_event_id VARCHAR(66), -- 链上事件 ID
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 订单表
CREATE TABLE orders (
id UUID PRIMARY KEY,
event_id UUID REFERENCES events(id),
user_address VARCHAR(42) NOT NULL,
side VARCHAR(3) NOT NULL, -- YES | NO
order_type VARCHAR(8) NOT NULL, -- MARKET | LIMIT
price DECIMAL(5,4),
amount DECIMAL(18,2) NOT NULL,
filled_amount DECIMAL(18,2) DEFAULT 0,
status VARCHAR(16) DEFAULT 'pending', -- pending | filled | partial | cancelled
created_at TIMESTAMP DEFAULT NOW()
);
-- 成交记录表
CREATE TABLE trades (
id UUID PRIMARY KEY,
event_id UUID REFERENCES events(id),
buyer_address VARCHAR(42) NOT NULL,
seller_address VARCHAR(42) NOT NULL,
side VARCHAR(3) NOT NULL,
price DECIMAL(5,4) NOT NULL,
amount DECIMAL(18,2) NOT NULL,
fee DECIMAL(18,2) NOT NULL,
tx_hash VARCHAR(66),
created_at TIMESTAMP DEFAULT NOW()
);
-- 索引
CREATE INDEX idx_events_category ON events(category);
CREATE INDEX idx_events_status ON events(status);
CREATE INDEX idx_events_end_time ON events(end_time);
CREATE INDEX idx_orders_event ON orders(event_id, status);
CREATE INDEX idx_orders_user ON orders(user_address);
CREATE INDEX idx_trades_event ON trades(event_id, created_at);5.2 数据流图
6. Gas 优化策略
6.1 优化方法
| 策略 | 说明 | 节省比例 | 实现复杂度 |
|---|---|---|---|
| L2 部署 | Base/Arbitrum | 90%+ | 低 |
| 批处理 | 批量处理订单 | 50% | 中 |
| 状态通道 | 链下交易,链上结算 | 80% | 高 |
| 事件聚合 | 事件批量创建 | 40% | 低 |
| 存储优化 | 使用 mapping 替代 array | 30% | 低 |
6.2 批处理实现示例
// 批量提交成交结果,节省 Gas
function batchSettle(
uint256 eventId,
address[] calldata buyers,
address[] calldata sellers,
uint256[] calldata amounts,
uint256[] calldata prices
) external onlyOperator {
require(buyers.length == sellers.length, "Length mismatch");
require(buyers.length == amounts.length, "Length mismatch");
for (uint256 i = 0; i < buyers.length; i++) {
_settleTrade(eventId, buyers[i], sellers[i], amounts[i], prices[i]);
}
}7. 部署架构
7.1 生产环境部署
7.2 OPC 轻量部署方案
作为 OPC,不需要复杂的生产环境。推荐轻量方案:
| 组件 | 推荐方案 | 成本 |
|---|---|---|
| 前端 | Vercel / Cloudflare Pages | 免费 |
| API | Railway / Fly.io | $5-20/月 |
| 数据库 | Supabase / Neon | 免费层 |
| Redis | Upstash | 免费层 |
| 监控 | Grafana Cloud | 免费层 |
| 域名 | Cloudflare | $10/年 |
8. 架构演进路线
8.1 MVP 到生产级的演进
预测市场架构不是一蹴而就的,需要分阶段演进:
| 阶段 | 架构 | 数据库 | 部署 | 用户规模 | 月成本 |
|---|---|---|---|---|---|
| MVP | 单体 | PostgreSQL 单实例 | Vercel + Railway | <1k DAU | $50 |
| 成长期 | 模块化 | PostgreSQL 主从 | Docker + K8s | 1k-10k DAU | $500 |
| 规模化 | 微服务 | 分库分表 + ClickHouse | 多区域 K8s | 10k+ DAU | $5,000 |
8.2 性能瓶颈与优化
| 瓶颈 | 症状 | 解决方案 | 优先级 |
|---|---|---|---|
| 数据库查询慢 | API 响应 >500ms | 索引优化 + Redis 缓存 | P0 |
| 撮合延迟高 | 订单处理 >100ms | 内存撮合 + 批处理 | P0 |
| 链上交互慢 | 确认时间 >30s | L2 部署 + 批量提交 | P1 |
| WebSocket 吞吐低 | 推送延迟 >1s | 连接池 + 消息压缩 | P1 |
| 前端加载慢 | 首屏 >3s | CDN + 代码分割 | P2 |
性能优化代码示例:
// Redis 缓存层
class CacheLayer {
private redis: Redis;
private defaultTTL = 60; // 60 秒
async get<T>(key: string, fetchFn: () => Promise<T>, ttl?: number): Promise<T> {
// 1. 尝试从缓存获取
const cached = await this.redis.get(key);
if (cached) {
return JSON.parse(cached);
}
// 2. 缓存未命中,从数据源获取
const data = await fetchFn();
// 3. 写入缓存
await this.redis.setex(key, ttl || this.defaultTTL, JSON.stringify(data));
return data;
}
// 批量获取,减少网络往返
async mget<T>(keys: string[]): Promise<(T | null)[]> {
const results = await this.redis.mget(...keys);
return results.map(r => r ? JSON.parse(r) : null);
}
}8.3 可观测性架构
| 支柱 | 工具 | 用途 | 采样率 |
|---|---|---|---|
| 日志 | Loki + Promtail | 错误排查 | 100% |
| 指标 | Prometheus | 性能监控 | 15s 间隔 |
| 追踪 | Jaeger | 链路分析 | 10% 采样 |
9. 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 架构过重 | 过度设计 | 先做 MVP,再迭代 |
| 技术选型错误 | 没有调研 | 先做技术调研 |
| 扩展性差 | 没有预留 | Factory 模式设计 |
| Gas 太高 | 链上操作过多 | 优化链下处理 |
| 数据不一致 | 链上链下同步问题 | 定期对账+事件监听 |
| API 延迟高 | 数据库查询慢 | 索引优化+缓存 |
9. 下一步
完成架构设计后,进入 阶段 4:智能合约
8.4 微服务拆分策略
当系统达到一定规模后,需要从单体架构拆分为微服务。以下是推荐的拆分顺序和策略:
| 拆分顺序 | 服务 | 拆分理由 | 依赖 | 复杂度 |
|---|---|---|---|---|
| 1 | 撮合引擎 | 性能瓶颈,需要独立扩展 | 无 | 低 |
| 2 | 做市服务 | 策略独立,需要快速迭代 | 撮合引擎 | 中 |
| 3 | 事件服务 | AI 生成逻辑独立 | 无 | 低 |
| 4 | 用户服务 | 认证+权限独立 | 无 | 低 |
| 5 | 数据服务 | 分析逻辑独立 | PostgreSQL | 中 |
服务间通信设计:
// 服务间通信接口
interface ServiceBus {
// 发布事件
publish(topic: string, message: any): Promise<void>;
// 订阅事件
subscribe(topic: string, handler: (message: any) => Promise<void>): void;
// 请求-响应模式
request<T>(service: string, method: string, params: any): Promise<T>;
}
// 使用示例
const bus = new ServiceBus();
// 撮合引擎发布成交事件
bus.publish('trade.matched', {
eventId: 'xxx',
buyerId: '0x...',
sellerId: '0x...',
price: 0.65,
quantity: 100
});
// 做市服务订阅成交事件,更新库存
bus.subscribe('trade.matched', async (trade) => {
await marketMaker.updatePosition(trade);
});
// 数据服务订阅成交事件,记录分析
bus.subscribe('trade.matched', async (trade) => {
await analytics.recordTrade(trade);
});8.5 数据库读写分离实现
当单库成为瓶颈时,读写分离是最简单的优化方案:
// 数据库读写分离层
class DatabaseRouter {
private writePool: Pool; // 主库连接池
private readPool: Pool; // 从库连接池
constructor(writeUrl: string, readUrl: string) {
this.writePool = new Pool({ connectionString: writeUrl });
this.readPool = new Pool({ connectionString: readUrl, max: 20 });
}
// 写操作路由到主库
async write<T>(query: string, params?: any[]): Promise<T> {
return this.writePool.query(query, params);
}
// 读操作路由到从库
async read<T>(query: string, params?: any[]): Promise<T> {
return this.readPool.query(query, params);
}
// 强制读主库(写后读场景)
async readFromMaster<T>(query: string, params?: any[]): Promise<T> {
return this.writePool.query(query, params);
}
}| 场景 | 路由策略 | 原因 |
|---|---|---|
| 创建订单 | 主库 | 写操作 |
| 查询订单簿 | 从库 | 读操作,可容忍延迟 |
| 下单后查询 | 主库 | 写后读一致性 |
| 用户列表 | 从库 | 读操作 |
| 结算操作 | 主库 | 写操作,需要强一致性 |
8.6 API 网关设计
预测市场需要一个统一的 API 网关来处理认证、限流、路由等横切关注点:
// API 网关核心配置
interface GatewayConfig {
rateLimit: {
windowMs: number; // 时间窗口(毫秒)
maxRequests: number; // 最大请求数
};
auth: {
jwtSecret: string;
excludePaths: string[]; // 不需要认证的路径
};
cors: {
origins: string[];
credentials: boolean;
};
}
// 限流中间件示例
class RateLimiter {
private requests: Map<string, number[]> = new Map();
check(clientId: string, limit: number, windowMs: number): boolean {
const now = Date.now();
const timestamps = this.requests.get(clientId) || [];
const validTimestamps = timestamps.filter(t => now - t < windowMs);
if (validTimestamps.length >= limit) {
return false; // 超过限流
}
validTimestamps.push(now);
this.requests.set(clientId, validTimestamps);
return true;
}
}API 网关最佳实践:
| 实践 | 说明 | 优先级 |
|---|---|---|
| 统一认证 | JWT + 钱包签名 | P0 |
| 限流保护 | 每用户 100 req/min | P0 |
| 请求日志 | 记录所有请求 | P1 |
| 错误处理 | 统一错误格式 | P1 |
| 版本管理 | /api/v1/ 前缀 | P2 |
8.7 WebSocket 实时推送架构
预测市场需要亚秒级的数据推送。以下是 WebSocket 推送架构设计:
// WebSocket 连接管理器
class WebSocketManager {
private connections: Map<string, WebSocket> = new Map();
private subscriptions: Map<string, Set<string>> = new Map();
/**
* 订阅事件更新
*/
subscribe(clientId: string, eventId: string): void {
const key = `event:${eventId}`;
if (!this.subscriptions.has(key)) {
this.subscriptions.set(key, new Set());
}
this.subscriptions.get(key)!.add(clientId);
}
/**
* 推送订单簿更新
*/
broadcastOrderBook(eventId: string, snapshot: OrderBookSnapshot): void {
const key = `event:${eventId}`;
const clients = this.subscriptions.get(key);
if (!clients) return;
const message = JSON.stringify({
type: 'orderbook',
eventId,
data: snapshot,
timestamp: Date.now()
});
for (const clientId of clients) {
const ws = this.connections.get(clientId);
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
}
}
}WebSocket 最佳实践:
| 实践 | 说明 | 优先级 |
|---|---|---|
| 心跳检测 | 每 30 秒 ping/pong | P0 |
| 断线重连 | 自动重连 + 状态恢复 | P0 |
| 消息压缩 | gzip 压缩减少带宽 | P1 |
| 订阅管理 | 精细化订阅避免浪费 | P1 |
| 背压处理 | 消息队列缓冲 | P2 |
参考与延伸
[11] Socket.IO Documentation(2025)— WebSocket 实时通信框架
[12] BullMQ(2025)— Redis 消息队列
[1] L2Beat(2025)— L2 数据对比
[2] Hardhat Documentation(2025)— 合约开发框架
[3] The Graph(2025)— 链上数据索引
[4] Vercel(2025)— 前端部署平台
[5] Supabase(2025)— 开源数据库服务
[6] OpenTelemetry(2025)— 可观测性框架
[7] Designing Data-Intensive Applications(2017)— Martin Kleppmann,分布式系统设计经典
[8] Microservices Patterns(2018)— Chris Richardson,微服务架构模式
[9] Base Network Documentation(2025)— Base L2 官方文档
[10] Redis Documentation(2025)— Redis 缓存最佳实践