Java券商实习笔记:WebSocket-港美股行情数据推送完整业务流程

Posted fntp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java券商实习笔记:WebSocket-港美股行情数据推送完整业务流程相关的知识,希望对你有一定的参考价值。

Java券商实习笔记:WebSocket-港美股行情数据推送完整业务流程

写在前面

兄弟们好…好久不见啊,我回来了。我实习归来,将为大家带来我在实习中的经验总结与我的独家JVM讲解!这一别甚是想念,这一连六个月我都在一家互联网金融公司实习,期间也积累了一些实战开发的笔记,准备在这里与大家分享,之前说过与大家一起探讨JVM的话题也不会忘记,喜欢我第一篇JVM开篇的同学,我们又可以一起研讨了!我也先将实习期间积累的这些知识经验,都无私分享出来,与大伙一同探讨。欢迎每一位粉丝,以及每一位读者,留下你的读后感悟,或者你的Java开发问题,可以私聊我,或者直接给在下文评论,我会及时给您回复,从今天起,我将会日更不定量的笔记,将我实习期间的所学所见所闻都一一分享出来,谢谢大家赏脸!相信比我实习内容更有吸引力的是:我是如何做到只复习了Java基础内容就找到了杭州的实习!?你不想知道吗?你不想知道我是怎么通过面试官的Spring全家桶的严刑拷打吗?不想我是怎么通过JVM八股文的筛选的吗?想知道怎么样只要拥有Java基础就可以随便找一个可靠的实习吗?关注我不迷路,我会在后续一一解答。包括,与各位一年之约的JVM底层解析。

关于WebSocket&Netty

        关于Netty的入门文章,想必在C站(CSDN)中讲得比我好的博主比比皆是,我这里就不过多的出洋相了,Netty的核心重点,后续也会有专栏博客,讲解Netty的一些细节上问题,以及源码层面去解读Netty的一些代码带来的思考问题。回顾了整个Netty从零基础到自学入门再到实战的这几个月,最主要我想,还是在于Java这个大的生态环境中,Netty的立足优势,也就是区别于直接的Mina(JavaNIO框架)与Grizzy(同为JavaNIO框架)。简而言之,Netty对于传统的JDK1.4之前的IO的改进无外乎在于以下几点:

  1. IO模型的构建优化
  2. IO事件调度优化
  3. API接口优化
  4. Protocol协议优化
  5. Serializable反序列化(将字节码反序列成JavaBean)

        没错了我们的Netty笔记文章,与这五点,毛关系没有…好吧,我们不卖关子了,直接讲实战开发,我是怎么做的吧。请大家关注业务流程以及WebSocket(基于Netty)的断线重连策略设计与实现!

一、关于WebSocket的一些须知事项:

1、WebSocket的哪些事值得关注?

(1)了解数据源:

        国内做券商,原始数据基本来自各大交易市场,上交所,港交所,北交所等等,而这些地方的数据都很原始,还需要进行数据清洗和过滤筛选,作为业务开发案人员,我们根本没有精力去造轮子,故此,我们采用了数据提供商,恒生电子旗下的恒生聚源与近几年势头大好的融聚汇的数据作为二手数据源。

        首先呢,我们简单讲一下为什么项目中会使用到WebSocket?
        答:在行情数据推送的这一模块,主要为了解决行情数据业务依赖的以下的技术痛点,因而采用了
WebSocket传输数据。具体是哪些痛点呢?

(1)一次连接,多次推送,建立简单可靠连接后,客户端可在长时间内保持数据传输的状态。
(2)保持即时消息时效性,并且只传递有效关键数据,不传递多余数据占据连接带宽。

所以,websocket也就是ws,在行情数据推送的整个流程中起到很关键的作用。关于WebSocket的一些细节:

ws连接的创建需要客户端首先发起请求连接,而握手请求使用的是HTTP协议。在客户端的请求头上
的Upgrade字段是websocket,表明需要升级为ws协议进行通讯。服务器在收到请求后,返回101状态
码表示服务理解了客户端的请求,并将通过Upgrade消息头通知客户端采用不同的协议来完成这个请
求。


以上图片源自百度。

二、关于行情数据推送的具体流程

        按照现行的ws设计思路,从业务流程设计角度出发,应该分为以下三个部分。客户端服务,解析服务,服务端服务。我会从大体角度先客观描述一下整个的流程,为了不与业务代码扭打在一起,我选择先概括,后细说的方法来描述。

1.为了连接上融聚汇:我们需要有客户端。

        首先,数据源提供商融聚汇为我们提供行情数据,他们提供的行情数据正是以WebSocket的形式即
WS进行的传递,那么,对于我们来说,我们需要拟建客户端去对接融聚汇的ws数据推送,故此,后端
需要设计客户端来连接ws。这个客户端主要负责的就是以下几个步骤:

(1)连接与准备。建立与融聚汇的ws服务端的连接,并保持心跳消息规律性畅通,准备好订阅或者是
取消订阅的消息内容主体,为在建立连接后进行具体的业务订阅做好准备。
(2)执行订阅。将准备好的订阅消息直接发送到融聚汇,完成行情数据订阅。
(3)响应接收。执行订阅融聚汇的行情服务,接受融聚汇返回的订阅成功与否结果信息,接受融聚汇返
回的详细业务数据,如快照推送,报价推送,买卖盘推送等。
(4)断线重连。保持与融聚汇的心跳联系,如果监听到与融聚汇断开连接,会及时触发断线重连机制,
并且需要重新连接上融聚汇的ws客户端。

        以上的四个步骤,都是属于客户端的原子职责,除此之外,客户端没有其他任务。(代码截取是部
分核心功能的底层实现延时)

1.1连接与准备

        不难想到,我们创建了客户端,那么第一个细节问题就是,我们如何连接上融聚汇的服务呢?融聚
汇为我们提供了ws连接的客户端模板案例,以及客户端连接的ws默认地址,在已有客户端案例的情况
下,我们可以基于客户端案例进行二次开发,从而连接上融聚汇的服务端。连接无非就是创建客户端对
象,补全连接时候所需要的必要条件,连接地址,端口等。按照官方的demo走我们已经成功了第一
步:建立了连接。

        那么,建立连接后,首先需要做的就是:发送心跳。心跳怎么发送呢?融聚汇为我们提供了相关的
文档,给出了明确的发送请求协议的格式规范,明确以JSON的请求体格式进行请求数据的规范传输,针
对于所列出的请求协议,归类后分析,一共就三类请求,第一类请求是心跳请求,第二类请求是订阅请
求,第三类请求是取消订阅请求。三类请求中,必须要长时间维护心跳请求,以保持与服务端的连接,
这也是ws客户端必须做到的事情,否则断开连接后,我们无法向融聚汇发起任何订阅请求。

        明确了请求协议类型与请求协议的规范格式后,我们下一步需要做的就是思考如何组装请求协议?
不难想到,这明显需要根据业务类型进行拆分,不同的业务执行不同的请求类型,由于我们的行情
数据订阅的时候,不论是美股还是港股,都是针对于用户来说的,所以我们肯定需要去对于不同用户进
行区分,这就是不同的需求,那么结合我们的需求分析以及融聚汇的订阅协议与取消订阅协议。

        不难发现,无论是港股还是美股,我们都需要订阅他们的实时行情数据与延时行情数据,实时行情
用于符合实时行情推送标准的用户进行推送,这里的标准,我们暂且定义为已登录的用户。那么很好理
解了,未登录的用户就是推送延时行情数据。

1.2执行订阅

        分析到这里,我们一共要准备四种订阅和取消订阅以及心跳的请求协议,由于订阅,取消订阅,心
跳这些都是客户端需要执行的任务,为保证数据之间相互独立不干扰,我们拟定了四个客户端,分别
是:港股实时客户端,美股实时客户端,港股延时客户端,美股延时客户端。这四种客户端,分别负责
订阅实时或延时的港美股行情数据。

        对于订阅来说,为什么需要四个客户端?准备多个客户端是因为行情数据,不论是港股还是美股,
都有实时与延时这两种行情数据,如果只准备一个客户端的话,就会出现问题:现在有多个客户端,当
客户端A给我们后端发送来了订阅证券ZQ-1的实时扩展行情的订阅消息,那么我们就需要创建后端的客
户端来订阅融聚汇的实时扩展行情数据,但是如果客户端B给后端发来了订阅证券ZQ-1的延时扩展行情
的订阅消息,那么我们就得用这个后端客户端去订阅ZQ-1的延时行情数据就会造成订阅覆盖问题,故
此,我们需要四个客户端:港股实时客户端,美股实时客户端,港股延时客户端,美股实时客户端。

        分清了需要分开订阅后,剩下的就是保证组装好请求协议了。回到现在的问题上,如何组装请求协
议,那么这个除了需要针对行情类型外,还需要注意的就是具体的业务类型,最简单的例子,k线数据在
订阅的时候,需要注明k线类型,并且注明复权模式。又比如快照行情,由于融聚汇是将ETF,正股统称
为正股,是一种订阅这两种数据,会以同一种数据结构形式返回,融聚汇将指数,板块都统称为指数,
订阅这两种行情数据,会统一以这种形式响应,并且,订阅指数与板块的时候,订阅所传输的股票代码
的方式也是截然不同,订阅指数的时候,需要传递的股票代码是需要拼接上 .hk 的,而订阅板块的时
候,不需要传递.hk 因此因此,我们需根据不同的业务来组装不同的请求协议。

        现在介绍完毕后,迎来了执行订阅操作,准备好的订阅消息体,在跟融聚汇建立了连接之后直接将
JSON数据发送到融聚汇的WS服务端,然后,解析融聚汇相应的回执,回执也是一种响应。,等待接收
到订阅回执后,完成行情数据的订阅操作。(下图为订阅流程代码与订阅回执响应)

其实执行订阅,就是(消息回执内容如下图:)

1.3响应接收

        那么,上面的响应仅仅是融聚汇告知我们订阅成功了,并不代表我们收到了具体的业务数据,那么
有那么多需要订阅的业务,那我们如何解析呢?具体的解析怎么去实现的呢?我们存储了一个服务工
厂,将对应的业务类型与业务协议号进行绑定,进行相应的映射,这样便于我们清楚的直观的看见订阅
的响应回写的业务类型。比如1001就代表着快照业务,1001代表的融聚汇的快照,上面我们提到,由于
融聚汇将正股与ETF都统称为快照行情数据,订阅的时候区分订阅的,我们在响应的时候是统一的响
应。

        那么,到此为止,我们已经知道了从建立连接,准备订阅,发起订阅。那么我们还有必要去仔细思
考一个问题,这个问题就是,我们是如何将融聚汇回写的响应进行打包封装给前端的?针对单个的响应
是如何进行的,我们通过单个响应的流程分析可以类比其他流程了。

        那么针对于单个的解析,我们是这么做的,首先,由于融聚汇给我们提供的响应信息是二进制字节
的序列化后的信息,需要根据融聚汇提供的每种业务类型的相应内容进行设计protobuf文件,设计完成
后,将Protobuf文件通过Protobuf编译器进行转换编译,从而映射到Java包装文件,我们可以直接使用
此java包装文件来接收二进制字节序列化之后的信息,从而得到一个java的响应对象。

        得到这个简单的java响应对象之后,这就算是拿到了粗糙的数据,拿到粗糙数据后,我们要先进行
一下清洗,针对于前端所需要的数据格式进行转换,比如前端需要的是字符串类型,比如增长率,前端
可能需要渲染的字符串的值,并且前端还需要一个具体的数值来进行计算,故此,我们需要根据不同的
数据需求来进行设计响应的protobuf文件,在编译protobuf文件,再在我们已经收到的protobuf数据进
行一次二次封装,二次封装的时候,需要注意数据的小数保留位数。(下图为保留的小数位数示意图)
        小数的正负号,大数单位等,再就是需要注意返回的数据时间,这个时间,有的时候需要的是时间戳,有的时候需要的是具体的字符串。这个根据前端的需求来即可。
        到这里,我们已经完成了建立连接,准备订阅,发起订阅,收到回执,数据封装,推送回写。到此
为止,客户端该完成的数据交互部分的职责已经全部完成。

1.4断线重连

        接下来还剩下一个客户端操作,就是需要监听断线重连以及保持与融聚汇的心跳联系,如下代码所
示:我们的业务逻辑是首先在Netty监听到断开连接后,我们会判断当前重连次数是否越界,不越界的
话,那么启动新的线程任务来执行重新连接操作,如果此次重连不成功,会累计重连次数,直到重连成
功,否则又将进入下一次重连,每一次重连之间间隔3000毫秒,这个在连接融聚汇的客户端初始化的时
候就已经设定的。默认就是3000毫秒也就是3秒。

        好了,到这里,我们对于客户端的四大职责都已经讲的很详细了,客户端的部分到此就讲完了,下
一个部分,是解析服务。上面我们粗略讲述了解析服务所起到的具体作用,但这并不是全部,只是冰山
一角,接下来,我们继续来看解析部分。

2.为了能正确订阅,发送数据,我们需要正确解析数据。

2.1 连接解析

        当我们后端作为服务端对外提供连接入口的时候,前端根据我们提供的参数信息进行连接。由于我们后端是基于Netty建起来的一套通信机制,所以,我们可以在Netty中接收到包装好的连接信息,解析连接信息,比如,如果前端是以链接地址+参数,如ws://127.0.0.1:8899/token=6e2dc2434sgf1d5a6f5,那么我们就可以收到这里的传递的token参数数据,从而调用专用的解析token数据的工具,来为连接者鉴权,通过鉴别token的真伪,来获得用户的userId,获得userId后,我们再进一步通过调用RPC(Dubbo)来拿到用户的行情卡数据,进而查询到当前用户所拥有的港美股行情卡权限,根据用户所拥有的港美股行情卡权限来订阅不同的行情。

2.2 订阅解析

        正确解析数据,涉及到很多方面。一方面,由于融聚汇的订阅格式不支持多只股票订阅,那么如果
让前端一只一只的订阅的话,会非常影响效率,为此,我们因该重新定义订阅格式,这样,让前端方便
快捷的快速订阅,前端传递了我们定义好的格式后,我们只需要做一下解析转换成融聚汇的订阅格式,
然后再进行订阅即可。除k线之外,其他订阅的格式解析都如下图所示:

        要做到正确解析订阅,我们的流程是,收到用户发来的订阅消息后,将字节序列化的对象转为字符
串,再转成规范的json格式,我们定义的json包含两个部分,一个部分是event,一个是data,event代
表着事件,有三种类型:subscript-订阅,unsubscript-取消订阅,Ping-心跳。data就是对应事件具体
的内容,event是指对应的动作,data是指动作具体需要做的事情,data包含的数据需要根据不同的订
阅格式进行讲解。以下是三种后端与前端交互制定的三种数据格式。
第一种格式是通用数据格式,第二种是K线格式,第三种是分时格式。

这里单独讲一下快照的解析与k线解析与分时解析三种解析:

1.快照解析

        快照我们制定了一套专属的解析规则,将订阅消息拆分为市场,证券类型,证券代码,行情数据类
型这四大类属性,对于非k线非分时的订阅,这种格式可以通用,不分港美股,区分刚港美股是通过市场
代码来区分的,港股HK,美股US。

2.K线解析

3.分时解析

        分时我们与前端制定的订阅格式如下:data包含市场类型,证券类型,证券代码,行情数据类型以及分时类型,分时类型包含三种分时,迷你分时,五日分时与今日分时。

2.3 响应解析

        相应解析,顾名思义,就是将我们订阅融聚汇后,融聚汇给我们返回的响应进行解析,最终返回给前端。那么,为什么需要解析呢?前面我们说到融聚汇将正股与ETF统称为snapshot快照行情,而我们是将指数与ETF分开响应的,故此,我们要做的就是先将融聚汇的响应接收,然后加工包装成两种不同的响应,进而给我们的客户端(前端)回写,前端订阅正股与ETF都会收到融聚汇同一类的响应,所以我们只需要根据前端的订阅类型进行分别包装响应即可。响应解析的时候,需要注意的是:

(1)数据格式。前端有的数据需要字符串类型与double双精度浮点数,对于此种情况,我们同一
条数据需要做两个包装,两个都回写给前端。
(2)数据精度。对于证券的涨跌幅,开盘价等这类数据,需要根据定义的文件中描述的数据精度
进行精确返回。
(3)对于非K线,非分时类型的订阅,我们通常都是标准的响应解析方式与流程。这个流程的第一
步,是根据融聚汇提供的protobuf文件进行编译生成.java的文件,第二步制定返回给前端的数据结构,
写成protobuf格式,进而编译成java文件,第三步,用融聚汇protobuf编译的java文件接受来自融聚汇
的订阅响应,第四步,封装本地的protobuf编译的java对象属性,第五步遍历事先存储在内存里面的连
接对象集合,将封装好的java对象打包成字节序列,对每一个有效的连接对象进行数据回写。此时如果
检测到没有需要执行回写的对象,此时执行取消订阅。

3.前端订阅,依赖于我们的服务端服务。

        前端订阅我们的推送服务,我们服务端,需要做的,不难想到,首先是需要建立WebSocket-Server,然后建立于客户端的连接,监听到连接上后,立即进行客户端身份的校验。如果ws是不携带参数token的形式连接。那么服务端只需要从该连接对象中,取出Headers属性内容,然后进行包装成一个对象,这里的headers就是我们与前端定义好的header,在confluence文档中可以看到。然后我们拿到token,也就是authority属性,调用方法解析内容,获得userId,拿到用户id后,我们可以根据用户id,通过RPC协议,调用行情卡服务,拿到该用户所属的行情卡权限,美股港股实时还是延时就清楚了,然后,根据权限分配订阅的客户端。到这里我们需要记录该用户的行情卡权限:使用Netty_SHELL_MAP来记录权限,CTX_SET来记录链接对象,CTX_PING来记录心跳对象,便于后续遍历发送心跳。如果ws是携带参数token的形式连接。比如ws://127.0.0.1:8899?token=e1a5165c6a51c51f15a5af41a5d4a51.那么我们就需要要注意了,现行的方法是将ws的此种连
接先进行识别,识别为FullHttpRequest后,拿到ws的连接参数token的内容,然后进行解析,获得用户
id,获得行情卡权限数据,然后,根据权限分配订阅的客户端。到这里我们需要记录该用户的行情卡权
限:使用Netty_SHELL_MAP来记录权限,CTX_SET来记录链接对象,CTX_PING来记录心跳对象,便于后续遍历发送心跳。(这里的连接尚存在问题,h5连接后无握手成功提示待解决!)
        到这里,客户端发起连接,到服务端连接成功,已经执行完毕了。剩下的就是接受用户的订阅消息,通过channelRead0()方法来接受订阅消息,先进行日志记录,然后紧接着转换为标准的JSON格
式数据。

        分别根据event,和data进行业务判定,现根据event,如果event是PINg那就是心跳,如果event是subscript那就是订阅业务,如果event是unsubscript那就是取消订阅。就这三种总的业务类型。如果前端传递空消息,服务端会主动关闭连接。如果是心跳,日志记录,如果是订阅或者取消订阅,首先看data内容是否是空,data不为空,那么根据订阅或者是取消订阅的data内容进行格式化拆成字符串数组,将字符串数组也就是非空指令进行参数传递,转发到准备好的服务工厂,服务工厂分为港股工厂和美股工厂,分别已经装载了所有港美股的行情业务,根据订阅的字符串数组传递的指令,先区分港股还是美股,在区分业务,最后订阅任务就从服务端,巧妙的迁移到了后台准备的四个客户端:(1)港股实时行情客户端(2)港股延时行情客户端(3)美股实时行情客户端(4)港股延时行情客户端,将由他们来分别完成港美股的业务订阅。值得注意的是,我们订阅有订阅工厂服务,当然响应也会有相应的响应服务工厂服务!

        那么具体的订阅流程,上面已经完成了详细的阐述,这里就不再过多的赘述,至此,港美股的
WebSocket订阅具体流程就已经走完了。接下来,我们简单讲一下需要注意的订阅以及响应事项。

三、关于行情数据订阅与解析的注意事项

(1)关于迷你分时推送:

        迷你分时的响应的时候,规则比较特殊,当前时间按分钟算如果是五的倍数,且有融聚汇的推送情
况下,我们做推送,直到当前时间不是五分的倍数,停止推送,等下一个五的倍数的时候,倘若此时融
聚汇仍有推送,我们会继续给前端推送。

(2)关于对港股快照的订阅与推送注意事项:

        快照来说,快照我们的前端是需要分开展示,因此,需要分开订阅,分开相应,融聚汇将正股ETF
的响应统称为快照响应,将指数与板块的响应统称为指数响应,故此对于快照来说,我们定义两种响应
格式,一种叫快照,一种叫指数。订阅指数的时候,默认会附加扩展行情进行响应。

(3)关于行情卡

        行情卡就是用户对应所拥有的行情权限。

四、WebSocket应用体系架构设计

        根据Websocket的上面的陈述,后台需要四个客户端来对接融聚汇进行完成消息订阅取消订阅和接
受订阅推送的操作。一共实时延时港美股四个客户端如下所示:其中他们分别由控制器与策略组成,都
是基于自定义的TcpClient接口的基础之上建立的扩展,并且基于SSL方式与融聚汇建立wss协议握手连
接。


服务端与客户端之间的关系流程,如下图所示:

        以上就是我在券商公司实习的最常见业务之一:Ws行情数据订阅与推送流程的实现与原理和架构图,大致就是如此了!如果有想去券商工作的同学可以先了解了解哦,下期内容,我们聊点别的实习内容!?锁定fntp,带你了解我的Java实习笔记!~

以上是关于Java券商实习笔记:WebSocket-港美股行情数据推送完整业务流程的主要内容,如果未能解决你的问题,请参考以下文章

新浪财经美股期货实时行情怎么看

投资美股应该如何入门?

Robinhood罗宾汉美股券商推出数字货币钱包后股价飙升 12%

美股 基础知识2-常见问答

素问 - 南下资金

炒美股史考特(Scottrade)开户准备及如何获取免费交易(最新2017版)