记一次drools5的性能优化过程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次drools5的性能优化过程相关的知识,希望对你有一定的参考价值。
参考技术A 支付平台每日早晨会收到账务系统发送的批量扣款指令,这些扣款指令需要通过 支付路由 系统获取到最优的扣款支付渠道,进而再通过 支付网关 系统送往各个支付渠道进行扣款。为了保证扣款成功率及批扣的回盘速度,这些批量扣款指令需要在1小时内全部发出。
系统调用链如下:
在对公司PAY2.0的支付路由模块(采用Drools5实现)改造过程中,跑批压测发现随着并发的增大,路由处理的速度会急剧下降,单批路由(1000笔/批)的速度为10s左右,但压测过程中,单批路由的速度下降到120s+,这个速度显然是不可接受的。
鉴于目前的问题,在单批发送性能正常,压测多批并发发送性能下降。首先想到的是内存不足导致的FGC频繁引起的性能下降。
验证思路:
关注GC情况,发现在处理过程中,FGC次数并没有上涨,YGC频率大概为1次/每秒。GC情况并无异常,具体GC频率见下方:
根据上述的应用日志,发现在2018-10-25 19:22:09.495~2018-10-25 19:22:09.559时间段,drools规则引擎执行花费了64ms(正常情况下应该是小于1ms的)
检查该时间段的GC日志,并无CMS GC日志,只有一些YGC日志,YGC执行速度很快,不是引起性能较低的原因。
根据以上的GC分析,认定了 性能的降低与垃圾回收无关 。
在进行GC排查时,抽样了应用日志,发现StatefulKnowledgeSession的创建速度并不稳定,并发上去后,StatefulKnowledgeSession的创建速度会变得越来越慢,最高甚至达到100ms+的消耗。
写了一个测试类,模拟测试环境不停的进行session创建,并使用jprofiler工具进行监控,发现session创建的耗时在于Class.forName的反射上。如下图所示:
要加快StatefulKnowledgeSession的创建速度,那么就要避免每一次创建都去进行Class.forName的操作。两个方案:
以上的两个方案实际并不友好,虽然提高了StatefulKnowledgeSession创建速度,但“provider为空的情况下创建的ksession”这个逻辑已经在线上验证过一段时间,现在为provider赋值,不知道会不会引起其他的逻辑BUG,所以 此方案放弃
基于以上两点分析,决定采用此方案进行性能优化,StatefulKnowledgeSession池的维护使用Apache旗下的common-pool2的开源工具类实现。common-pool2的使用此处不在赘述,仅说下几个要点:
通过方案二的处理,目前在相同运行环境下,压测每批交易的路由执行速度均能稳定在10s以内。
drools5的官方推荐使用方式,StatefulKnowledgeSession都是新new出来的,但在此我们使用的是共享session方式来解决性能问题(有局限性,需要结合自己业务规则),也算是一个新的解决问题的思路。记录一下,也希望能够帮到大家
记一次接口性能优化实践总结:优化接口性能的八个建议
前言
最近对外接口偶现504超时问题,原因是代码执行时间过长,超过nginx配置的15秒,然后真枪实弹搞了一次接口性能优化。在这里结合优化过程,总结了接口优化的八个要点,希望对大家有帮助呀~
- 数据量比较大,批量操作数据入库
- 耗时操作考虑异步处理
- 恰当使用缓存
- 优化程序逻辑、代码
- SQL优化
- 压缩传输内容
- 考虑使用文件/MQ等其他方式暂存,异步再落地DB
- 跟产品讨论需求最恰当,最舒服的实现方式
嘻嘻,先看一下我们对外转账接口的大概流程吧
1.数据量比较大,批量操作数据入库
优化前:
//for循环单笔入库
for(TransDetail detail:list){
insert(detail);
}
优化后:
// 批量入库,mybatis demo实现
<insert id="insertBatch" parameterType="java.util.List">
insert into trans_detail( id,amount,payer,payee) values
<foreach collection="list" item="item" index="index" separator=",">(
#{item.id}, #{item.amount},
#{item.payer},#{item.payee}
)
</foreach>
</insert>
性能对比:
单位(ms) for循环单笔入库 批量入库
500条 1432 1153
1000条 1876 1425
解析
- 批量插入性能更好,更加省时间,为什么呢?
打个比喻:假如你需要搬一万块砖到楼顶,你有一个电梯,电梯一次可以放适量的砖(最多放500),
你可以选择一次运送一块砖,也可以一次运送500,你觉得哪种方式更方便,时间消耗更少?
2.耗时操作考虑异步处理
耗时操作,考虑用异步处理,这样可以降低接口耗时。本次转账接口优化,匹配联行号的操作耗时有点长,所以优化过程把它移到异步处理啦,如下:
优化前:
优化后
匹配联行号的操作异步处理
性能对比:
假设一个联行号匹配6ms
同步 异步
500条 3000ms ~
1000条 6000ms ~
解析:
- 因为联行号匹配比较耗时,放在异步处理的话,同步联机返回可以省掉这部分时间,大大提升接口性能,并且不会影响到转账主流程功能。
- 除了这个例子,平时我们类似功能,如用户注册成功后,短信邮件通知,也是可以异步处理的,这个优化建议香饽饽的~
- 所以,太耗时的操作,在不影响主流程功能的情况下,可以考虑开子线程异步处理的啦。
3.恰当使用缓存
在适当的业务场景,恰当地使用缓存,是可以大大提高接口性能的。这里的缓存包括:Redis,JVM本地缓存,memcached,或者Map等。
这次转账接口,使用到缓存啦,举个简单例子吧~
优化前
以下是输入用户账号,匹配联行号的流程图
优化后:
恰当使用缓存,代替查询DB表,流程图如下:
解析:
- 把热点数据放到缓存,不用每次查询都去DB拉取,节省了这部分查SQL的耗时,美滋滋呀~
- 当然,不是什么数据都适合放到缓存的哦,访问比较频繁的热点数据才考虑缓存起来呢~
4. 优化程序逻辑、代码
优化程序逻辑、程序代码,是可以节省耗时的。
我这里就本次的转账接口优化,举个例子吧~
优化前:
优化前,联行号查询了两次(检验参数一次,插入DB前查询一次),如下伪代码:
punlic void process(Req req){
//检验参数,包括联行号(前端传来的payeeBankNo可以为空,但是如果后端没匹配到,会抛异常)
checkTransParams(Req req);
//Save DB
saveTransDetail(req);
}
void checkTransParams(Req req){
//check Amount,and so on.
checkAmount(req.getamount);
//check payeebankNo
if(Utils.isEmpty(req.getPayeeBankNo())){
String payeebankNo = getPayeebankNo(req.getPayeeAccountNo);
if(Utils.isEmpty(payeebankNo){
throws Exception();
}
}
}
int saveTransDetail(req){
String payeebankNo = getPayeebankNo(req.getPayeeAccountNo);
req.setPayeeBankNo(payeebankNo);
insert(req);
...
}
优化后:
优化后,只在校验参数的时候插叙一次,然后设置到对象里面~ 入库前就不用再查啦,伪代码如下:
void checkTransParams(Req req){
//check Amount,and so on.
checkAmount(req.getamount);
//check payeebankNo
if(Utils.isEmpty(req.getPayeeBankNo())){
String payeebankNo = getPayeebankNo(req.getPayeeAccountNo);
if(Utils.isEmpty(payeebankNo){
throws Exception();
}
}
//查询到有联行号,直接设置进去啦,这样等下入库不用再插入多一次
req.setPayeeBankNo(payeebankNo);
}
int saveTransDetail(req){
insert(req);
...
}
解析:
- 对于优化程序逻辑、代码,是可以降低接口耗时的。以上demo只是一个很简单的例子,就是优化前payeeBankNo查询了两次,但是其实只查一次就可以了。很多时候,我们都知道这个点,但就是到写代码的时候,又忘记了呀~所以,写代码的时候,留点心吧,优化你的程序逻辑、代码哦。
- 除了以上demo这点,还有其它的点,如优化if复杂的逻辑条件,考虑是否可以调整顺序,或者for循环,是否重复实例化对象等等,这些适当优化,都是可以让你的代码跑得更快的。
之前我这篇文章,也提了几个优化点噢,有兴趣的朋友可以看一下哈~
写代码有这些想法,同事才不会认为你是复制粘贴程序员
5. 优化你的SQL
很多时候,你的接口性能瓶颈就在SQL这里,慢查询需要我们重点关注的点呢。
我们可以通过这些方式优化我们的SQL:
加索引
- 避免返回不必要的数据
- 优化sql结构
- 分库分表
- 读写分离
有兴趣的朋友可以看一下我这篇文章呢,很详细的SQL优化点:
后端程序员必备:书写高质量SQL的30条建议
6.压缩传输内容
压缩传输内容,文件变得更小,因此传输会更快啦。10M带宽,传输10k的报文,一般比传输1M的会快呀;打个比喻,一匹千里马,它驮着一百斤的货跑得快,还是驮着10斤的货物跑得快呢?
解析:
- 如果你的接口性能不好,然后传输报文比较大的话,这时候是可以考虑压缩文件内容传输的,最后优化效果可能很不错哦~
7. 考虑使用文件/MQ等其他方式暂存数据,异步再落地DB
如果数据太大,落地数据库实在是慢的话,可以考虑先用文件的方式保存,或者考虑MQ,先落地,再异步保存到数据库~
本次转账接口,如果是并发开启,10个并发度,每个批次1000笔数据,数据库插入会特别耗时,大概10秒左右,这个跟我们公司的数据库同步机制有关,并发情况下,因为优先保证同步,所以并行的插入变成串行啦,就很耗时。
优化前:
优化前,1000笔先落地DB数据库,再异步转账,如下:
优化后:
先保存数据到文件,再异步下载下来,插入数据库,如下:
解析:
- 如果你的耗时瓶颈就在数据库插入操作这里了,那就考虑文件保存或者MQ或者其他方式暂存吧,文件保存数据,对比一下耗时,有时候会有意想不到的效果哦。
8.跟产品讨论需求最恰当,最舒服的实现方式
这点个人觉得还是很重要的,有些需求需要好好跟产品沟通的。
比如有个用户连麦列表展示的需求,产品说要展示所有的连麦信息,如果一个用户的连麦列表信息好大,你拉取所有连麦数据回来,接口性能就降下来啦。如果产品打桩分析,会发现,一般用户看连麦列表,也就看前几页~因此,奸笑,哈哈~ 其实,那个超大分页加载问题也是类似的。即limit +一个超大的数,一般会很慢的~~
总结
本文呢,基于一次对外接口耗时优化的实践,总结了优化接口性能的八个点,希望对大家日常开发有帮助哦~嘻嘻,有兴趣可以逛逛我的github哈,本文会收藏到github里滴哈
公众号
- 欢迎关注我个人公众号,交个朋友,一起学习哈~
以上是关于记一次drools5的性能优化过程的主要内容,如果未能解决你的问题,请参考以下文章