随着德邦证券近年来不断加大数字化建设的投入,从而积累了大量数据资产。我们秉承着数据即服务的理念,不断的提升数据利用率,加快数据的流动性,增大业务赋能的力度,以便更好的服务于各业务部门。平台研发团队对证券基础数据仓库的建设进行了相关的调研工作,摸索出一套基于Flink实时流计算、ClickHouse OLAP数据库技术的实时行情数据架构。同时为了更好的验证数据以及展示成果,团队基于Netty网络框架,快速构建了行情流推送的服务端和Android客户端,以下主要介绍行情流推送Netty相关技术。
Netty技术特点介绍
Netty作为一个应用极广的网络通讯框架,可以快速开发出高性能、高可用的网路IO应用程序。高并发、高性能、简单易用等特点让它在Java领域应用非常广泛,Dubbo、RocketMQ、Zookeeper、Flink、ElasticSearch 等中间件的底层网络通讯也都是基于Netty实现。
1、高并发
a. IO多路复用技术
Netty基于JDK NIO API开发,底层采用多路复用技术,在Linux操作系统中可以通过一个多路复用器Epoll监听大量 socket文件描述符,当fd中有就绪事件时会把这些就绪事件添加到就绪集合中再回调到用户态中,io应用程序可以根据回调过来的就绪事件信息做对应业务处理。
b. 基于事件主从Reactor多线程IO模型
Netty主从模式根据事件职责实现连接与读写解藕,Boss Group主要处理Accept事件信息负责与终端建立长连接,然后把创建好的channel注册到Worker Group Selector中,后续读写等IO事件操作交给WorkerGroup线程池处理,基于事件主从模式除了分工明确外,还可以让系统把更多CPU资源分配给更加耗时的读写IO,从而提高系统的吞吐量。
2. 高性能
a. 零拷贝
网络io过程中会涉及到大量的字节数据传输,零拷贝技术通过减少字节数据从内核态到应用态之间的拷贝次数和上下文切换次数来提高应用io效率,Netty在零拷贝技术应用这块主要体现在以下几个方面:
在网络传输组包过程中会涉及到多个ByteBuf拼接操作,通过CompositeByteBuf可以帮我们对多个ByteBuf进行逻辑拼接,从而避免不必要的字节拷贝;
ByteBuf默认使用直接内存可以避免从JVM堆到直接内存缓冲区之间的一次数据拷贝过程;
文件传输通过native调用操作系统层面sendFile本地方法,可以直接将文件从内核缓冲区的拷贝到socket缓冲区,避免内核态和应用态之间的两次数据拷贝过程,同时从内核缓冲区到socket缓存区只拷贝文件偏移量和位置,避免大量字节拷贝带来的性能开销。
b. 对象池化技术
前面零拷贝中提到在直接内存中使用ByteBuf操作字节数据可以提升io性能避免不必要的数据拷贝和上下文切换,但是在直接内存中分配和释放对象较堆对象要慢很多,为了解决这个问题,Netty基于JeMalloc实现对象池化技术,对象重用可以有效避免频繁分配释放带来的额外性能开销,还可以减少内存碎片降低JVM GC的频率。下图是Twitter对堆内、堆外是否使用池化技术的压测性能统计,可以看出在使用池化技术时性能提升2倍多。
3. 简单易用
JDK NIO中的API繁多且使用繁琐,Netty默认实现了很多基于读写事件处理器,只需要在ChannelPipeline中注入对应的处理器就可以帮我们处理断线重连、客户端安全验证、消息编解码、粘包半包分包等常见网络io问题,开发人员只需要关注具体的业务功能实现就可以开发出一个高效网络io程序。
4. 安全
Netty提供自定义SslHandler,支持SSL/TLS协议,它在JDK SSLEngine基础之上实现了自己OpenSslEngine,相比较性能上面提升了一倍多。
5. 活跃社区
技术选型的时候往往社区的活跃度是重要考虑因素,活跃的社区意味着更多的人参与到Netty的讨论和建设,不仅Issues解决速度快,同时也能促进Netty朝着更高效、更安全、更便捷方向发展,确保项目后期版本稳定升级。
Netty行情流推送服务产生的背景
前期平台研发室针对Level2证券数据自研搭建一套分布式数仓系统,积累了大量行情基础数据和指标数据(K线、异常交易、全息盘口...),为了展示这些数据资产成果,同时也为了更好的进行业务赋能,平台研发团队开发了行情数据中台服务、行情流推送服务,并开发Java和Python版本的SDK,供相关业务部门快速使用相关API。
传统行情实时查询采用Pull模式定时轮训,这种方式不仅会遗漏价格的实际波动变化,在价格实时性方面也会有一定延迟,延迟的大小取决与刷新的周期,而通过Push方式推送实时增量行情信息更符合项目预期,通过前期的技术积累和调研选择Netty网络框架来快速构建行情流推送服务。
基于Netty行情推送服务的主要功能
基于Netty行情推送服务提供tcp和websocket两种协议的推送服务,websocket主要针对是web前端的用户,推送服务主要提供鉴权、实时行情订阅服务。
1. 鉴权
tcp长连接建立需要发送一个token到server端鉴权,token通过其他API服务下发,server校验通过之后才会提供后续的订阅服务。
2. 行情订阅服务
支持订阅实时报价、成交、盘口变化、不同周期k线、取消订阅等功能。
实时报价订阅:支持多合约订阅,订阅成功之后会立即返回当前合约列表快照信息,后续会增量推送最新价信息。
成交订阅:会实时推送合约最新的成交明细。
盘口订阅:基于当日所有委托、成交、撤单信息流维护的全息盘口信息,推送指定价格范围内的盘口变动。
订阅周期K线:基于Flink的时间滑动窗口计算生成,每滑动到一个新窗口会把对应周期K线信息推送到前端刷新前端历史K线列表。
行情流服务系统架构发展史
早期数据源主要是证券Level2基础证券数据和计算指标数据,通过Kafaka实现数据的分发,Netty应用程序负责实时消费处理队列中的行情数据,终端channel上下文中记录订阅功能号和合约信息匹配队列中解析后行情数据实现消息的订阅推送功能,下图就是目前行情流服务的系统架构图:
伴随接入行情源不断增多,目前对接数据源有Level1、Level2、期货、港美股、基金、债券等,导致Netty的行情流服务在解析处理kafka队列中数据时有一定延迟,需要配置更大并发数去获取解析消费队列中数据,从而导致CPU在连续交易时间段内一致处于高负载状态,为了解决这个问题,平台研发团队设计了新的系统架构。
新的系统架构从订阅功能、交易所、资产类别几个纬度对订阅数据流进行拆分,把队列解析处理分担到不同的子推送服务中来降低单台服务器cpu负载过高的问题,也符合后续行情源持续拓展的需求。从上图可以看到多出来一个前置转发服务会根据终端订阅的合约资产类型和订阅功能主动创建多个内部tcp子连接,转发服务维护着终端和内部子推送服务之间关联关系,根据关联关系转发子推送服务传递过来的数据到对应终端Channel。
前置转发服务主要功能:对外负责和终端之间进行数据交互,对内转发所有内部子推送服务push过来的数据。
1. 关联关系维护
转发服务根据终端订阅的合约资产类型和订阅功能找到对应内部子推送服务建立内部子连接关系,并维护一份终端连接和内部子连接关联关系,通过关联关系转发内部子服务推送过来实时行情数据到对应终端channel中。
2. 负载均衡
子推送服务通过注册中心发布,转发服务可以根据可用服务列表和当前连接数等信息实现负载均衡策略。
3. 失效转移
子推送服务A下线的时候转发服务需要根据关联A的连接信息重新在可用的同类子服务B重新建立新的内部子连接,这样可以保证用户不会因为子服务下线导致订阅功能失效。
实践总结&展望
行情流推送服务是一个探索实践的项目,支持综合行情推送订阅服务,已经发布到生产环境半年左右,这半年里来平稳运行,经过版本迭代目前已经支持A股、美股、港股、基金、债券、国内期货等证券资产类别订阅服务。
未来数据源还将加入国际期货、期权、现货、OTC等品种报价,打造成覆盖国内外证券资产实时行情数据平台,利用Flink实时计算特性进一步探索盯盘、套利监测、指标计算等业务领域,期望通过不断的深度挖掘,产生更多对企业、用户有价值的信息。