【Aviator】(二)应用实战
正如上一篇博客所讲:“项目中之前的计算公式统一维护在base包(base包的公式会被订单、产品、api、财务、还款等系统依赖),由于公式会频繁地改动,造成base频繁升级,其他系统也得跟着发版”中存在的问题,把公式做成灵活可配,就是一件有意义的事情。
零、公式计算描述
此次需求,客户选择金融产品后,根据所选择的“自有”、“浦发”、“微众”三种类型资金来源,来执行试算操作,每执行一次试算,需要44个公式执行计算操作。
一、先看老方式:
最初的公式,都维护在代码中,虽然不够灵活,但是代码组织结构写的很棒:
1.接口统一定义所有公式的名称、方便各个子类在计算时,名称混乱;
2.父类实现一些不常变化的公式方法,定义计算所有公式时,每个公式的先后执行顺序;
3.根据业务标准(新车、旧车;资金来源;产品类型)等,定义第三层公式实现类(子类按照业务标准定义并继承)
4.对外暴露Adapter,外界通过这个类,来调用不同业务标准下的公式计算。
我还是很喜欢这块当时的设计,值得学习。(因代码不对外,只说一下思路)
二、再看新方式:
1.目的,实现公式改动时“后台可配,无需编码”。
2.整体思路:
3.几点实现细节
0)界面设计
如图,每种资金来源占用一个tab页,公式类型维护在枚举,公式内容及说明存放在mysql和redis中,每次更新通过“cache aside pattern”策略,同步更新redis和mysql,且记录每次公式的修改,便于历史记录查看,及错误追踪。
关于权限,在“菜单”入口处,设置只有“特定人员”可以看到并进入此页面。
1)aviator表达式引擎
使用aviator过程,遵循预先编译表达式的原则,从redis获取到公式集,根据公式id获取内容后,先编译,后执行:
/*** Double类型,算式执行* @param param 公式执行时的入参,线程安全* @param express string类型的表达式内容* @return * @throws ExpressionRuntimeException*/
public static Double doExecute(Integer key, String express,ConcurrentHashMap<String , Object> param) throws CompileExpressionErrorException,ExpressionRuntimeException {//1.编译表达式Expression expression = AviatorEvaluator.compile(express);if(expression ==null){throw new CompileExpressionErrorException(String.format("[%s]解析失败", ProdFormulaEnum.getNameByIndex(key)));}//2.执行计算,入参提前存放到ConcurrentHashMap中Object result = expression.execute(param);return (Double) result;
}
2)内置函数支持
对于公式中的三元运算、幂运算,参考第一篇中的讲解,注意,aviator不支持“[]”作为优先级的符号。
3)redis设计
起初AviatorUtil中,加入了本地缓存,代码如下:
/*** 缓存编译后的表达式,无需再次编译*/
ConcurrentHashMap<String , Expression> expressMap = new ConcurrentHashMap<String , Expression>();/*** 算式执行,返回类型为Double* @param param* @param express* @return* @throws ExpressionRuntimeException*/
public Double doExecute(HashMap<String , Object> param, String express)throws CompileExpressionErrorException,ExpressionRuntimeException {Expression expression = null;//设计思路,编译过的表达式,无需再次编译synchronized (lock) {expression = expressMap.get(express);if (expression == null) {expression = AviatorEvaluator.compile(express);expressMap.put(express, expression);}}if(expression ==null){throw new CompileExpressionErrorException(String.format("[%s]解析失败",express));}Object result = expression.execute(param);return (Double) result;
}
页面配置修改后,清空此“expressMap”内容,但此项目线上部署多台机器,公式一旦修改,nginx也只能清空调一台机子中的缓存,于是,本地缓存编译结果方式,不予考虑。
最终,通过“cache aside pattern”,把修改后的公式优先同步到redis,每次试算拉取redis中最新的表达式内容,再执行试算。并发情况下,redis压力正在测试。
遇到问题:
1.调试不便
复杂公式计算失败,很难定位到问题,建议打开TRACE模式
public static Double doExecute2(Integer key, String express,HashMap<String , Object> param)throws CompileExpressionErrorException,ExpressionRuntimeException {//如下所示AviatorEvaluator.setOption(Options.TRACE_EVAL, true);Expression expression = AviatorEvaluator.compile(express);if(expression ==null){throw new CompileExpressionErrorException(String.format("[%s]解析失败", ProdFormulaEnum.getNameByIndex(key)));}Object result = expression.execute(param);return (Double) result;
}
每一个公式,每一步的执行都非常清晰:
[Aviator TRACE] <JavaType, 90000.0, Double> -sub <JavaType, 55.0, Double> => <Double, 89945.0>
[Aviator TRACE] <JavaType, 1, Integer> / <Long, 12> => <Long, 0>
[Aviator TRACE] <Long, 0> * <JavaType, 1, Integer> => <Long, 0>
[Aviator TRACE] <Double, 89945.0> * <Long, 0> => <Double, 0.0>
[Aviator TRACE] <JavaType, 1, Integer> / <Long, 12> => <Long, 0>
[Aviator TRACE] <Long, 0> * <JavaType, 1, Integer> => <Long, 0>
[Aviator TRACE] <Long, 1> + <Long, 0> => <Long, 1>
[Aviator TRACE] Func : math.pow(<Long, 1>,<JavaType, 3, Integer>)
[Aviator TRACE] <Double, 1.0> -sub <Long, 1> => <Double, 0.0>
[Aviator TRACE] <Double, 0.0> / <Double, 0.0> => <Double, NaN>
[Aviator TRACE] <JavaType, 90000.0, Double> -sub <JavaType, 55.0, Double> => <Double, 89945.0>
[Aviator TRACE] <JavaType, 1, Integer> / <Long, 12> => <Long, 0>
[Aviator TRACE] <Long, 0> * <JavaType, 1, Integer> => <Long, 0>
[Aviator TRACE] <Double, 89945.0> * <Long, 0> => <Double, 0.0>
[Aviator TRACE] <Double, NaN> + <Double, 0.0> => <Double, NaN>
[Aviator TRACE] Result : NaN
2.整型返回值
Long result = (Long) AviatorEvaluator.execute("1+2+3");
如上,Aviator的数值类型仅支持Long和Double, 任何整数都将转换成Long, 任何浮点数都将转换为Double, 包括用户传入的变量数值。适配整型结果,2中方案:
1)统一将结果处理为BigDecimal类型
AviatorEvaluator.setOption(Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL, true);
2)AviatorUtil,重写一个返回值为Long的execute方法,我采用此方式。
3.线程安全
疑惑“多个线程同时调用 AviatorEvaluator.execute() 会存在多线程安全问题吗”,看了下execute方法源码,且在issue中,作者说明了“AviatorEvaluator.execute() 本身是线程安全的。只要你的表达式执行逻辑是线程安全的,传入的 env 是线程安全的,那就没有问题”。
https://github.com/killme2008/aviator/issues/91(参考这个issue)
4.能够撑住多大试算并发量,在测试过程中不断试验。
三、总结
1.新老方式都不错,开关可以随意切换,算是个容灾策略。
2.未来试算的方向应该是走动态配置,过渡过程需要维护2套代码。
3.aviator的一个缺点,公式中如果有逻辑,且不能通过三元运算转义,这时,配置中的公式就受限。
欢迎大佬们,指正,互相交流。
That's all.
2019年3月16日17:27:20 (周六)
【Aviator】(二)应用实战相关推荐
- zookeeper系列(二)实战master选举
2019独角兽企业重金招聘Python工程师标准>>> master选举 考虑7*24小时向外提供服务的系统,不能有单点故障,于是我们使用集群,采用的是Master+Slave.集群 ...
- Spark机器学习实战 (十二) - 推荐系统实战
0 相关源码 将结合前述知识进行综合实战,以达到所学即所用.在推荐系统项目中,讲解了推荐系统基本原理以及实现推荐系统的架构思路,有其他相关研发经验基础的同学可以结合以往的经验,实现自己的推荐系统. 1 ...
- Ceres Solver: 高效的非线性优化库(二)实战篇
Ceres Solver: 高效的非线性优化库(二)实战篇 接上篇: Ceres Solver: 高效的非线性优化库(一) 如何求导 Ceres Solver提供了一种自动求导的方案,上一篇我们已经看 ...
- zookeeper系列(二)实战master选举 1
zookeeper系列(一)zookeeper必知 zookeeper系列(二)实战master选举 zookeeper系列(三)实战数据发布订阅 zookeeper系列(四)实战负载均衡 zooke ...
- 从头开始学Tableau-第十二章(实战3 地图实践)
从头开始学Tableau-第十二章(实战3 地图实践) 第一章 第二章 第三章 第四章 第五章 第六章 第七章 第八章 第九章 第十章 第十章 数据源 这个实践主要是用地图来呈现数据,我们所用的是保险 ...
- 猫狗二分类实战(PyTorch)
PyTorch实战指南 文章目录 PyTorch实战指南 比赛介绍 文件组织架构 关于`__init__.py` 数据加载 模型定义 工具函数 配置文件 main.py 训练 验证 测试 帮助函数 使 ...
- OAuth2学习(二)——OAuth2实战
在上一篇文章中我们讲解了OAuth2的一些基本概念,对OAuth2有了基本的认识.这一节内容我们就讲解一下OAuth2实战,围绕OAuth2的基本类型进行实战讲解. 授权码模式(authorizati ...
- k8s redis集群_K8S系列二:实战入门
写在前面 本文是K8S系列第二篇,主要面向对K8S新手同学,阅读本文需要读者对K8S的基本概念,比如Pod.Deployment.Service.Namespace等基础概念有所了解.尚且不熟悉的同学 ...
- Android列表用法之二:实战ListView高级用法
在我们的项目中,并不是所有列表都是简单的使用.类似于新闻列表.QQ聊天列表等,具有图文并排的列表,每个item都有它不同的布局类型,都有其不同的实现方式.这类复杂的列表表现形式,在各类知名应用当中,不 ...
- redis(二)redis实战 使用redis进行文章的排序
2019独角兽企业重金招聘Python工程师标准>>> http://www.beckbi.cn/?p=172 redis实战使用redis进行文章的排序 转载于:https://m ...
最新文章
- 【播放器SDK】Android如何实现固定竖屏播放视频
- OSGI企业应用开发(二)Eclipse中搭建Felix运行环境
- 深度学习笔记一:稀疏自编码器
- 内存申请与一级二级指针
- java 去除 quot,JAVA去除web页面传入后台的特殊字符工具类 | 学步园
- java actionscript_ActionScript(对比Java)学习笔记二
- mybatis Table book.t_abmin not find
- psql sql语法
- 数据结构之红黑树插入案例详解
- 江苏省南京市谷歌高清卫星地图下载
- 云主服务器排行榜_国内云服务器排名
- PAT-求特殊方程的正整数解(简单编程题)
- 本科毕业论文外文翻译必须要翻译全文吗?
- 安装Dreamweaver CS5遇到的问题
- FIFO读数据异常分析
- 网易-资深iOS开发工程师
- 使用python turtle库13行代码实现奥运五环
- 反垃圾邮件系统|基于Springboot+vue 实现反垃圾邮件系统
- 重来之大学版|卸负篇——破除光环效应,学长学姐、教授老师真的有这么厉害吗?
- [黑马IOS自学第十四篇]Foundation框架学习