文章目录
背景介绍
-
强平的初级目标是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记录请求信息,处理结果,开始时间,结束时间,总耗时,以便后续统计吞吐量和平均处理时间
平常的开始时间,结束时间,总耗时单位为毫秒,在这里暂时使用微妙作为单位,更有说服力
}
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记录请求信息,处理结果,开始时间,结束时间,总耗时,以便后续统计吞吐量和平均处理时间
平常的开始时间,结束时间,总耗时单位为毫秒,在这里暂时使用微妙作为单位,更有说服力
}
jraft架构交互设计
- 0号节点作为jraft主节点
- 1号节点作为jraft从节点
- 2号节点作为jraft从节点
- 3号节点作为jraft从节点
- 4号节点作为jraft从节点
- 主节点挂掉,重新选主
mysql架构交互设计简易版
-
0号节点作为应用程序,启动一个有监听消息权限的jvm,还启动了另外一个无监听消息权限的jvm
-
1号节点空闲,无任何应用程序
-
2号节点空闲,无任何应用程序
-
3号节点作为数据库主库
-
4号节点空闲
-
0号节点的无监听消息权限的jvm是无状态的,不会处理任何业务,内存数据始终为空,占用资源极低
-
一旦发生异常,自动切换程序(哨兵),立刻kill掉有监听消息权限的jvm,停止监听消息
-
自动切换程序(哨兵),对无监听消息权限的jvm,授权,允许其监听消息
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,授权,允许其监听消息
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架构升级版测试,调优,及分析稳定性,吞吐量,响应性能 |