QAExchange 常见问题 (FAQ)
本文档收集整理了 QAExchange 系统使用过程中的常见问题及解决方案。
📖 目录
安装和编译
Q1: 编译时报错 "failed to compile qaexchange"
症状:
error: failed to run custom build command for `qaexchange`
原因:
- Rust 版本过低
- qars 依赖未找到
- 系统库缺失
解决方案:
检查 Rust 版本:
rustc --version
# 需要 1.91.0-nightly 或更高版本
rustup update nightly
rustup default nightly
检查 qars 依赖:
# 确保 qars2 在正确位置
ls ../qars2/
# 应该看到 Cargo.toml 和 src/ 目录
# 如果没有,clone qars
git clone https://github.com/QUANTAXIS/qars ../qars2
安装系统依赖 (Ubuntu/Debian):
sudo apt-get update
sudo apt-get install build-essential pkg-config libssl-dev
安装系统依赖 (macOS):
brew install openssl pkg-config
Q2: 编译时提示 "can't find crate for qars"
症状:
error[E0463]: can't find crate for `qars`
原因: qars
依赖路径配置错误
解决方案:
检查 Cargo.toml
中的路径:
[dependencies]
qars = { path = "../qars2" }
确保路径正确:
# 从 qaexchange-rs 目录执行
cd ..
ls -d qars2
cd qaexchange-rs
如果路径不对,修改 Cargo.toml
:
qars = { path = "/absolute/path/to/qars2" }
Q3: 编译警告 "unused variable" 或 "dead code"
症状:
warning: unused variable: `xxx`
warning: function is never used: `yyy`
原因: 开发过程中的正常警告
解决方案:
忽略警告编译:
cargo build --lib 2>&1 | grep -v warning
消除特定警告:
#![allow(unused)] fn main() { #[allow(dead_code)] fn unused_function() { ... } #[allow(unused_variables)] let unused_var = 42; }
Q4: iceoryx2 编译失败
症状:
error: failed to compile `iceoryx2`
原因: iceoryx2 是可选功能,可能环境不支持
解决方案:
禁用 iceoryx2:
# 编译时禁用 iceoryx2 feature
cargo build --lib --no-default-features
修改 Cargo.toml:
[features]
default = [] # 移除 "iceoryx2"
iceoryx2 = ["dep:iceoryx2"]
配置问题
Q5: 启动时报错 "config file not found"
症状:
Error: Config file not found: config/exchange.toml
原因: 配置文件缺失或路径错误
解决方案:
检查配置文件:
ls config/
# 应该看到 exchange.toml 和 instruments.toml
如果缺失,创建默认配置:
config/exchange.toml
:
[exchange]
name = "QAExchange"
trading_hours = "09:00-15:00"
settlement_time = "15:30"
[risk]
margin_ratio = 0.1
force_close_threshold = 1.0
[server]
http_host = "127.0.0.1"
http_port = 8000
ws_host = "127.0.0.1"
ws_port = 8001
config/instruments.toml
:
[[instruments]]
instrument_id = "SHFE.cu2501"
exchange_id = "SHFE"
product_id = "cu"
price_tick = 10.0
volume_multiple = 5
margin_ratio = 0.1
commission = 0.0005
Q6: 合约配置无效
症状: 下单时提示 "instrument not found"
原因: 合约未注册或配置格式错误
解决方案:
检查 instruments.toml 格式:
[[instruments]] # 注意双中括号
instrument_id = "SHFE.cu2501" # 必须包含交易所前缀
exchange_id = "SHFE" # 大写
product_id = "cu" # 小写
price_tick = 10.0 # 浮点数
volume_multiple = 5 # 整数
运行时注册合约:
curl -X POST http://localhost:8000/api/admin/instrument/create \
-H "Content-Type: application/json" \
-d '{
"instrument_id": "SHFE.cu2501",
"exchange_id": "SHFE",
"product_id": "cu",
"price_tick": 10.0,
"volume_multiple": 5,
"margin_ratio": 0.1,
"commission": 0.0005
}'
查询已注册合约:
curl http://localhost:8000/api/admin/instruments
Q7: 日志级别设置无效
症状: 设置 RUST_LOG=debug
后仍然只看到 INFO 日志
原因: 环境变量设置方式错误
解决方案:
正确设置日志级别:
# Linux/macOS
export RUST_LOG=qaexchange=debug
cargo run --bin qaexchange-server
# 或者临时设置
RUST_LOG=qaexchange=debug cargo run --bin qaexchange-server
# Windows (PowerShell)
$env:RUST_LOG="qaexchange=debug"
cargo run --bin qaexchange-server
按模块设置日志:
# 只显示 matching 模块的 DEBUG 日志
RUST_LOG=qaexchange::matching=debug
# 多模块设置
RUST_LOG=qaexchange::matching=debug,qaexchange::storage=info
在代码中设置:
#![allow(unused)] fn main() { // src/main.rs env_logger::Builder::from_default_env() .filter_level(log::LevelFilter::Debug) .init(); }
运行问题
Q8: 启动时报错 "Address already in use"
症状:
Error: Address already in use (os error 98)
原因: 端口已被占用
解决方案:
查找占用端口的进程:
# Linux/macOS
lsof -i :8000
lsof -i :8001
# 杀死进程
kill -9 <PID>
修改端口配置:
config/exchange.toml
:
[server]
http_port = 8002 # 改为其他端口
ws_port = 8003
或者使用环境变量:
HTTP_PORT=8002 WS_PORT=8003 cargo run --bin qaexchange-server
Q9: 启动后无法访问 HTTP API
症状: curl http://localhost:8000/health
返回 "Connection refused"
原因:
- 服务未启动成功
- 端口配置错误
- 防火墙拦截
解决方案:
检查服务是否运行:
# 查看进程
ps aux | grep qaexchange
# 查看日志
tail -f logs/qaexchange.log
检查端口监听:
# Linux/macOS
netstat -an | grep 8000
# 应该看到:
# tcp 0 0 127.0.0.1:8000 0.0.0.0:* LISTEN
检查防火墙:
# Ubuntu
sudo ufw status
sudo ufw allow 8000
# CentOS
sudo firewall-cmd --add-port=8000/tcp --permanent
sudo firewall-cmd --reload
使用 0.0.0.0 监听所有接口:
[server]
http_host = "0.0.0.0" # 允许外部访问
http_port = 8000
Q10: 运行一段时间后崩溃
症状: 服务运行几小时后自动退出
原因:
- 内存溢出 (OOM)
- Panic 未捕获
- 磁盘空间不足
解决方案:
检查内存使用:
# 监控内存
top -p $(pgrep qaexchange)
# 查看 OOM 日志
dmesg | grep -i "out of memory"
sudo grep -i "killed process" /var/log/syslog
启用崩溃日志:
// src/main.rs use std::panic; fn main() { panic::set_hook(Box::new(|panic_info| { log::error!("Panic occurred: {:?}", panic_info); })); // ... your code }
检查磁盘空间:
df -h
# 确保有足够空间用于 WAL 和 SSTable
限制 WAL 和 SSTable 大小:
Q11: 如何后台运行服务
问题: 关闭终端后服务停止
解决方案:
使用 nohup:
nohup cargo run --bin qaexchange-server > logs/server.log 2>&1 &
使用 systemd (推荐生产环境):
创建 /etc/systemd/system/qaexchange.service
:
[Unit]
Description=QAExchange Trading System
After=network.target
[Service]
Type=simple
User=quantaxis
WorkingDirectory=/home/quantaxis/qaexchange-rs
ExecStart=/home/quantaxis/.cargo/bin/cargo run --bin qaexchange-server --release
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
启用服务:
sudo systemctl daemon-reload
sudo systemctl enable qaexchange
sudo systemctl start qaexchange
sudo systemctl status qaexchange
查看日志:
sudo journalctl -u qaexchange -f
交易问题
Q12: 下单失败 "insufficient funds"
症状: 下单返回错误 "Insufficient funds"
原因:
- 账户可用资金不足
- 保证金计算错误
- 账户未入金
解决方案:
查询账户信息:
curl http://localhost:8000/api/account/user123
检查返回的 available
字段:
{
"user_id": "user123",
"balance": 100000,
"available": 50000, # 可用资金
"margin": 50000
}
计算所需保证金:
保证金 = 价格 × 数量 × 合约乘数 × 保证金率
例: 50000 × 1 × 5 × 0.1 = 25000 元
入金:
curl -X POST http://localhost:8000/api/management/deposit \
-H "Content-Type: application/json" \
-d '{
"user_id": "user123",
"amount": 100000
}'
Q13: 订单状态一直是 PENDING
症状: 下单后订单状态不更新
原因:
- 撮合引擎未运行
- 合约未注册
- 价格超出涨跌停板
解决方案:
检查撮合引擎:
# 查看日志
grep "matching engine" logs/qaexchange.log
检查订单详情:
curl http://localhost:8000/api/order/order123
检查合约状态:
curl http://localhost:8000/api/admin/instruments | grep "SHFE.cu2501"
确保合约状态为 TRADING
:
{
"instrument_id": "SHFE.cu2501",
"status": "TRADING" # 不是 SUSPENDED
}
手动触发撮合:
如果是测试环境,可以提交反向订单:
# 原订单: BUY 1手 @ 50000
# 提交: SELL 1手 @ 50000
curl -X POST http://localhost:8000/api/order/submit \
-H "Content-Type: application/json" \
-d '{
"user_id": "user456",
"order_id": "order456",
"instrument_id": "SHFE.cu2501",
"direction": "SELL",
"offset": "OPEN",
"volume": 1,
"price_type": "LIMIT",
"limit_price": 50000
}'
Q14: 撤单失败 "order not found"
症状: 撤单时提示订单不存在
原因:
- 订单ID错误
- 订单已成交
- 订单已撤销
解决方案:
查询订单状态:
curl http://localhost:8000/api/order/order123
检查订单状态:
{
"order_id": "order123",
"status": "FILLED" # 已成交,无法撤单
}
可撤单状态:
ACCEPTED
: 已接受,未成交PARTIAL_FILLED
: 部分成交
不可撤单状态:
FILLED
: 已完全成交CANCELLED
: 已撤销REJECTED
: 已拒绝
正确撤单:
curl -X POST http://localhost:8000/api/order/cancel \
-H "Content-Type: application/json" \
-d '{
"user_id": "user123",
"order_id": "order123" # 确保 order_id 正确
}'
Q15: 持仓浮动盈亏计算不对
症状: 查询持仓时 float_profit
值不正确
原因:
- 最新价未更新
- 开仓价计算错误
- 合约乘数配置错误
解决方案:
手动计算验证:
多头浮动盈亏 = (最新价 - 开仓价) × 持仓量 × 合约乘数
例: (50500 - 50000) × 2 × 5 = 5000 元
空头浮动盈亏 = (开仓价 - 最新价) × 持仓量 × 合约乘数
例: (50000 - 49500) × 2 × 5 = 5000 元
查询持仓详情:
curl http://localhost:8000/api/position/user123
检查关键字段:
{
"instrument_id": "SHFE.cu2501",
"volume_long": 2,
"open_price_long": 50000,
"last_price": 50500, # 最新价
"float_profit": 5000, # 应该等于 (50500-50000)*2*5
"volume_multiple": 5
}
触发价格更新:
提交成交单更新 last_price
:
# 任意成交都会更新 last_price
Q16: 强制平仓没有触发
症状: 账户风险度 > 100% 但未强平
原因:
- 日终结算未执行
- 强平阈值配置过高
- 强平逻辑未实现
解决方案:
检查风险度:
curl http://localhost:8000/api/account/user123
{
"balance": 20000,
"margin": 25000,
"risk_ratio": 1.25 # 125% > 100%
}
手动触发结算:
# 先设置结算价
curl -X POST http://localhost:8000/api/admin/settlement/set-price \
-H "Content-Type: application/json" \
-d '{
"instrument_id": "SHFE.cu2501",
"settlement_price": 50000
}'
# 执行日终结算
curl -X POST http://localhost:8000/api/admin/settlement/execute
检查强平配置:
config/exchange.toml
:
[risk]
force_close_threshold = 1.0 # 100%
查看强平日志:
grep "Force closing" logs/qaexchange.log
WebSocket 连接
Q17: WebSocket 连接失败
症状: 前端无法连接 WebSocket
原因:
- WebSocket 服务未启动
- 端口配置错误
- CORS 问题
解决方案:
测试 WebSocket 连接:
使用 websocat
(推荐):
# 安装
cargo install websocat
# 连接
websocat ws://localhost:8001/ws?user_id=user123
或使用 JavaScript 测试:
const ws = new WebSocket('ws://localhost:8001/ws?user_id=user123');
ws.onopen = () => {
console.log('Connected');
ws.send(JSON.stringify({aid: 'peek_message'}));
};
ws.onmessage = (event) => {
console.log('Received:', event.data);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
检查 WebSocket 服务:
netstat -an | grep 8001
配置 CORS:
如果跨域访问,需要配置 CORS:
#![allow(unused)] fn main() { // src/service/websocket/mod.rs use actix_cors::Cors; HttpServer::new(|| { App::new() .wrap( Cors::default() .allow_any_origin() .allow_any_method() .allow_any_header() ) .route("/ws", web::get().to(ws_handler)) }) }
Q18: WebSocket 连接频繁断开
症状: WebSocket 每隔 10 秒断开
原因: 心跳超时
解决方案:
客户端实现心跳:
const ws = new WebSocket('ws://localhost:8001/ws?user_id=user123');
// 每 5 秒发送 ping
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({aid: 'ping'}));
}
}, 5000);
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.aid === 'pong') {
console.log('Heartbeat OK');
}
};
自动重连:
function connectWebSocket() {
const ws = new WebSocket('ws://localhost:8001/ws?user_id=user123');
ws.onclose = () => {
console.log('Connection closed, reconnecting...');
setTimeout(connectWebSocket, 1000);
};
return ws;
}
const ws = connectWebSocket();
Q19: WebSocket 消息延迟高
症状: 成交后 1-2 秒才收到通知
原因:
- 未发送
peek_message
- 通知队列积压
- 网络延迟
解决方案:
正确实现 peek_message 机制:
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.aid === 'rtn_data') {
// 处理数据更新
processUpdate(msg.data);
// 立即发送下一个 peek_message
ws.send(JSON.stringify({aid: 'peek_message'}));
}
};
// 连接后立即发送第一个 peek_message
ws.onopen = () => {
ws.send(JSON.stringify({aid: 'peek_message'}));
};
检查通知队列:
# 查看日志
grep "notification queue" logs/qaexchange.log
# 应该看到类似:
# [INFO] notification queue size: 5 (< 500 threshold)
监控延迟:
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
const now = Date.now();
const latency = now - msg.timestamp;
console.log('Notification latency:', latency, 'ms');
};
Q20: WebSocket 收不到成交通知
症状: 订单成交但 WebSocket 未收到 Trade 通知
原因:
- 未订阅通知频道
- user_id 不匹配
- NotificationGateway 未连接
解决方案:
检查 WebSocket 连接参数:
// 确保 user_id 与下单时的 user_id 一致
const ws = new WebSocket('ws://localhost:8001/ws?user_id=user123');
订阅交易频道:
ws.onopen = () => {
// 订阅全部频道
ws.send(JSON.stringify({
aid: 'subscribe',
channels: ['trade', 'account', 'position', 'order']
}));
ws.send(JSON.stringify({aid: 'peek_message'}));
};
检查通知是否发送:
# 查看日志
grep "Sending Trade notification" logs/qaexchange.log
手动查询成交记录:
curl http://localhost:8000/api/order/user/user123
性能问题
Q21: 订单吞吐量低于预期
症状: 只能达到 10K orders/s,远低于 100K 目标
原因:
- 单线程提交
- HTTP 连接复用不足
- 预交易检查耗时过多
解决方案:
并发提交订单:
#![allow(unused)] fn main() { use tokio::task; let mut handles = vec![]; for i in 0..1000 { let handle = task::spawn(async move { submit_order(order).await }); handles.push(handle); } for handle in handles { handle.await.unwrap(); } }
使用连接池:
#![allow(unused)] fn main() { use reqwest::Client; let client = Client::builder() .pool_max_idle_per_host(100) // 连接池大小 .build()?; }
禁用预交易检查(测试环境):
#![allow(unused)] fn main() { // src/exchange/order_router.rs pub async fn submit_order_fast(&self, order: QAOrder) -> Result<String> { // 跳过 pre_trade_check self.matching_engine.submit_order(order).await } }
压测示例:
# 使用 Apache Bench
ab -n 100000 -c 100 -p order.json -T application/json \
http://localhost:8000/api/order/submit
Q22: 撮合延迟过高
症状: P99 延迟 > 1ms,远高于目标 100μs
原因:
- 锁竞争
- Orderbook 实现效率低
- Debug 模式运行
解决方案:
使用 Release 模式:
cargo build --release
cargo run --release --bin qaexchange-server
# 性能提升 10-100x
检查是否使用 qars Orderbook:
#![allow(unused)] fn main() { // src/matching/engine.rs use qars::qamarket::matchengine::Orderbook; // ✓ 正确 // 不要自己实现 Orderbook }
减少锁粒度:
#![allow(unused)] fn main() { // 不好: 长时间持有锁 let mut orderbook = self.orderbook.write(); orderbook.submit_order(order); orderbook.process(); drop(orderbook); // 好: 尽快释放锁 { let mut orderbook = self.orderbook.write(); orderbook.submit_order(order); } // 锁在此处自动释放 self.process_trades(); }
性能分析:
# 使用 flamegraph
cargo install flamegraph
sudo flamegraph target/release/qaexchange-server
# 查看 flamegraph.svg 找出热点
Q23: 内存占用过高
症状: 运行一段时间后内存占用超过 10GB
原因:
- MemTable 未及时 Flush
- 通知队列积压
- 订单/成交记录未清理
解决方案:
配置 MemTable 自动 Flush:
#![allow(unused)] fn main() { // src/storage/memtable/oltp.rs pub const MEMTABLE_FLUSH_SIZE: usize = 64 * 1024 * 1024; // 64 MB pub const MEMTABLE_FLUSH_INTERVAL: Duration = Duration::from_secs(300); // 5 分钟 }
清理历史订单:
#![allow(unused)] fn main() { // 定期清理已完成订单 pub fn cleanup_old_orders(&self, before: i64) { self.orders.retain(|_, order| { order.timestamp > before }); } }
监控内存:
# 实时监控
watch -n 1 'ps aux | grep qaexchange | grep -v grep'
# 内存分析
cargo install valgrind
valgrind --tool=massif target/release/qaexchange-server
限制通知队列大小:
已实现背压控制,参见 NotificationGateway::BACKPRESSURE_THRESHOLD = 500
Q24: CPU 使用率过高
症状: CPU 占用持续 100%
原因:
- 忙等待循环
- 无限重试
- 日志输出过多
解决方案:
避免忙等待:
#![allow(unused)] fn main() { // 不好: 忙等待 loop { if condition { break; } } // 好: 使用 sleep loop { if condition { break; } tokio::time::sleep(Duration::from_millis(10)).await; } }
减少日志输出:
# 只记录 WARN 及以上级别
RUST_LOG=qaexchange=warn cargo run --release
检查无限循环:
# 使用 perf 分析 CPU 热点
sudo perf record -g target/release/qaexchange-server
sudo perf report
数据和存储
Q25: 数据恢复失败
症状: 重启后账户数据丢失
原因:
- WAL 文件损坏
- WAL 回放失败
- 未调用 Checkpoint
解决方案:
检查 WAL 文件:
ls -lh data/wal/
# 确保有 .wal 文件
手动回放 WAL:
# 启用详细日志
RUST_LOG=qaexchange::storage=debug cargo run --bin qaexchange-server
# 查看回放过程
grep "Replaying WAL" logs/qaexchange.log
验证 WAL 完整性:
#![allow(unused)] fn main() { // 检查 CRC32 校验 pub fn verify_wal(&self, file_path: &str) -> Result<bool> { // ... CRC 验证逻辑 } }
定期 Checkpoint:
#![allow(unused)] fn main() { // 每小时创建一次 Checkpoint tokio::spawn(async move { let mut interval = tokio::time::interval(Duration::from_secs(3600)); loop { interval.tick().await; checkpoint_manager.create_checkpoint().await; } }); }
Q26: WAL 文件过大
症状: data/wal/
目录占用超过 100GB
原因:
- 未清理旧 WAL
- Checkpoint 未及时创建
- 高频交易写入
解决方案:
配置 WAL 清理策略:
#![allow(unused)] fn main() { // src/storage/wal/manager.rs pub const WAL_RETENTION_DAYS: u64 = 7; // 保留 7 天 pub fn cleanup_old_wal(&self) { let cutoff = Utc::now() - Duration::days(WAL_RETENTION_DAYS); // ... 删除旧文件 } }
手动清理:
# 查看 WAL 文件大小
du -sh data/wal/
# 删除 7 天前的 WAL
find data/wal/ -name "*.wal" -mtime +7 -delete
WAL 压缩:
# 压缩旧 WAL
gzip data/wal/*.wal.old
创建 Checkpoint 后清理:
#![allow(unused)] fn main() { pub fn create_checkpoint(&self) -> Result<()> { // 1. 创建 Checkpoint self.create_snapshot()?; // 2. 清理已 Checkpoint 的 WAL self.cleanup_wal_before(checkpoint_lsn)?; Ok(()) } }
Q27: SSTable 查询慢
症状: 查询历史数据耗时 > 1 秒
原因:
- 未使用 Bloom Filter
- SSTable 文件过多
- 未使用 mmap
解决方案:
启用 Bloom Filter:
#![allow(unused)] fn main() { // src/storage/sstable/oltp_rkyv.rs pub fn build_with_bloom_filter(&self) -> Result<()> { let bloom = BloomFilter::new(10000, 0.01); // 1% FP rate // ... 构建 Bloom Filter } }
触发 Compaction:
# 查看 SSTable 文件数
ls data/sstable/ | wc -l
# 如果 > 100,手动触发 Compaction
curl -X POST http://localhost:8000/api/admin/compaction/trigger
启用 mmap:
#![allow(unused)] fn main() { // src/storage/sstable/mmap_reader.rs pub fn open_with_mmap(path: &Path) -> Result<Self> { let file = File::open(path)?; let mmap = unsafe { MmapOptions::new().map(&file)? }; // ... 零拷贝读取 } }
查询优化:
#![allow(unused)] fn main() { // 使用 Polars LazyFrame let df = LazyFrame::scan_parquet(path)? .filter(col("timestamp").gt(start_time)) .select(&[col("order_id"), col("volume")]) .limit(100) .collect()?; }
Q28: Parquet 文件损坏
症状: 读取 OLAP 数据时报错 "Invalid Parquet file"
原因:
- 写入中途崩溃
- 磁盘错误
- 格式不兼容
解决方案:
检查文件完整性:
# 使用 parquet-tools
pip install parquet-tools
parquet-tools inspect data/sstable/olap/xxx.parquet
删除损坏文件:
# 备份
cp data/sstable/olap/xxx.parquet data/backup/
# 删除
rm data/sstable/olap/xxx.parquet
# 从 WAL 重建
cargo run --bin recover-from-wal
启用写入校验:
#![allow(unused)] fn main() { use parquet::file::properties::WriterProperties; let props = WriterProperties::builder() .set_compression(Compression::SNAPPY) .set_write_batch_size(1024) .build(); }
故障排查
Q29: 如何查看系统运行状态
解决方案:
健康检查:
curl http://localhost:8000/health
系统监控:
curl http://localhost:8000/api/monitoring/system
返回:
{
"accounts_count": 100,
"orders_count": 1500,
"trades_count": 500,
"ws_connections": 50,
"memory_usage_mb": 512,
"cpu_usage_percent": 25.5,
"uptime_seconds": 3600
}
存储监控:
curl http://localhost:8000/api/monitoring/storage
返回:
{
"wal_size_mb": 128,
"memtable_size_mb": 32,
"sstable_count": 15,
"sstable_total_size_mb": 1024
}
Q30: 如何开启调试日志
解决方案:
环境变量:
# 全局 DEBUG
RUST_LOG=debug cargo run
# 仅特定模块
RUST_LOG=qaexchange::matching=debug,qaexchange::storage=trace
# 包含依赖库
RUST_LOG=debug,actix_web=info
代码中设置:
#![allow(unused)] fn main() { // src/main.rs env_logger::Builder::from_default_env() .filter_module("qaexchange::matching", log::LevelFilter::Trace) .filter_module("qaexchange::storage", log::LevelFilter::Debug) .init(); }
日志格式:
#![allow(unused)] fn main() { env_logger::Builder::from_default_env() .format(|buf, record| { writeln!( buf, "[{} {} {}:{}] {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"), record.level(), record.file().unwrap_or("unknown"), record.line().unwrap_or(0), record.args() ) }) .init(); }
Q31: 如何抓取 WebSocket 通信内容
解决方案:
浏览器开发者工具:
- 打开 Chrome DevTools (F12)
- 切换到 Network 标签
- 筛选 WS (WebSocket)
- 查看 Messages 标签
使用 Wireshark:
# 安装 Wireshark
sudo apt-get install wireshark
# 捕获本地回环
sudo wireshark -i lo -f "tcp port 8001"
代码中记录:
#![allow(unused)] fn main() { // src/service/websocket/session.rs fn handle_text(&mut self, text: &str, ctx: &mut Self::Context) { log::debug!("WS Received: {}", text); // 记录接收 let response = process_message(text); log::debug!("WS Sending: {}", response); // 记录发送 ctx.text(response); } }
Q32: 性能分析工具推荐
解决方案:
CPU 分析:
# flamegraph
cargo install flamegraph
sudo flamegraph --bin qaexchange-server
# perf (Linux)
sudo perf record -g target/release/qaexchange-server
sudo perf report
内存分析:
# valgrind
valgrind --tool=massif target/release/qaexchange-server
ms_print massif.out.<pid>
# heaptrack (更快)
heaptrack target/release/qaexchange-server
heaptrack_gui heaptrack.qaexchange-server.<pid>.gz
异步任务分析:
# tokio-console
cargo install tokio-console
# 代码中启用
#[tokio::main]
async fn main() {
console_subscriber::init();
// ...
}
# 运行 console
tokio-console
网络分析:
# tcpdump
sudo tcpdump -i lo port 8000 -w capture.pcap
# 分析
wireshark capture.pcap
开发问题
Q33: 如何运行单元测试
解决方案:
运行所有测试:
cargo test
运行特定模块测试:
cargo test --lib matching
cargo test --lib storage
运行单个测试:
cargo test test_submit_order
显示测试输出:
cargo test -- --nocapture
并行测试:
# 默认并行
cargo test
# 单线程运行(避免资源竞争)
cargo test -- --test-threads=1
测试覆盖率:
# 安装 tarpaulin
cargo install cargo-tarpaulin
# 生成覆盖率报告
cargo tarpaulin --out Html
Q34: 如何添加新的 HTTP 端点
解决方案:
1. 定义请求/响应模型:
src/service/http/models.rs
:
#![allow(unused)] fn main() { #[derive(Debug, Serialize, Deserialize)] pub struct MyRequest { pub user_id: String, pub data: String, } #[derive(Debug, Serialize, Deserialize)] pub struct MyResponse { pub result: String, } }
2. 实现处理函数:
src/service/http/handlers.rs
:
#![allow(unused)] fn main() { pub async fn my_handler( req: web::Json<MyRequest>, app_state: web::Data<AppState>, ) -> Result<HttpResponse, Error> { // 业务逻辑 let result = process_request(&req.user_id, &req.data)?; Ok(HttpResponse::Ok().json(MyResponse { result: result.to_string(), })) } }
3. 注册路由:
src/service/http/routes.rs
:
#![allow(unused)] fn main() { pub fn config(cfg: &mut web::ServiceConfig) { cfg.service( web::resource("/api/my-endpoint") .route(web::post().to(my_handler)) ); } }
4. 测试:
curl -X POST http://localhost:8000/api/my-endpoint \
-H "Content-Type: application/json" \
-d '{"user_id": "user123", "data": "test"}'
Q35: 如何扩展 WebSocket 消息类型
解决方案:
1. 定义新消息类型:
src/service/websocket/messages.rs
:
#![allow(unused)] fn main() { #[derive(Debug, Serialize, Deserialize)] #[serde(tag = "aid")] pub enum ClientMessage { #[serde(rename = "peek_message")] PeekMessage, #[serde(rename = "my_new_message")] MyNewMessage { param1: String, param2: i64, }, } }
2. 实现处理逻辑:
src/service/websocket/session.rs
:
#![allow(unused)] fn main() { fn handle_client_message(&mut self, msg: ClientMessage, ctx: &mut Self::Context) { match msg { ClientMessage::PeekMessage => { // 现有逻辑 } ClientMessage::MyNewMessage { param1, param2 } => { // 新消息处理 let result = self.process_new_message(param1, param2); ctx.text(serde_json::to_string(&result).unwrap()); } } } }
3. 客户端发送:
ws.send(JSON.stringify({
aid: 'my_new_message',
param1: 'test',
param2: 123
}));
Q36: 如何调试 qars 依赖问题
症状: qars 行为不符合预期
解决方案:
查看 qars 源码:
cd ../qars2
code src/qaaccount/account.rs
修改 qars 并测试:
cd ../qars2
# 修改代码
vim src/qaaccount/account.rs
# 在 qaexchange 中测试
cd ../qaexchange-rs
cargo build --lib
使用 qars 的测试:
cd ../qars2
cargo test qa_account
查看 qars 版本:
grep "qars" Cargo.toml
# qars = { path = "../qars2" }
cd ../qars2
git log -1
临时使用其他 qars 版本:
[dependencies]
# qars = { path = "../qars2" }
qars = { git = "https://github.com/QUANTAXIS/qars", branch = "dev" }
常用命令速查
编译和运行
# 编译库
cargo build --lib
# 编译服务器
cargo build --bin qaexchange-server
# 运行(debug)
cargo run --bin qaexchange-server
# 运行(release)
cargo run --release --bin qaexchange-server
# 运行示例
cargo run --example client_demo
测试
# 所有测试
cargo test
# 特定测试
cargo test test_name
# 显示输出
cargo test -- --nocapture
# 测试覆盖率
cargo tarpaulin
API 测试
# 健康检查
curl http://localhost:8000/health
# 开户
curl -X POST http://localhost:8000/api/account/open \
-H "Content-Type: application/json" \
-d '{"user_id": "user123", "initial_balance": 100000}'
# 下单
curl -X POST http://localhost:8000/api/order/submit \
-H "Content-Type: application/json" \
-d '{
"user_id": "user123",
"order_id": "order001",
"instrument_id": "SHFE.cu2501",
"direction": "BUY",
"offset": "OPEN",
"volume": 1,
"price_type": "LIMIT",
"limit_price": 50000
}'
# 查询账户
curl http://localhost:8000/api/account/user123
# 查询持仓
curl http://localhost:8000/api/position/user123
日志和监控
# 查看日志
tail -f logs/qaexchange.log
# 开启 DEBUG 日志
RUST_LOG=debug cargo run
# 系统监控
curl http://localhost:8000/api/monitoring/system
# 存储监控
curl http://localhost:8000/api/monitoring/storage
获取帮助
如果以上方案无法解决您的问题,请:
- 查看详细文档:
docs/03_core_modules/
- 查看示例代码:
examples/
- 提交 Issue: GitHub Issues
- 加入社区: QQ群 或 Discord
版本: v1.0.0 最后更新: 2025-10-06 维护者: QAExchange Team