• 强平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万数据以下,每时每刻增删改查都高速运转。

    • 强平服务的数据保存在数据库后,开发成本,运维监控成本,定位问题的成本,解决问题的成本,都会大幅度降低。

    • 口说无凭,实践才是检验真理的唯一标准,不妨设计一个实验对比

    实验目的

    • 分别测试jraft和mysql作为容灾持久化方式时,错误率,稳定性

    • 分别测试jraft和mysql作为容灾持久化方式时,吞吐量

    • 分别测试jraft和mysql作为容灾持久化方式时,平均处理时间

    • 分别测试jraft和mysql作为容灾持久化方式时,最大处理时间

    硬件环境

    • 实验环境仿照准生产环境,5台机器,所有accountId的尾数都为0

    数据来源

    • 存在100万accountId结尾为0的用户。

    • 每秒钟有随机1万个用户消息提交到kafka需要处理。

    • 持续运行30分钟,总共消息量30x60x1万=1800万

    • 应用程序监听kafka消息,监听到即处理,处理完则提交offset,jraft架构通过4个轮子持久化,mysql架构则通过提交数据库持久化

    • 处理每个消息中途会停留1毫秒(模拟调用外部rpc)

    • 每个消息处理完毕把json格式的日志打印到日志文件,包括以下字段,当前kafka的offset,请求信息,处理结果(true/false),开始时间,结束时间,总耗时

    • 测试完毕把日志文件同步一份到es通过kibana统计,或者把同步到prometheus通过grafana分析,或者手写java统计

    • 统计的指标主要包括

      总消息数

      总成功处理数

      总失败处理数

      每秒钟处理消息多少个

      每秒钟处理成功消息多少个

      每秒钟处理失败消息多少个

      平均每个消息处理时间

      消息处理时间最长的10%消息,其平均消息处理时间

    jraft架构应用代码设计

    监听mq消息
    
    幂等判断
    
    处理结果=失败(暂定为失败)
    
    根据accountId查询本地内存
     
    try{
        System.out.println("业务处理开始,4个轮子开始");
    	模仿rpc休息1毫秒
    	System.out.println("业务处理结束,4个轮子结束");	
    
    	处理结果=true(更改为成功)
    }catch(业务内部失败,rpc调用失败){  
    
        如果是业务中途失败,包括jraft同步失败,需要恢复本地内存
        log4j2记录日志消息 	
        重试暂略
      	
    }finally{
    	提交kafka的offset,如果提交失败,重新消费即可,因为前面有幂等处理
    	log4j2记录请求信息,处理结果,开始时间,结束时间,总耗时,以便后续统计吞吐量和平均处理时间
    	平常的开始时间,结束时间,总耗时单位为毫秒,在这里暂时使用微妙作为单位,更有说服力
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    mysql架构应用代码设计

    监听mq消息
    
    幂等判断
    
    处理结果=失败(暂定为失败)
    
    根据accountId查询guava内存
    
    不存在则从mysql查询(这一步骤和jraft主节点不同)
    
    try{
    
    	System.out.println("业务处理开始");
    
    	模仿rpc休息1毫秒
    
    	手动开启mysql事务(innodb)
    
    	把最新的account写入mysql
    
    	把最新的account写入guava
    
    	提交mysql事务
    	
    	System.out.println("业务处理结束");
    
    	处理结果=true(更改为成功)
    }catch(业务内部失败,rpc调用失败或mysql提交失败){  	
    
    	如果是业务中途失败,包括提交到mysql失败,需要恢复guava
        log4j2记录日志消息
        重试暂略  		
      	
    }finally{
    	提交kafka的offset,如果提交失败,重新消费即可,因为前面有幂等处理
    	log4j2记录请求信息,处理结果,开始时间,结束时间,总耗时,以便后续统计吞吐量和平均处理时间
    	平常的开始时间,结束时间,总耗时单位为毫秒,在这里暂时使用微妙作为单位,更有说服力
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    jraft架构交互设计

    • 0号节点作为jraft主节点
    • 1号节点作为jraft从节点
    • 2号节点作为jraft从节点
    • 3号节点作为jraft从节点
    • 4号节点作为jraft从节点
    • 主节点挂掉,重新选主
    kafka jraft主节点 外部系统 本地文件 jraft的4个从节点 监听消息 rpc耗时1毫秒 返回响应 阻塞同步最新数据 同步成功 阻塞同步最新数据(并发) 2个返回则同步成功 提交 kafka jraft主节点 外部系统 本地文件 jraft的4个从节点

    mysql架构交互设计简易版

    • 0号节点作为应用程序,启动一个有监听消息权限的jvm,还启动了另外一个无监听消息权限的jvm

    • 1号节点空闲,无任何应用程序

    • 2号节点空闲,无任何应用程序

    • 3号节点作为数据库主库

    • 4号节点空闲

    • 0号节点的无监听消息权限的jvm是无状态的,不会处理任何业务,内存数据始终为空,占用资源极低

    • 一旦发生异常,自动切换程序(哨兵),立刻kill掉有监听消息权限的jvm,停止监听消息

    • 自动切换程序(哨兵),对无监听消息权限的jvm,授权,允许其监听消息

    kafka 应用程序0号节点 外部系统 mysql主节点 监听消息 另外两个节点空闲 rpc耗时1毫秒 返回响应 阻塞同步最新数据 同步成功 提交 kafka 应用程序0号节点 外部系统 mysql主节点

    mysql架构交互设计升级版

    • mysql架构简易版,有2个节点是空闲没有利用起来,这里利用起来
    • 上游修改,建立3个队列topic0,topic1,topic1
    • 上游发送消息是,accountId对3取模,结果0推送到topic0,结果1推送到topic1,结果2推送到topic2
    • 0号节点作为应用程序,启动一个有监听消息权限的jvm,监听topic0,还启动了另外一个无监听消息权限的jvm,未来监听topic1
    • 1号节点作为应用程序,启动一个有监听消息权限的jvm,监听topic1,还启动了另外一个无监听消息权限的jvm,未来监听topic2
    • 2号节点作为应用程序,启动一个有监听消息权限的jvm,监听topic2,还启动了另外一个无监听消息权限的jvm,未来监听topic0
    • 3号节点作为数据库主库,建立3个分区,0号分区,1号分区,2号分区,分别存储topic0消息处理结果,topic1消息处理结果,topic2消息处理结果
    • 4号节点作为数据库从库
    • 所有节点无监听消息权限的jvm是无状态的,不会处理任何业务,内存数据始终为空,占用资源极低
    • 一旦发生异常,自动切换程序(哨兵),立刻kill掉有监听消息权限的jvm,停止监听消息
    • 自动切换程序(哨兵),对无监听消息权限的jvm,授权,允许其监听消息
    上游系统 kafka 应用程序0号节点 外部系统 mysql主节点 发送消息 accountId对3取模 结果0推送到topic0 结果0推送到topic1 结果0推送到topic2 监听0号消息 0号节点开始监听topic0 1号节点开始监听topic1 2号节点开始监听topic2 rpc耗时1毫秒 返回响应 阻塞同步最新数据 mysql主节点进行分区 topic0的消息处理后落入0号分区 topic1的消息处理后落入1号分区 topic2的消息处理后落入2号分区 同步成功 提交 上游系统 kafka 应用程序0号节点 外部系统 mysql主节点

    mysql架构交互设计生产机版

    • 如果实验对比后(稳定性,吞吐量,处理时间),mysql架构是合理的,那么可以进一步优化
    • 预生产环境有10个zone,每个zone拥有5个机器,总共50台机器
    • 可以划分32台机器运行jvm,8台机器作为mysql主库(每个库4个分区),8台机器作为mysql从库,另外2台作为自动切换程序(哨兵)
    • 上游消息对32取模,分别落入topic0到topic31
    • 32个有监听权限的jvm分别监听topic0和topic31
    • 消息处理完后,分别写入8*4=32个分区
    • 一旦发生异常,自动切换程序(哨兵),立刻kill掉有监听消息权限的jvm,停止监听消息
    • 自动切换程序(哨兵),对无监听消息权限的jvm,授权,允许其监听消息
    • 如果达到预期的情况下,每个jvm处理的账户量是32分之1,而原来处理的账户量是十分之一
    • 如果原来处理量十分之一能满足需求,由于现在这种方案每个节点的内存使用率只有原来的三分之一, 再增大2倍用户cpu内存也是绰绰有余。

    任务排期

    任务效果时长
    jraft架构应用程序的框架搭建和应用代码编写,日志埋点
    mysql架构应用程序的框架搭建和应用代码编写,日志埋点
    kafka数据准备,每秒钟推送1万消息,持续30分钟
    jraft架构整体测试,调优,及分析稳定性,吞吐量,响应性能
    mysq架构简易版测试,调优,及分析稳定性,吞吐量,响应性能
    mysq架构升级版测试,调优,及分析稳定性,吞吐量,响应性能
  • 相关阅读:
    【day5】数组
    JavaScript中的特殊数据类型和其具体用法
    【离散化】802. 区间和
    电脑重装系统后内存占用高怎么解决?
    猿创征文|强力推荐的开发工具——>IDEA和FinalShell
    uni-app系列:uni.navigateTo传值跳转
    .NET Emit 入门教程:第六部分:IL 指令:1:概要介绍
    通过WSL在阿里云上部署Django项目MySQL
    触摸一体机广告机开发板_MTK联发科平台超小型安卓主板方案
    Django(5)视图
  • 原文地址:https://blog.csdn.net/k3108001263/article/details/125476941