Golang 由于其高效快速、天然支持并发的显著优势,大大降低了服务的开发门槛,使得企业更易于直接架构业务,这些特性使得越来越多的传统公司技术部门选择拥抱 Go ,并结合自身业务特点摸索出了丰富的实践经验。在由 Go 中国和七牛云联合主办的 Gopher China2017中,来自金大师的技术经理张泽武详细分享了一个使用 Go 语言开发全套证券、期货、行情处理及分发系统。项目背景是在团队成员未完全到位的情况下,使用简单易用的 Go 语言后,用三个月的时间开发完成。并在随后的项目发展中,这套系统逐步演化完善成一套自有的快速开发框架。
项目故事
项目启动的时候,领导对这个行情系统有几个要求,怎么在最短的时间交付,怎么满足大量并发的请求,怎么保证低延时,把交易所的行情数据尽可能快的发到客户端。在这些基础的要求达到后,后面再接着开发分时线、K线、逐笔、指标等数据服务。
行情系统
行情系统有哪些要求呢?要快、准、稳。快是行情要响应快、延时低,如果慢了,用户价值会下降,而且会引发问题。准是指数据不能有偏差,发过来数据往下传的时候,加工处理要准确。还有可能发过来的数据不正确,数据服务商的数据有问题,数据质量不过关,要做容错处理。国外的节假日也会对数据有影响,这都是要行情系统要考虑的,把垃圾数据过滤掉。稳是指服务要稳定,可用性最少要99.99%。
下面介绍一下系统服务设计,第一部分是接入服务,主要是面向客户端,解决高并发、高在线的需求。第二部分计算服务,主要是加工行情数据,拿到行情数据进行计算,计算后落存储要持久化,按照数据的特性分类管理 。第三部分是采集服务,我们接了比较多的交易所,这些市场提供的接口和数据的组织的方式各不一样,协议也有很大差别。这些差异就在采集服务中来处理,我们定一套内部的数据协议,在采集的过程中,把外部协议转换成内部协议。
以上把大体的服务做了一些分类后,清晰了不少。接下来是一些开发工作,为了让开发人员能尽快上手,要让开发人员更专注到自己的工作中,降低开发门槛。于是我把开发要用到的库,作了一些抽象、归类,把相同的内容和业务做了一些库化。这里分了八类。第一个main.go,它把常规的初始化动作,包括监控、统计、外部命令、一些参数、退出的机制,做了一些约定固化。当要进行一个新功能开发的时候,拿到 main.go 后在它的框架下面直接做业务逻辑就可以了,不用再关心周边的内容。
接下来是跟业务相关的基础业务库。在前面几个通用库的基础上进行组装后,可以去做一些基础的业务开发,也进行库化。这样做了后,一定程度上解耦了,抽象出了一些对象、组件,然后再把这些对象、组件进行服务化。后期如果服务多了以后,可以再解决服务管理的问题。这样的设计,是希望团队能够在开发的过程中更容易专注于业务交付,在开发流程上简化了,也使开发的门坎进一步下降。
我们做了一些基础业务库,如协议库,请求协议,分时线、K线等数据协议等等,把整套协议都放在一个库里面。如交易日处理库,期货、现货市场的交易日处理问题比较突出,黄金交易所周五晚的开盘时间是20:00,到下周一15:30收盘,一个交易日横跨三天,还有节假日,交易日处理上会有一些需要注意的地方。
接入服务
接入服务能支持后端去状态开发,有故障恢复的能力,支持负载均衡。最后我会展示一些接入服务的测试数据。接入服务主要是面向客户端的提供稳定、及时、优质的服务。接入服务有四个目标,一是要长期稳定的,上线以后基本不再发生变化。二是保证及时服务响应,后端服务发过来,它能够快速到转发。三是当服务器发生异常的时候,使客户端无感。四是可以弹性上下线,后端能新增服务上线,也能选择把某一个服务直接摘掉。
在以上目标下整理了几个要点,一个是解耦业务,与具体业务无关。提供服务注册功能,后端业务服务程序能注册到接入服务,任何业务都可以注册到接入服务。可同时注册多个同一业务的服务程序,并提供多播策略和多策略负载均衡服务。要提供业务路由服务,按请求协议中的业务路由到服务提供者。提供业务状态寄存服务,当后端服务开发的时候,可以把请求在上下文,直接保存到状态服务。接入服务不关心上下文内容是什么,但是它提供寄存服务。能支持路由策略、多播策略,负载均衡策略在线的扩展。
接入服务在实现的过程中,分了六个模块,一个是网络收发模块,这也是能用的,前后端所有的服务都可以使用网络模块。有一个服务调度,调度前面说的这些内容,像路由、服务管理、状态寄存,命令处理,对他们进行一些调度。
接入服务中的转发服务是最基本的服务,客户端向我接入服务发起请求的时候,直接转发到后端业务,然后再转发回客户端。
接入服务的服务去状态指后端业务服务,可以是无状态的服务方式。客户端发起请求的时候,状态的上下文是一个空的状态,把这个空的状态和请求转发给后端服务,后端服务返回的时候,结果1的状态会保存到上下文状态里面,然后再转发给客户端。后端服务继续推送后续结果的时候,结果状态会不断更新到上下文里面。这样后端的行情服务就可以把状态去掉,不在服务中保留服务状态。
接入服务中的故障恢复。假如说我们现在的服务都正常,其中一个节点突发故障,接入服务向它转发请求时,该故障服务器是没有办法处理的。这时接入服务的调度模块会把该请求和状态n进行重新路由,把状态 n 带到正常的服务节点上,正常的服务会把正确的结果重新返回过来。后续在推送新结果的时候,状态就是 n+1。故障节点就可以随时摘除掉了。
接入服务中的负载均衡和动态上线。假如后端有N个服务,它在我服务管理列表,服务管理列表它会把这个信息放到路由表里面。当后端新增一个服务的时候,向服务列表注册,服务管理列表会把这个信息放到路由表里面,对原有的服务不会有影响。客户端可以直接发新业务的请求。
路由策略等等的控制是通过命令模块来支持,客户端把设定好的策略,发到命令模块可以控制路由表和服务列表。
这个接入服务,对后期业务的快速开发有一个比较好的支撑。
事实上,Go 语言不仅在服务性能上表现卓越,而且非常适合容器化部署,国内已经有很多IT企业采用Go作为服务端的主要开发语言,比如上海的七牛、北京的今日头条等等,今日头条很大一部分服务已经运行于内部的私有云平台。结合微服务相关组件,他们正朝着 Cloud Native 架构演进。