Golang 由于其高效快速、天然支持并发的显著优势,大大降低了服务的开发门槛,使得企业更易于直接架构业务,这些特性使得越来越多的传统公司技术部门选择拥抱 Go ,并结合自身业务特点摸索出了丰富的实践经验。在由 Go 中国和七牛云联合主办的 Gopher China2017中,来自金大师的技术经理张泽武详细分享了一个使用 Go 语言开发全套证券、期货、行情处理及分发系统。项目背景是在团队成员未完全到位的情况下,使用简单易用的 Go 语言后,用三个月的时间开发完成。并在随后的项目发展中,这套系统逐步演化完善成一套自有的快速开发框架。
项目故事
项目启动的时候,领导对这个行情系统有几个要求,怎么在最短的时间交付,怎么满足大量并发的请求,怎么保证低延时,把交易所的行情数据尽可能快的发到客户端。在这些基础的要求达到后,后面再接着开发分时线、K线、逐笔、指标等数据服务。
理顺这些步骤后,我们开始考虑当下面临亟待处理的问题,有以下几个:
1、怎么接入数据源,直接接交易所的数据,还是从二级平台去接,
2、怎么提高行情速率,希望尽可能减少行情数据在服务端的流转。
3、要考虑分时、K线、指标数据的计算服务,
4、接入服务节点设置是1万,
5、节点是十一前要交付。
接下来是组建团队。组建团队也有几个问题。首先是团队开发语言选什么,我期望最好是C++的,因为我自己有很长时间的C++经历,项目初期选择C++可以更快速的启动。二是最好能招到有行业经验的开发,有证券、期货从业经验,知道是业务是怎么会回事。三是要有服务端的经验,因为行情系统就是一个服务。如果一直招不到符合以上这些条件的怎么办,到最后我想着,炒过股票的也行,至少懂一点业务知识。在招聘的过程中,每发现一个有Go开发经验的人,都特别开心。这是团队组建初期的一些体验。
我们的第一个工程师是6月9号到岗,到8月10号第四位伙伴加入我们。在这样陆续到岗的情况下,还要十一前要完成交付,压力可想而知。所以当到岗两位C++伙伴后,我争取到用Go来开发这套行情系统。我们在7月做一些基础服务的开发,如接入服务、计算服务、数据源采集,接到黄金交易所的行情数据。到8月的时候,把这些基础的服务做一些集成、调试。9月做了测试发布,虽然还带着一些小问题,但上线了,初步完成了目标。
总结起来是因为选择了Go才能如若交付。当时老板的担心是用了Go以后,后来招人是个难题。但是用C++就那么几个人3个月是做不完的。我选择Go还有一个很重要的原因,因为当时刚参加完第一次的Gopher大会,得到了鼓舞,也特别有信心,就这么坚持下来了。在此特别感谢Asta谢。
行情系统
行情是什么?它是即时报价,在报价的这个时间点过去后,即时报价就变成了一个历史报价。历史报价会有一些统计、加工处理需要。举个例子,假如说我要买土豆,这是一个用户需求。我问老板土豆多少钱一斤,这是我的询价行为,老板回答3块钱一斤,这是老板报的价格。当价格发生变化的时候,老板通知了我,这个就是行情服务。有很多人同时问老板,土豆青菜分别多少钱,这时候它是一个并发的询价行为,老板直接回复已经吃不消了,就把你的通讯方式留下来,当价格发生变化的时候再来通知你,这是行情订阅服务。
证券行情主要来自于交易所交易后产生的数据,我们一般拿到的数据是实时成交的即时数据。在成交数据拿到后再加工变成其他的数据,这就是行情系统要做的事情。
行情系统有哪些要求呢?要快、准、稳。快是行情要响应快、延时低,如果慢了,用户价值会下降,而且会引发问题。准是指数据不能有偏差,发过来数据往下传的时候,加工处理要准确。还有可能发过来的数据不正确,数据服务商的数据有问题,数据质量不过关,要做容错处理。国外的节假日也会对数据有影响,这都是要行情系统要考虑的,把垃圾数据过滤掉。稳是指服务要稳定,可用性最少要99.99%。
说一下行情系统设计的特性要求。并发的要求是指当用户规模发生变化的时候,系统应当要具备弹性伸缩能力。项目上线初期数据量不是很大的时候,并发压力还不是很大,随着项目的推广,并发压力就会上来。容错要求是指一旦发生故障时,行情系统能否把故障的影响范围控制在局部,不影响整体服务。不能因为某一个服务出问题,导致整个服务出问题。故障响应要求是指一旦故障出现,且无法控制在局部时,能不能快速恢复恢复。这个要求一定程度上可以通过运维手段解决,如果在前期架构设计时提供支持,后期运维压力会小很多。
下面介绍一下系统服务设计,第一部分是接入服务,主要是面向客户端,解决高并发、高在线的需求。第二部分计算服务,主要是加工行情数据,拿到行情数据进行计算,计算后落存储要持久化,按照数据的特性分类管理 。第三部分是采集服务,我们接了比较多的交易所,这些市场提供的接口和数据的组织的方式各不一样,协议也有很大差别。这些差异就在采集服务中来处理,我们定一套内部的数据协议,在采集的过程中,把外部协议转换成内部协议。
以上把大体的服务做了一些分类后,清晰了不少。接下来是一些开发工作,为了让开发人员能尽快上手,要让开发人员更专注到自己的工作中,降低开发门槛。于是我把开发要用到的库,作了一些抽象、归类,把相同的内容和业务做了一些库化。这里分了八类。第一个main.go,它把常规的初始化动作,包括监控、统计、外部命令、一些参数、退出的机制,做了一些约定固化。当要进行一个新功能开发的时候,拿到 main.go 后在它的框架下面直接做业务逻辑就可以了,不用再关心周边的内容。
服务在运行的状态下,是会需要进行一些控制,命令框架用来做这个事。我们在处理行情计算的时候,收到行情数据是即时行情,同时会要并行计算分时线、分钟K线,日K、周K、月K等数据,这中间的调度功能,也把它库化了。配置库是指我们很多服务属性是相同的,把它加载的过程和配置描述的方式定下来以后,形成一个配置库,其他的开发可以参照这个,可以很快的把他需要的配置加进来。还有一个工具库,放一些比较基础的工具。
有这样一个框架性的东西后,新来的同事非常容易上手。
接下来是跟业务相关的基础业务库。在前面几个通用库的基础上进行组装后,可以去做一些基础的业务开发,也进行库化。这样做了后,一定程度上解耦了,抽象出了一些对象、组件,然后再把这些对象、组件进行服务化。后期如果服务多了以后,可以再解决服务管理的问题。这样的设计,是希望团队能够在开发的过程中更容易专注于业务交付,在开发流程上简化了,也使开发的门坎进一步下降。
我们做了一些基础业务库,如协议库,请求协议,分时线、K线等数据协议等等,把整套协议都放在一个库里面。如交易日处理库,期货、现货市场的交易日处理问题比较突出,黄金交易所周五晚的开盘时间是20:00,到下周一15:30收盘,一个交易日横跨三天,还有节假日,交易日处理上会有一些需要注意的地方。
有时候行情源会出问题,多路竞争的时候,一是某一路行情源故障了,不会导致行情中断,二是竞争之下,能保证最快的发情,这是一些最优的处理。对分时、K线、逐笔、分价算法、指标算法进行了库化。开始的时候,PC客户端因为在做指标算法开发的时候,来不及用C++做,我们就把Go的算法库编译出来,放到C++客户端里面,只用了一个星期。
压缩算法库是一个根据行情数据的特征进行压缩的库。在接入服务中做的内存数据缓存的,会有一些缓存策略,封装成缓存策略库。其他的业务库,在后面的业务周期中,可以很快的增加。
接入服务
接入服务能支持后端去状态开发,有故障恢复的能力,支持负载均衡。最后我会展示一些接入服务的测试数据。接入服务主要是面向客户端的提供稳定、及时、优质的服务。接入服务有四个目标,一是要长期稳定的,上线以后基本不再发生变化。二是保证及时服务响应,后端服务发过来,它能够快速到转发。三是当服务器发生异常的时候,使客户端无感。四是可以弹性上下线,后端能新增服务上线,也能选择把某一个服务直接摘掉。
在以上目标下整理了几个要点,一个是解耦业务,与具体业务无关。提供服务注册功能,后端业务服务程序能注册到接入服务,任何业务都可以注册到接入服务。可同时注册多个同一业务的服务程序,并提供多播策略和多策略负载均衡服务。要提供业务路由服务,按请求协议中的业务路由到服务提供者。提供业务状态寄存服务,当后端服务开发的时候,可以把请求在上下文,直接保存到状态服务。接入服务不关心上下文内容是什么,但是它提供寄存服务。能支持路由策略、多播策略,负载均衡策略在线的扩展。
接入服务在实现的过程中,分了六个模块,一个是网络收发模块,这也是能用的,前后端所有的服务都可以使用网络模块。有一个服务调度,调度前面说的这些内容,像路由、服务管理、状态寄存,命令处理,对他们进行一些调度。 接入服务中的转发服务是最基本的服务,客户端向我接入服务发起请求的时候,直接转发到后端业务,然后再转发回客户端。
接入服务的服务去状态指后端业务服务,可以是无状态的服务方式。客户端发起请求的时候,状态的上下文是一个空的状态,把这个空的状态和请求转发给后端服务,后端服务返回的时候,结果1的状态会保存到上下文状态里面,然后再转发给客户端。后端服务继续推送后续结果的时候,结果状态会不断更新到上下文里面。这样后端的行情服务就可以把状态去掉,不在服务中保留服务状态。
接入服务中的故障恢复。假如说我们现在的服务都正常,其中一个节点突发故障,接入服务向它转发请求时,该故障服务器是没有办法处理的。这时接入服务的调度模块会把该请求和状态n进行重新路由,把状态 n 带到正常的服务节点上,正常的服务会把正确的结果重新返回过来。后续在推送新结果的时候,状态就是 n+1。故障节点就可以随时摘除掉了。
接入服务中的负载均衡和动态上线。假如后端有N个服务,它在我服务管理列表,服务管理列表它会把这个信息放到路由表里面。当后端新增一个服务的时候,向服务列表注册,服务管理列表会把这个信息放到路由表里面,对原有的服务不会有影响。客户端可以直接发新业务的请求。
路由策略等等的控制是通过命令模块来支持,客户端把设定好的策略,发到命令模块可以控制路由表和服务列表。
测试部分
接下来是我们测试的数据。测试场景是分为连接数的测试、模块能力的测试,接收,发送的测试,少量客户端还有多客户端的,同时是完整的业务测试,接收、发送和回复,延时数据和内部的队列数据。我们使用的数据包大小是200byte,在1G的网络下进行。
下面是个示例,测试客户端,它是使用一个动态行情把业务ID放到协议里面,再打一下包,发给服务端。中间的过程会接入服务器,路由发送服务。服务端开发的时候很简单,使用接入服务的SDK,产生服务对象,服务对象向我们的接入服务地址注册,提供自己的服务名称,并且告诉服务器我们这个服务是解决哪一个业务,为哪一个业务提供服务的,业务开发可以直接在 Client 上处理,拿出来数据进行后面的开发。
接收能力50万包/秒,达到网卡的上限。最开始是Go1.4,升到1.6以后,CPU下降了不少。
转发测试有后端的服务,第一个数据因为测试的场景服务器资源比较有限是25万包/秒,后面服务器升上来以后,能达到50万包/秒。
多客户端测试,分别是在5000、10000、20000,25000的客户端情况下,每个客户端在每秒发10个包、20个包、25个包,最大达到25万包/秒,这是一个8核的CPU速度。
请求应答测试,这是一个完整的业务流程,能达到收发各450兆每秒,加起来900兆每秒,达到了上线。
数据延时测试,也是做了一个统计,每10万包做了统计,没有明显的延时。
队列,队列分别从1、48、400到800进行了测试,客户端的情况随着队列的数量增加,CPU的消耗降低,内存的消耗上升。队列比较少的时候,是一个瓶颈。
这个接入服务,对后期业务的快速开发有一个比较好的支撑。这个技术方案后面有兴趣可以进一步交流探讨。
----------------------------------------
8月19日 北京
七牛云携手链家,共同推出“大数据最新场景化应用实践”主题架构师实践日
七牛云技术总监陈超、链家网大数据部负责人吕毅联袂出品,精选干货内容:
Go 中国 粉丝福利:
点击“阅读原文”即可享免费报名
提供精彩留言获赞最多的三位读者即可获取七牛云手办一套!