首先说明一下,我并没打算把这个项目设计的多么高大上。一个最简单的理由就是我没有那么多资源。比如做架构设计,要考虑计算机性能、数据库主从备份、服务多点部署和一些容灾问题,而这些都需要机器。但是我只有一台机器,所以也只能尽可能将这台机器的性能榨干,而主从、多点部署都问题就不能涉及了。(转载请指明出于breaksoftware的csdn博客)
架构
为了比较贴近生产环境,我将架构设计如下图
鉴于实际情况,我并没有在数据库上做主从同步,因为在一台机器做主从意义不大。同时上图中所有服务都部署在一台4核、2.8G主频,8G内存,128G磁盘的机器上。
实时抓取服务会在每个交易日的开始和结束时间之间定时抓取数据,保存到实时数据库中。同时它也会把信息同步给实时计算服务。我统计过,一天的数据量不足500M,所以实时计算服务可以让信息常驻内存,有利于其运行效率提升。如果实时计算服务崩溃,则它可以从实时数据库中加载当天的数据恢复运行。
在交易时间,更快地抓取数据,实时抓取服务和实时数据库操作会占满机器的CPU资源。过了交易时间后,则会通过离线计算服务,把实时数据库中的数据经过计算保存到历史数据库中。
设计
数据库结构的设计
有人可能会问,为什么要区分实时数据库和历史数据库?要回答这个问题,我需要先介绍下我得设计思路和遇到的问题。
首先,我希望在有限的资源内,实时数据获取的频度高、间隔短。这样可以保证我们数据的时效性。每次拉取时,我都是获得尽可能多的数据,然后经过简单的数据分拆,通过批量insert的方式保存到数据库中。这样的流程可以减少和数据源的交互时间、也大大减少数据库的操作时间。
由于实时数据库中同类型数据都保存在一张表中,也就是说所有股票的同类型数据都在一张表中,这种设计就是为了高效记录实时数据。而在对历史数据的分析时,这样的设计明显不合适。于是我让每支股票的信息保存在一张表中。在交易时间之后,机器处于空闲状态时,离线计算服务会把实时数据插入到其对应的表中。这样既可以让数据获取比较高效,也让数据安全性得以提升。
目前我的设计中,一支股票有四张表保存不同的信息,分别是:历史真实数据、除权后数据、主力行为数据和交易详情数据。以A股3千多支股票计算,则会产生一万多张表。我曾尝试过在一个数据库中保存几百个表,实际发现,随着表数量增加,整个库的访问都会变得很慢。于是拆库的问题摆在前言。还好每支股票都有唯一的代码,我将其代码通过取模的方式将它们拆分到300个库中,这样每个库里表数量不足100个,可以保证数据库的访问效率。
至于具体的表定义,我们将在之后的博文中做介绍。
程序结构的设计
“实时抓取”和“离线计算”是我们程序的核心功能。它们具有如下特点:操作数据库、记录日志。在设计程序结构时,我希望相应模块可以独立工作,这样可以让不同任务公用相同的模块。于是我划分出如下模块:
- 配置管理器。负责读取和管理配置。它是以单例形式存在的。
- 日志管理器。它负责管理日志的输出方式。
- 正则表达式管理器。因为抓取的内容需要通过正则表达式去提取,所以设计了一个单例模块用于辅助该功能实现和使用。
- 数据库管理器。我们的数据被分布在不同的数据库中,所以需要一个独立的数据库管理模块让底层分库等复杂操作对上层隐藏。
- 普通任务管理器。“实时抓取”和“离线计算”都是普通任务,这些任务统一通过该管理器进行管理。
- 系统任务管理器。因为配置文件可能在程序运行时发生改变,所以我们需要定期去检查改变,并通知各管理根据改变的内容做相应调整。
普通任务管理器和系统任务管理都需要一个调度框架来支持。我没有去造轮子,而是选用了Advanced Python Scheduler。它的详细文档可以见http://apscheduler.readthedocs.io/en/latest/。
最后我们看下工程的目录结构
- conf目录用于保存框架和策略使用的各个配置文件。其下table_template用于保存创建数据库中表的SQL模板。
- log目录用于保存运行时产生的各种日志文件。
- src保存的是工程所有代码。其下frame保存的是框架代码;strategy保存的是各个抓取策略的代码,其中还包括一部分离线计算的代码;tools保存的是工具性的代码,比如对数据库进行统一的某种操作。
经过这两篇博文对数据源、架构结构的介绍,我们大致可以看出整个项目的设计。接下来我将介绍各个模块的实现。