转码服务器逻辑上分为3层,驱动层、数据层和服务层。
驱动层(DriverIO):管理和连接各市场二级驱动(Dll),为数据层提供驱动原始数据。
数据层(DataIO):主要提供实时数据和历史数据的转码,把驱动源数据转为行情数据,并保存在内存或文件中。
服务层(RequestIO、UserIO):为客户端(或下级服务器)提供行情数据的请求和实时数据推送。从数据层获取数据组包发送到客户端。
此外,通过一个工作线程(ProcessIO)对系统状态进行维护和管理。
转码服务器的运行与2个时间相关,行情时间与本机时间。原则是本机状态的变化一律以本机时间为标准。而行情时间只用于计算转码,与系统状态无关。系统状态如图:
转码机包括正在初始化、行情中、正在收盘、盘后四个状态,状态变化由工作线程判断本机时间触发,比如上午9:00初始化、下午16:00收盘。其中初始化状态断开客户端(不断开下级Cache服务器),不提供服务。收盘状态不提供历史数据的请求。
驱动模块主要是连接“传输系统驱动”和“转码系统数据转换模块”的桥梁。他是将驱动整合后向转换模块提供数据。对职责初步总结如下:
注意:
首先,需要说明的是每一个驱动都有以下几种状态:
在驱动主要线程中有设置当前驱动的流程,下面对这个流程描述如下:
驱动DLL推送时要调用响应函数,驱动模块推送时也要调用转码模块的推送函数,他们之间关系如下图:
因为使用的驱动有多个,且可能来源不同。甚至不同来源中商品数量、类别、类别的价格放大倍数也不相同,所以为了保障数据的统一性,必须确定标准的码表、类别、价格放大倍数等。在这里统称为标准码表。
标准码表为启动后第一次从正常驱动中获取的码表、市场信息等数据。一旦确立,就不能进行修改(除了全部重新初始化、停止驱动模块等)。
注意:转码服务程序会在每一个交易日初始化时启动驱动模块,并且在该交易日结束时停止该模块。
1.1市场信息 MMarketInfo
1.2名称代码表 MNameTable
1.3行情快照 MBaseData
1.4明细 MTickData
1.5走势 MTrendData
实时数据是指,转码机定时将内存中的数据快照保存到文件中,如果转码机关闭,或异常停止,可以从这些实时数据文件中恢复,使数据的丢失减到最小。实时数据文件包括:
infodata.xxx:市场信息
infodata.xxx_bak:市场信息备份文件
namedata. xxx:名称代码表
namedata. xxx _map:名称代码表映射文件
nowdata. xxx:行情快照
stock300. xxx:300参数文件
tickdata. xxx:明细文件
hisdata. xxx:走势文件
更加详细的内容,可以查看相关目录文件的文档。
数据层历史数据不保留内存,只提供接口可以获取指定一段历史数据。定位历史数据不再创建索引,而是使用和缓存模块相同的算法“二分查找”来定位数据。具体算法可以查看《历史数据缓存模块设计》文档。它们算法之间的细微差别在于:通过历史文件获取数据使用文件指针定位,在获取分钟线时,调整为以天为单位获取历史数据。
为了支持转码机的扩展性,服务层留出插件的接口,支持实时行情数据的推送,以及各种实时和历史数据的请求接口。插件所有数据的推送和获取都通过函数的方式传递内存。
为了让数据层接口与协议无关,所有获取数据的接口一律返回原始内存数据的拷贝,然后由服务层把内存数据拼为请求协议的结构。数据请求统一从MDataIO获取数据,由MdataIO统一维护各个数据模块的数据存取。
为了防止服务层需要回头调用数据的情况,更新内存行情快照时,同时把更新后的快照数据memcpy出来供服务层推送。
其中BaseData更新内存数据时,会同时把内存数据copy到buf,这样在服务层就可以直接使用内存数据。Buf之所以定义为非特定数据指针,好处是比较灵活,以后其他类型动态包也可以如此处理。只是需要在代码中作足够的注释,并且同时维护数据层和服务层的相应处理部分的代码。
为了节省磁盘IO,并不是每次推送数据都更新到realtime文件,而是由工作线程定时触发写文件。其中走势、明细数据从内存中另外开辟一小块缓冲区,用于保存未写入文件的数据。
服务层包括RequestIO(数据请求)、CacheIO(历史数据缓存)、UserIO(数据主动推送)。
请求模块由MRequestIO组成,MRequestIO主要包含一个注册请求接口,以及若干个协议相关的具体函数。为了尽量避免重复代码,所以每个请求协议不再单独占用一个请求函数,而是由统一的一个处理函数来处理,每个协议只提供获取指定祯数据的函数,供请求处理调用。(除了一些特殊请求需要单独处理,比如登录、历史数据请求)
这样,增加一个请求协议时,只需要增加两个数据相关的函数就可以了,避免了每个协议都需要写重复的外围代码。
具体Cache详细方案请看Cache模块详细设计文档。
用户信息以分组的形式保存,每个用户组中,包含了登录用户信息,市场商品信息,并用链表把他们串联起来,除此之外下级服务器的信息单独用一个列表维护,如图:
每个用户组之中,MarketPushData包含整个市场的所有商品,同时每个用户有一个UserPushData数组,包含固定数量的商品推送信息(比如20个)。MarketPushData的每个商品连接出一个链表,该链表把注册了该商品的用户推送信息串起来。
当用户注册某个商品时,从UserPushData中提取出一个未使用的PushData单元,把该PushData填入该商品信息,并插入该商品链表的头部,如图:
当用户取消商品推送时,通过商品序号找到商品链表,把PushData从该链表剥离。
当商品实时行情快照到来时,同样通过商品序号找到商品链表,遍历该链表,通过链表上的PushData信息向客户端推送数据。
各市场有区别,请参照具体文档。