第三方库/框架
- C++ ^ea87cf
- async_simple C++异步框架 C++20
- websocket WebSocket客户端和服务器
- unordered_dense 高速unordered_map和unordered_set
- cinatra 高性能HTTP框架
- glaze 高速JSON库 C++23
- fast_double_parser 高速double解析
- Seqlock 高性能顺序锁
- Python
- orjson 高速JSON库
项目启动 main.py
通过参数调用函数
通过获取 main.py 的启动参数来调用不同的函数 进行编译等操作
编译C++代码
- 清理旧的 build bin lib 目录
- 切换到build目录然后
cmake ..os.system("cmake ..")os.chdir("build") - 最后使用
make编译C++代码 有返回值 如果是0则表示编译完成
启动项目
- 先kill掉以前的进程 休眠1秒
- 查看是否需要rebuild
- 创建tmux的会话来达到创建进程的目的 123
bash守护进程脚本
主要是为了监控进程状态
while true; do:无限循环,执行指定的程序并等待其退出。- 如果程序退出码是 0,表示正常退出;如果是 130,表示用户通过 Ctrl+C 中断,脚本会结束。
- 如果程序被系统杀死(退出码 137,OOM Killer),脚本会尝试清理 Node.js 进程。
- 如果程序崩溃,则会等待 0.1 秒后重启程序。
针对每一个交易所都会启动一个监控和进程
核心程序
- [[#加载配置文件|加载配置文件]]
- [[#初始化共享内存|初始化共享内存]]
- 启动WebSocket服务器 监听客户端请求
加载配置文件
使用的是[[#^ea87cf|glaze框架]]解析json文件
初始化共享内存
- 通过[[#MemoryManager类]]来管理多个进程 不同语言之间的共享内存
- 这里调用时直接给server核心进程分配共享内存
- 方便后续其他进程需要共享内存时直接进行分配
- 初始化数据结构
- 获取配置文件中所有的交易所名称和交易对名称 并且进行去重
- 这里去重使用方法是这样的
- 先将列表排序
sort(vec.begin(), vec.end());- 算法里的sort用的是一种混合排序
- 大多数情况用的是快排
- 元素少的时候用插入排序
- 递归深度过深的时候变成堆排序
- 算法里的sort用的是一种混合排序
- 去除相邻的重复元素
vec.erase(unique(vec.begin(), vec.end()), vec.end());unique是用来移动的 把相邻的重复元素全部放到最后 返回值是最后一个有效值的尾部迭代器 也就是第二个3的迭代器
- {1, 1, 2, 3, 3, 4, 4} => {1, 2, 3, 4, 3, 4, 4}
- 可以用双指针来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19template<class Iterator>
Iterator unique(Iterator first, Iterator last)
{
if(first == last) // 如果是空容器 直接返回
return last;
Iterator result = first; // result 用于指向去重的最后一个元素
++ first; // 从第二个元素开始遍历比较 此时的first是待比较的元素
while(first != last)
{
// 因为result之前的元素一定是不重复的,所以可以直接用result位置的元素和first待比较的元素进行比较
if(*result != *first) // 如果不同 就移动到result的下一个位置
{
++result;
*result = *first;
}
++ first;
}
return ++result;
}
然后再用erase进行移除
好处就是不需要额外开空间
- 先将列表排序
- 初始化交易窗口和对照信息
- 对每个交易所 交易对 交易窗口 生成唯一的id
- 分配内存
MemoryManager类
其实就相当于一个简单的共享内存池,管理的内存单位是字节
成员变量
- base_ptr 指向进程地址空间的共享内存区域的指针
- total_size 共享内存总大小
- allocations 一个小的std::map 用于存储已经分配的内存区域的名称和对应偏移量的大小 ^1a71d1
- 是从string -> Allocation的映射
- Allocation 主要包含了offset和size 标记起始位置和偏移量
- last_pos 记录上一次分配内存的结束位置 确保内存分配不会重叠
- shm_name 共享内存的名称 用于创建共享内存的区域
成员函数
- 构造函数
- shm_open, shm_unlink 可以申请和释放指定名称的共享内存文件对象,返回值是一个文件描述符
- ftruncate 用来更改共享内存对象大小 也就是给共享内存对象申请空间
- mmap 内存映射 用于将共享内存对象中的内容映射到进程地址空间 获取到共享内存的指针
- 析构函数
- munmap释放共享内存
- allocate
- 用于分配一块共享内存并且指定名称
- 说是分配 其实核心原理就是把指定大小的内存置零 然后返回分配后的起始位置指针
- get_ptr
- 获取指定名称的共享内存
- 这里是通过上面的[[#^1a71d1|allocations]]的map 来获取到对应的共享内存块 然后在通过共享内存的基地址+偏移量 返回指定共享内存块的地址
- get_names
- 获取所有的内存块名称
- get_offset
- 获取指定内存块的偏移量
LockfreeArray类
高效无锁的并发数据访问模式,实现高效的读写操作,采用version确保在并发读写过程中的数据一致性
项目问题
- 实习的工作内容是什么
- 进行交易所的API对接
- 完成部分重构工作
- 实现一个Websocket的pool
- 整个交易系统你做了什么优化
- 将cerr输出日志改为使用CLOG输出日志
- 尽可能使用Websocket进行数据订阅和请求发送
- 将Python代码转为C++代码和Rust代码
- 调用延迟从0.5ms降低到小于0.1ms
- 你觉得还有哪些可以优化的点
1 | gantt |