强平系分流程缺失,直接导致自动化单元测试无法编写,无法覆盖功能风险点
以下指标的组合需要模拟测试:
1.强平模式:常规强平,手工强平,延时爆仓
2.保证金类型:RM模式,PM模式
3.市场波动:indexPrice和markPrice几乎不变,indexPrice和markPrice大幅波动
4.风险类型:IM溢出,MM溢出
5.订单类型:无活动单,只有永续活动单,只有期权活动单,既有永续活动单又有期权活动单
6.仓位类型:无仓位,只有永续仓位,只有期权仓位,既有永续仓位又有期权仓位
7.成交方向:只有买,只有卖,买卖混合
8.数量大小:一个,多个
9.交易事件和账户事件:下单,改单,撤单,撮合,结算,交割,入金,出金
10.上游压力:30秒内少量用户少量交易,30秒内少量用户大量交易,30秒内大量用户少量交易,30秒内大量用户大量交易
11.非功能需求:并发吞吐(30秒内至少强平1万用户),性能耗时(排除RPC调用耗时,各组件处理时间不超过10毫秒),异常处理(RPC调用执行出错
排除10和11的非功能指标,需要覆盖情况:3x2x2x2x4x4x3x2x8x4=73728种情况
典型强平流程也是解决其他一切强平业务和技术问题的基础,未来的所有单元测试将会以典型强平流程出发,针对每个类的进行自动化断言
有必要补充完整典型强平流程 强平系分流程梳理
一.系分的评判标准:
二.存在问题:
1.当前强平系分缺少模块划分,功能划分,缺少依赖关系,只根据系分无法推断代码逻辑结构,无法推断强平服务有多少类,多少函数。
2.当前强平系分缺少模块时序图,模块流程图,功能时序图,功能流程图,缺少附加说明,只根据系分无法推断代码实现逻辑,无法推断强平服务每一个函数有多少if判断,多少for循环。
3.无法从当前强平代码推断系分的模块划分,功能划分,依赖关系。
4.无法从当前强平代码推断系分的时序图,流程图,附加说明。
5.当前强平运行维护中发现的bug,开发人员直接修改代码,想到什么修改什么,修改之前没有跟团队其他成员交流沟通解决方案,代码修正后没有同步修正系分。
6.以上问题直接导致系分已经严重脱离实际,阅读系分除了浪费时间似乎没什么用处,增加很多技术上和业务上的沟通成本,尤其是接下来远程团队协作极其不利(整个保证金任何一个环节理解出错编码出错操作出错,所有以前的努力全部归零)。
三.解决方案:
1.尽快安排补充和更新系分,让系分和代码保持一致,在编写系分过程中,必定会找出以前从来没有发现得bug,这个时间精力投入是值得的。
2.每次补充或更新完系分,应当集思广益,找其他开发同学确认是否合理,是否有更优方案,三人行必有我师焉,三个臭皮匠顶个诸葛亮。
3.每次编写或更新完代码,应当新增或更新系分,确保系分和代码一致,并找其他开发同学确认。
4.如果有条件,系分和代码要定期安排其他团队审查,确保每一部分都是易理解的,严谨的,只有这样才能让团队外的人“快速“看透理解,反之,缺胳膊少腿模棱两可的系分,团队外的人肯定看不明白,或者短时间看不明白,审查失败。
强平的初级目标是30秒钟1万个流程,远期目标是3秒钟1万个强平流程,
强平流程中大部分耗时都是发生在外部rpc调用的阻塞和jraft本地落盘和jraft广播到副节点同步数据时的阻塞
jraft通过半强一致性协议防止内存数据丢失需要提前落盘持久化,需要广播给其他节点。假如生产环境jraft集群有5个节点,1秒钟10万次更改,那么除了主节点需要写入本地硬盘10万次(同步阻塞),还需要40万次网络写入(20万次同步阻塞),总共50万次IO操作,这些IO操作耗时既严重影响了单次更改的耗时,也影响了整体吞吐量。
jraft把应用程序变成了有状态,直接限制了应用程序的伸缩性,同一个强平基本上要同一台机器完成,瓶颈在单机上,机器内一处发生异常全盘完蛋。如果出现这种情况,停机修复期间将会造成重大损失。
jraft的业务代码夹杂了很多跟业务本身无关的代码,开发和维护成本高。运行经常出错,稳定性较差。缺乏易用的监控组件,定位问题困难,运维成本高。数据保存位置特殊,复现问题极其困难,解决问题耗时长。
全内存有状态设计更适合计算型的应用,比如撮合服务,运行中途不需要跟外部系统交互,cpu满负荷运转。无状态应用,把数据状态保存在mysql/redis/kafka等中间件,适合IO型的应用,比如交易服务。
强平服务虽然需要计算当前风险水位和最大亏损值,但跟永续期权交易资产运营撮合等服务的交互等待对比,计算耗时占比不超过1%。整体看来强平服务属于IO应用型而非计算型应用。
基于现有环境,可采用mysql(margin-db)作为强平服务保存数据的替代方案。从性能看,mysql在100万以下数据的增删改查速度是非常快(平时说的mysql慢是千万级别时才会出现或者大量join查询才会出现)。如果是批量提交,10个字段以内,一秒钟提交10万行都是轻而易举。
从伸缩性和分布式处理看,强平服务的数据保存在数据库后,强平服务可以根据需要随时伸缩,业务高峰期加机器,业务低谷时缩减机器动态调整。同时一个强平流程可分发到任何机器处理,甚至强平流程的每个环节每种状态都可以由不同机器完成,真正意义上分布式协助处理(当前jraft集群节点的副节点主要是为了容灾而非分布式处理)。
从整体网络流量看,强平服务的数据保存在数据库后,强平流程的每1次状态更改只需要向mysql发送1次网络写入请求,而不需要像jraft集群一样除了写入本地文件还需要分发到多个副节点,mysql更节省流量。
从整体同步阻塞看,强平服务的数据保存在数据库后,强平的每一次状态更改只需要一次同步阻塞,发生在mysql持久化时那一刻。jraft集群如果有5个节点,写入本地文件是阻塞,另外4个网络请求也是阻塞,jraft阻塞的次数远大于mysql。
从单次写入速度看,jraft由java语言开发,mysql由c语言开发,c语言的操作速度更加优秀,单次写入速度大概率还是myql更优。
强平流程运行时,对历史强平记录并不敏感,比如它不依赖于24小时前的数据。这些历史强平数据可以迁移到hbase等地方永久存储(后续大数据分析报表统计),保持强平数据库margin-db的单表数据永远在100万数据以下,每时每刻增删改查都高速运转。
强平服务的数据保存在数据库后,开发成本,运维监控成本,定位问题的成本,解决问题的成本,都会大幅度降低。
口说无凭,实践才是检验真理的唯一标准,不妨设计一个实验对比,实验思路https://confluence.yijin.io/pages/viewpage.action?pageId=78614535
强平的新业务开发是在现有的AccountMarginData中不断增加对象,子对象,字段。里面包含了各种list,包含了各种无约束的hashmap,导致AccountMarginData对象非常庞大
曾经做过统计,假如list中的元素只有一个,hashmap的key也只有一个,那么整个对象至少有600个字段。
业务新增时,没有详细设计实现文档,没有评审记录,简单粗暴的在AccountMarginData增加对象或字段,嵌套在大对象的某一层,让对象变得更大更肥不清晰,长久以往,能把这些字段说清楚的没几个人,仅仅沟通成本理解成本就急速上升。
有些对象的字段跟其他对象的字段的意义是一样的,但是程序更新时只更新了一个对象的字段,其他对象的字段形成了事实上的脏数据,很容易让开发人员和运维人员误解误用。
传统的数据结构是建立在关系数据库中的结构化数据(想象成excel表),经过严格的建模过程,不同表之间通过id等唯一值逻辑上关联,不会出现大对象,不会嵌套,容易理解,开发,维护和拓展新业务。
强平的关键字段只有50个字段以内,用几张小表即可表达强平系分流程梳理
模块化的最佳效果是把任意业务模块代码删除了(除了util工具类等无状态的公共模块),不影响其他模块的使用,比如键盘的一个按键坏了,其他按键还能继续使用(局部问题不影响整体推进)
实际情况很难保证其他模块不出错,因此要做好模块间隔离,做好异常处理,即使别的模块挂掉无法调用也毫不影响,本模块成为了“永动机”。
当前强平的模块耦合非常强,当新业务到来时直接在旧业务中加if,一点小问题,所有模块gg。
当前强平的调用关系不是很清晰,比如出现模块内的service层调用别的模块的DAO层,模块内的DAO层做业务处理(把service层的代码搬到DAO层在做了),
当前强平不同模块的代码,不同子模块的代码,不同功能的代码混合在一起,比如在实体类中编写业务代码,在查询类的方法(看起来是无状态)中改变字段值(方法名看起来无状态实现却是有状态的,到处埋雷)。
针对模块化问题可采用这种方案。模块内再分层,模块大了继续分子模块,子子模块,模块间只能通过service层接口调用或者消息队列通信https://confluence.yijin.io/pages/viewpage.action?pageId=71360355
由于rpc调用等会阻塞线程,导致后续的请求排队等待,
解决方案从两方面着手,一方面是程序设计,一方面是模块拆分,
在程序设计时,查询类的可使用(rpc调用+缓存),增删改类的使用消息队列,避免慢查询阻塞,避免事务处理等待
模块拆分,有时候就是因为机器资源不足,怎么优化也无济于事,累死累活收效不明显,这样最佳方案是拆分服务,参考https://confluence.yijin.io/pages/viewpage.action?pageId=78610703
强平跟运营交易期权永续撮合资产等服务都有交互,整个强平链路每一个环节都可能出问题,
目前强平定位和排查问题时手忙脚乱焦头烂额,根源是缺乏有效的日志及报警,分析效率低下,甚至压根就没法跟踪。
没有清晰直观的数据支持,出问题只能自己背锅了,要是自己代码有问题那也认了,要是其他系统的问题引发,那么甩锅机会就这样丢了,太可惜了。
有必要对强平流程前后状态,强平每个步骤的前后状态,期权活动订单状态,永续活动订单状态, 期权仓位状态,永续仓位状态,rpc调用输入输出异常捕获,消息发送和接收,接管户资金池数据一致性,进行全方位360度无死角监控和统计
规划方案参考https://confluence.yijin.io/pages/viewpage.action?pageId=78612765
Metric监控主要包括各业务逻辑指定时间窗口内的最大耗时,最小耗时,平均耗时,总次数,正常次数,异常次数,每秒钟的调用次数(吞吐量),以及他们的报警提醒
Metric监控常见的有以下几种方式,
1.把数据统计到mysql再编写前后端代码展现和导出。
2.把数据直接统计到promethous,再通过grafana展现
3通过elasticsearch-exporter插件把es中日志采集到promethous,再通过grafana展现
第一种,适合非开发运维人员使用,比如运营和产品要知道今天有多少订单,数据持久化到mysql和hbase等数据库,功能最强大,他们想怎么分析都是可以的。
第二种,适合开发运维使用,诊断问题非常有用,尤其关注超长耗时,吞吐量,异常调用次数,和异常报警。
第三种,适合开发运维使用,诊断问题非常有用,尤其关注超长耗时,吞吐量,异常调用次数,和异常报警。
在这里只讨论开发运维人员使用的第二种第三种,看起来第二种和第三种最终效果一样,但实现方式有以下不同,
第二种需要在pom文件引入metric依赖,直接侵入代码
第二种混合在核心业务代码中,看起来业务代码非常乱,违反功能尽可能单一的原则
第二种要想对某些指标动态开闭也相对困难,比如今天我要监控这个指标,明天又要监控另外一个指标,后天又要关闭某个指标。
第二种虽然是异步发送到promethous,但是最终也是要耗费宝贵的应用程序的cpu内存网络资源
第二种metric只是一个值,要反查具体是由当时的业务数据非常困难
第三种需要插件,这些插件已经由运维提供了,包括es怎么收集日志文件,elasticsearch-exporter怎么把数据同步到promethous,运维全搞定了,程序员只需要把要监控的指标值打印到日志文件,再去grafana配置一下即可
第三种如果promethous挂掉了,而es没有挂掉,其实还可以直接在kibana配置,同样可以统计分析异常报警,只不过界面没有grafana绚丽。
第三种即使运维删库跑路了,包括es,prometdous,kibana和grafana没有任何数据,我们还是留了一手, 没错,登录应用程序所在机器手动查询日志。我们的系统自主可控,进可攻退可守。
第三种程序直接把监控指标输出到日志文件(也是异步批量),理论上会比发送到网络要更快速,耗费cpu内存网络资源更少
第三种程序把监控指标也输出到本地硬盘,会浪费一点磁盘空间,所有要做好清理功能,比如24小时前的日志文件全删除,这方面不需要应用开发侧操心,运维已经存在完整可靠的解决方案
第三种metric虽然也只是一个值,但日志已经存储在es上,方便反查强平流程,轻而易举的追踪当时的业务数据
强平代码可使用第三种,无论程序员开发维护量还是机器运行性能都会更好
强平服务强依赖nacos,但nacos由其他团队维护,万一他们删库跑路了,强平服务还能正常使用吗?叫天天不应叫地地不声。
可以设计一个兜底方案,在紧急情况可使用本地配置文件,或者应用程序启动时从运营支撑系统查询(rpc),或者应用程序运行过程中监听运营支撑系统的配置更改消息(kafka)
强平服务准生产环境针对accountId进行路由,对10取模,结果为0的进入0号zone,结果为1的进入1号zone……结果为9的进入9号zone
我不清楚当初为何对10取模,或许是感觉将来用户量就是这么多,几乎不怎么变化?或许0,1,2……9,在人类看来,几乎不需要算就能快速确定落入哪个zone处理?
现实情况计划大多时候赶不上变化。比如未来某一天,各发达国突然支持数字货币流通,鼓励民众使用数字货币,那用户量肯定必定会暴增。此外,相比人来说,计算机对10的取模要进行复杂的计算。
也就是面临了未来扩缩容问题和计算是否高效问题
最佳实践是对8,16,32,64这样的数据取模,比如当前以8来取模,未来扩展到16为例
0号zone的accountId,二进制从右到左,第4位是0的保留在0zone到7zone,第4位是1的分别迁移到8zone到15zone,也就是0号zone刚好有1半(高位部分)迁移到8号zone,7号zone刚好有一半(高位部分)迁移到15号zone,非常的清晰和均匀
计算机对8,16,32这种数据的除法可以通过位运算,相比对10取模速度要快得多。
一.业务特性
二.当前程序底层数据结构(队列)设计,有可能出现以下情况:
三.解决方案:
一.业务特性:
二.存在问题:
三.解决方式:
一.业务特性:
二.存在问题(以enum类举例):
类名规范
是否存在自定义构造函数
1.有些存在自定义构造函数,比如ServiceActionEnum
2.有些不存在自定义构造函数,比如LiqAccountStatus
构造函数参数个数
1.有些自定义构造函数包含有3个参数,比如包括index,英文编码,中文描述,比如ServiceActionEnum
2.有些自定义构造函数包含2个参数,只包括index,英文编码,比如LiqBanEnum
3.有些自定义构造函数包含2个参数,但只有英文编码和中文描述,没有index,比如RiskHandleMode
4.有些自定义构造函数只有1个参数,只有index,比如LiqMode
程序使用和传输方式
三.推荐方案:
一.业务特性:
二.存在问题:
三.推荐方案:
1.利用响应式变成理念来组织强平流程代码
2.事件驱动替代literflow流程调度框架,把每个流程步骤当做模块,模块间通过kafka消息总线通信,每个模块输出作为一个消息发布到消息队列的一个topic,下游模块订阅,下游模块输入就是上游模块的输出,或者下游模块的运行是由上游模块的发布事件驱动
3.每个模块消费上游模块消息时,必须做幂等处理,因为上游可能重复发送了消息,每个模块处理完后必须确保输出消息投递成功才提交offset,这样如果中途宕机,重启后可以重新消费
强平工具类问题:
helper util
问题:
强平浮盈浮亏模块每秒钟都在对每个用户推送浮盈浮亏
价格波动
用户杠杆调整
用户入金
交割,开仓,平仓等仓位变化
时间窗口
接管户多次创建
接管户差错队列