新网关与旧网关的区别:
1.进行了性能优化。
优化内部数据传输,减少使用json传输数据,降低内存开销。
这块可以具体举例
2.合并了一个项目-np。减少了链路,降低了故障率。
3,对交易推送、订单统一管理。
存在的问题:
1.大量使用json传输数据,内存开销比较大。导致GC频繁,服务稳定性下降。
2.与NP、Broker交互时候存在大对象,
1.考虑到行情数据的实时性,只对kline数据做持久化存储,保存至mysql。
2.为了保证数据一致性,减小写入mysql时的资源争抢,只通过PersistVerticle的master节点进行持久化操作。
3.master节点宕机或重启后,通过zk重新选主,并通过redis进行数据补偿,保证写入mysql中的数据不丢或者不重复。
4.上游的数据进入Upstream之后,实时数据直接通过eventbus发送至下游;最近一段时间的历史数据,保存至redis,供下游查询使用;kline历史数据直接通过mysql获取。
UpStream
1、网关与上游 行情计算有2种数据通道 restful与websocket。restful用于获取历史K线,websocket接收实时推送。restful接口只用于从行情计算获取历史K线,用于网关数据缺失的补偿,只在网关启动时或运行中出现异常情况时执行。禁止用于客户端req请求的处理。请求参数和返回结果统一使用json格式。请求采用GET方法,参数拼接到url中。
2、k线统一使用candlestick。包括了交易对名称、k线的开始时间戳(秒)、开盘价、收盘价、最高价、最低价、成交额、成交量、笔数。
3、websocket 存活机制 服务端10s发一个ping,客户端回复pong, 服务端6个周期没收到pong,断开连接。客户端6个周期没有收到ping,断开连接。
DownSteam
1、客户端请求数据采用json格式,请求以后,服务端会响应返回resp,同时,通过websocket向下游推数据。k线最多返回2000条数据。
从上游接收到数据, 在高开低收, 成交额, 成交量和成交笔数完全一样的情况下, 将不会下推数据. 即保证推送的数据跟上一条是不一样的
topic数组 1-5.
session管理。所有的订阅都在session, 对market的订阅在ws1。在每个中有个session对象,
2、topic为20对,其中K线 10种(1min、5min、15min、30min、60min、4h、1D、1K、1月、1年)。交易深度7种(0、1、2、3、4、5、10),成交记录、overview、marketsummary 成交汇总
订阅
700交易对 1k用户订阅,
1、dispatcher进行转发,一共8路。
2、session的管理
每一个核有一个用户的session对象,session里有一个20长度的数组-topic[]。topic下挂的map,map里交易对ID与subitem对象的list,subitem里面存的是档位、K line。这样就明白每一个用户订阅的是哪些信息。交易对都是按topic分片,通过下标定位出用的哪个map。哪个list,哪些数据。知道用户ID与订阅关系后,进行group. 序列化的时候从level0开始,遇到订阅上的进行分发,未订阅的不分发,直接到最后的100档。需要注意的是,有人订阅在序列化,不是每一档都进行序列化,直接到最后一档才进行序列化。
进行转发分片。
启动顺序:
1、vertX的上下文启动。
2、部署对其他有依赖的启动,如last xxx等。
3、cache启动。(一个节点部署一个实例)
4、UpStream与DownSteam的启动。(在DownStream启动之前不应该杀死老的实例)
trade(成交量) cache:启动分为阻塞启动、非阻塞启动。当阻塞启动失败,对外不提供服务。区分标准是 交易对活跃度。
cache分trade cache与 k line cache
trade cache会连接DB,启动时候从DB拉取最新的2000条数据。当写的时候,会进行选取master.采用zk的选组。写入按照批次来写,缓存一部分数据然后写入。(会有丢数据情况,但业务场景允许部分数据缺失)。
k line cache有特殊的业务场景,不管成交量如何,K线数据总量不发生变化,并且一直在一个k线上变化。采用track数组模式。
Down Stream
1、改变以前按照CPU核数*2的机制来设置Down Stream个数。设置为8路。
优化重点:
原来:从np拿到压缩后的二进制数据-unzip为string-转为json object。json object至少产生30个对象,1070个bit。对象的变化会引起GC的负担。
现在:采用VO对象,里面使用10个double类型的属性,共80bit。有些数据不够8bit,会进行补齐。VO对象是对计算机友好的数据。
原来:从网卡中接受过来的数据,一开始在系统态中,占用的是堆外内存。从系统态读到JVM用户态中,要进行copy的动作。
1k的数据-unzip后变为5k(放大5倍)-string转json object变为14k(根据key的可复用程度,平均为8倍)--通过vertX的总线传播,分发到96个downStream扩大96倍(json object要进行复制,因为不知道源数据是否是可变的)--至少600k。
现在:1k数据--系统态转为用户态,再转为VO对象,还是1k数据--加自定义编解码器,保证VO对象的不可变性--
在发往总线之前,进行序列化(减少在每个DownStream进行序列化操作)--序列化后的数据存入VO对象,放入总线。
后期优化方向:从用户态转为系统态,当一个downStream有1000个用户,也需要copy 1000次。 可以选择在序列化时,直接序列化成堆外内存,避免重复Copy。
原来:系统中有字符串,如正则表达式(会产生8个对象、320bit)、format函数、类型转换(如double类型转为string,从8bit转为24bit)。
现在:去掉字符串。由于字符串是对人友好型,对机器不友好。当字符串是特定某些值,选择用enum类代替、做编码集代替string,如交易对可以用固定ID表示。
原来:日志打印频繁,影响性能。
现在:使用采样打印。
原来:使用了一些全局的同步集合,如currentHashMap、CopyAndWriteArrayList等。大部分性能消耗在锁等待上。
现在:由于vertX是异步框架,可以去掉全局map,必须加锁情况下采用CAS机制等。
原来:压缩采用JNI本地方法调用。JNI会对二进制数组做处理,把二进制数组的指针传给JNI,加上类似锁的标记。当GC发现有该标记,会停止GC。当JNI处理完以后,会进行GC补偿。 从而造成GC延误。
zip/unzip会创建输入流、输出流。zip一个80bit的对象,大致会变为140bit。当unzip一个140bit的数据,需要创建一个8k的buffer。
同时,char[]必须是连续分配的内存空间,当TLAB的内存不够,只能去堆中进行分配。去堆中分配需要进行加锁,影响性能,导致cpu空转。当unzip一个大对象,buffer需要进行多次扩容,从8k逐渐扩容到几M,每次扩容都要进行copy,占用cpu总线、内存总线。