Go 凭什么击败 C++ 成为证券期货行情系统的首选语言

4,778 阅读16分钟
原文链接: mp.weixin.qq.com

       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日 北京

七牛云携手链家,共同推出“ 大数据最新场景化应用实践”主题架构师实践日

七牛云技术总监陈超、链家网大数据部负责人吕毅联袂出品,精选干货内容:

  • 链家网资深大数据研发工程师邓钫元,分享如何运用“开源+自身业务定制”解决大数据多维分析痛点

  • 七牛云大数据高级工程师党合萱,全面揭秘七牛自主研发千亿级大数据平台的实践之路

  • 蚂蜂窝 大数据平台负责人汪木铃带来蚂蜂窝作为国内最早一批使用 Druid 的公司,在实战过程中踩过的“坑”和优化经验

  • 前新浪微博技术专家、三好网 CTO 卫向军,讲解如何利用大数据技术方案在实际业务应用中增值

Go 中国 粉丝福利:

点击“ 阅读原文”即可享免费报名

提供精彩留言获赞最多的三位读者即可获取七牛云手办一套!