强平安稳分析

强平系分流程缺失

  • 强平系分流程缺失,直接导致自动化单元测试无法编写,无法覆盖功能风险点

  • 以下指标的组合需要模拟测试:

    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.系分的模块功能划分及依赖关系,能够推断代码逻辑结构,确保覆盖所有类和函数。
    • 3.系分的流程图时序图及附加说明,能够推断代码实现逻辑,确保覆盖所有的if判断和for循环。
    • 4.任何开发人员可以从系分推断某个类某个函数某个if判断某个for循环。如果系分存在但代码不存在,则要删除系分或补全代码。
    • 5.任何开发人员可以从某个类某个函数某个if判断某个for循环反推系分,如果代码存在但系分不存在,应当补全系分或删除代码。
    • 6.任意两个开发人员,根据系分推断出来的某个类某个函数某个if判断某个for循环是一致的。如果差异较大,那极有可能是系分的模块功能划分粒度太粗,时序图流程图及附加说明模棱两可,这也可以那也可以,应当及时优化系分或优化代码。
    • 7.任意两个开发人员,根据某个类某个函数某个if判断某个for循环反推出来的系分是一致的。如果差异较大,那极有可能是系分的模块功能划分粒度太粗,时序图流程图及附加说明模棱两可,这也可以那也可以,应当及时优化系分或优化代码。
  • 二.存在问题:

    • 1.当前强平系分缺少模块划分,功能划分,缺少依赖关系,只根据系分无法推断代码逻辑结构,无法推断强平服务有多少类,多少函数。

    • 2.当前强平系分缺少模块时序图,模块流程图,功能时序图,功能流程图,缺少附加说明,只根据系分无法推断代码实现逻辑,无法推断强平服务每一个函数有多少if判断,多少for循环。

    • 3.无法从当前强平代码推断系分的模块划分,功能划分,依赖关系。

    • 4.无法从当前强平代码推断系分的时序图,流程图,附加说明。

    • 5.当前强平运行维护中发现的bug,开发人员直接修改代码,想到什么修改什么,修改之前没有跟团队其他成员交流沟通解决方案,代码修正后没有同步修正系分。

    • 6.以上问题直接导致系分已经严重脱离实际,阅读系分除了浪费时间似乎没什么用处,增加很多技术上和业务上的沟通成本,尤其是接下来远程团队协作极其不利(整个保证金任何一个环节理解出错编码出错操作出错,所有以前的努力全部归零)。

  • 三.解决方案:

    • 1.尽快安排补充和更新系分,让系分和代码保持一致,在编写系分过程中,必定会找出以前从来没有发现得bug,这个时间精力投入是值得的。

    • 2.每次补充或更新完系分,应当集思广益,找其他开发同学确认是否合理,是否有更优方案,三人行必有我师焉,三个臭皮匠顶个诸葛亮。

    • 3.每次编写或更新完代码,应当新增或更新系分,确保系分和代码一致,并找其他开发同学确认。

    • 4.如果有条件,系分和代码要定期安排其他团队审查,确保每一部分都是易理解的,严谨的,只有这样才能让团队外的人“快速“看透理解,反之,缺胳膊少腿模棱两可的系分,团队外的人肯定看不明白,或者短时间看不明白,审查失败。

强平jraft阻塞和运维问题

  • 强平的初级目标是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监控主要包括各业务逻辑指定时间窗口内的最大耗时,最小耗时,平均耗时,总次数,正常次数,异常次数,每秒钟的调用次数(吞吐量),以及他们的报警提醒

  • 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,但nacos由其他团队维护,万一他们删库跑路了,强平服务还能正常使用吗?叫天天不应叫地地不声。

  • 可以设计一个兜底方案,在紧急情况可使用本地配置文件,或者应用程序启动时从运营支撑系统查询(rpc),或者应用程序运行过程中监听运营支撑系统的配置更改消息(kafka)

强平服务对10取模问题

  • 强平服务准生产环境针对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取模速度要快得多。

强平服务保序问题(暂未确认)

  • 一.业务特性

    • 1.所有强平流程对单个账户不同操作保持顺序
    • 2.有些操作,比如入金和交割,来者不拒,不需要对其他操作保序
  • 二.当前程序底层数据结构(队列)设计,有可能出现以下情况:

    • 1.一个账户的强平流程未完成,阻塞其他账户强平,也就是不同账户的强平流程是串行,前面账户强平流程未结束,导致后面账户的强平流程永远不会发生
    • 2.同一个强平流程本来可以并行执行的强平步骤,最后变成串行化,同一个强平步骤本来可以并行执行的强平RPC,最后变成串行化,比如强平流程有损降档部分减仓步骤需要逐个串行发送rpc,假若有10个仓位,要重复执行10次,强平发送rpc部分减仓申请,永续接收部分减仓申请,强平等待,永续处理,永续发送回执,强平处理回执。
    • 4.同一个账户的高优先级操作,比如交割,在强平流程中途起效了,但导致整个流程数据,尤其是跟接管户资金池对账不上
  • 三.解决方案:

    • 1.如果出现第一个问题,需要为每个账户建立单独的队列,把队列当做concurretHashMap的value,把accountId当做concurrentHashMap的key,确保每个账户操作互不影响,账户之间无先后关系,账户内部才有先后关系
    • 2.如果出现第二个问题?能否让永续提供批量接口?或者强平计算哪些仓位需要部分减仓,然后并行发送rpc,减少单次流程的耗时?
    • 3.如果出现第三个问题,某个操作到来时,要先对操作类型进行确认,默认是队列尾部插入,如果有入金这种高优先级操作,则在队列头部插入,处理业务时从队列头部开始消费
    • 4.如果出现第四个问题,和“强平系分流程梳理”类似,需要产品列出各种场景的流程图,说明清楚每种流程的每个步骤会不会发生入金交割插队操作,是否允许入金交割插队,插队后是否立刻终止剩余流程,入金交割完成后,水位异常和水位正常分别怎么处理

强平rpc接口重试问题

  • 一.业务特性:

    • 1.部分外部服务接口(比如资产)要求调用方(强平)提供重试功能
    • 2.只要返回码非0,就得重试
    • 3.只要超时和网络异常,也得重试。
  • 二.存在问题:

    • 1.强平系分缺少重试设计,缺少哪些接口需要重试
    • 2.重试的规则不清楚,返回异常时要重试几次,每次重试的间隔时间多久,重试后还是失败怎么办
  • 三.解决方式:

    • 1.尽快制订详尽的系分文档,说明哪些接口需要重试和它们各自的重试规则
    • 2.部分rpc接口通信可改成kafka方式通信,调用方(比如强平)发送消息到kafka的topic1(相当于发送请求参数),接收方(比如资产服务)订阅topic1消息消费处理,并把处理结果推送到topic2,调用方(比如强平)订阅topic2消息消费(相当于接收响应结果)

强平enum类规范化问题

  • 一.业务特性:

    • 整个强平服务运行需要大量元数据,配置参数,数据字典支撑,有些还需要运行中动态修改
    • 元数据属于公共代码,所有模块和功能共享,牵一发而动全身,系分文档和程序代码,都要注重命名方式,调用细节,使用场景
  • 二.存在问题(以enum类举例):

    • 类名规范

      • 有些是Enum结尾的,比如ServiceActionEnum
      • 有些不是Enum结尾的,比如LiqAccountStatus
    • 是否存在自定义构造函数

      • 1.有些存在自定义构造函数,比如ServiceActionEnum

      • 2.有些不存在自定义构造函数,比如LiqAccountStatus

    • 构造函数参数个数

      • 1.有些自定义构造函数包含有3个参数,比如包括index,英文编码,中文描述,比如ServiceActionEnum

      • 2.有些自定义构造函数包含2个参数,只包括index,英文编码,比如LiqBanEnum

      • 3.有些自定义构造函数包含2个参数,但只有英文编码和中文描述,没有index,比如RiskHandleMode

      • 4.有些自定义构造函数只有1个参数,只有index,比如LiqMode

    • 程序使用和传输方式

      • 1.有些直接使用enum的index
      • 2.有些直接使用enum的name
      • 3.有些使用enum的自定义index,也就是构造函数的第一个值,其类型是数字,比如最常见的ServiceActionEnum
      • 4.有些使用的是英文编码code,其类型为字符串,比如RiskHandleMode
  • 三.推荐方案:

    • 1.从对称性角度看,有必要制订一个enum类规范改进,考虑到整个系统的使用范围和影响,程序上比较推荐仿照ServiceActionEnum,自定义3个参数的构造函数,包括index,英文code,中文描述,使用时特别是传输时尽可能传输自定义参数的index,数字类型
    • 2.把enum,常量类的每个字段,nacos的配置参数,springboot的启动参数,从运营支撑系统动态加载动态更新的一些数据字典,补充到系分文档,系分文档必须说明元数据每个字段的使用场景

强平流程编排工具literflow问题

  • 一.业务特性:

    • 1.强平流程步骤较多,以RM模式举例,至少包括权限判断步骤,延时爆仓步骤,初始化步骤,取消活动单步骤,安全降档步骤,有损降档部分减仓步骤,有损降档永续接管步骤,阶梯强平下平仓单步骤,阶梯强平转仓给做市商步骤,期权接管步骤,恢复步骤
    • 2.强平流程的步骤错综复杂,每个步骤还包含很多RPC调用和消息发送和接收,需要状态维护,涉及超时,重试,异常,熔断,降级,限流
  • 二.存在问题:

    • 1.当前强平流程通过literflow组织代码,存在以下问题。
    • 2.强平流程编排工具literflow使用体验较差,通过idea无法点击xml某个节点进入对应的springBean,也没有办法从javabean中的类名或springBeanName反向定位该bean究竟被哪些chain引用,非常不便捷
    • 3.强平流程编排工具literflow校验不智能,无法通过idea动态的校验语法是否出错,这直接另外一个问题,比如开发人员很随便命名也检测不出来,比如margin-core和margin-express的有些类功能是一样的,但是名字是不一样的,造成阅读障碍。
    • 4.强平流程编排工具literflow的java代码路由方式也比较难于理解,以margin-express为例,至少有6个地方使用flowExecutor.execute2Resp,至少有51个地方使用return chainname,这里的6个地方使用使用了字符串拼接,不便于查找下一个literflow组件在哪里,另外51个地方使用return chainname则使用了很多if,把不同模块的功能代码耦合起来,不便于后续的升级维护,或者说后续如果要升级维护,则需要同步修改至少2个模块
    • 5.强平流程编排工具literflow限制了分布式,限制了模块化,所有literflow调度的模块只能在单机运行,如果要把同一流程不同模块在不同机器运行会很困难
  • 三.推荐方案:

    • 1.利用响应式变成理念来组织强平流程代码

    • 2.事件驱动替代literflow流程调度框架,把每个流程步骤当做模块,模块间通过kafka消息总线通信,每个模块输出作为一个消息发布到消息队列的一个topic,下游模块订阅,下游模块输入就是上游模块的输出,或者下游模块的运行是由上游模块的发布事件驱动

    • 3.每个模块消费上游模块消息时,必须做幂等处理,因为上游可能重复发送了消息,每个模块处理完后必须确保输出消息投递成功才提交offset,这样如果中途宕机,重启后可以重新消费

强平入口问题

强平工具类问题:

helper util

问题:

强平浮盈浮亏模块每秒钟都在对每个用户推送浮盈浮亏

价格波动

用户杠杆调整

用户入金

交割,开仓,平仓等仓位变化

时间窗口

接管户多次创建

接管户差错队列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值