正如上一篇博客所讲:“项目中之前的计算公式统一维护在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】(二)应用实战相关推荐

  1. zookeeper系列(二)实战master选举

    2019独角兽企业重金招聘Python工程师标准>>> master选举 考虑7*24小时向外提供服务的系统,不能有单点故障,于是我们使用集群,采用的是Master+Slave.集群 ...

  2. Spark机器学习实战 (十二) - 推荐系统实战

    0 相关源码 将结合前述知识进行综合实战,以达到所学即所用.在推荐系统项目中,讲解了推荐系统基本原理以及实现推荐系统的架构思路,有其他相关研发经验基础的同学可以结合以往的经验,实现自己的推荐系统. 1 ...

  3. Ceres Solver: 高效的非线性优化库(二)实战篇

    Ceres Solver: 高效的非线性优化库(二)实战篇 接上篇: Ceres Solver: 高效的非线性优化库(一) 如何求导 Ceres Solver提供了一种自动求导的方案,上一篇我们已经看 ...

  4. zookeeper系列(二)实战master选举 1

    zookeeper系列(一)zookeeper必知 zookeeper系列(二)实战master选举 zookeeper系列(三)实战数据发布订阅 zookeeper系列(四)实战负载均衡 zooke ...

  5. 从头开始学Tableau-第十二章(实战3 地图实践)

    从头开始学Tableau-第十二章(实战3 地图实践) 第一章 第二章 第三章 第四章 第五章 第六章 第七章 第八章 第九章 第十章 第十章 数据源 这个实践主要是用地图来呈现数据,我们所用的是保险 ...

  6. 猫狗二分类实战(PyTorch)

    PyTorch实战指南 文章目录 PyTorch实战指南 比赛介绍 文件组织架构 关于`__init__.py` 数据加载 模型定义 工具函数 配置文件 main.py 训练 验证 测试 帮助函数 使 ...

  7. OAuth2学习(二)——OAuth2实战

    在上一篇文章中我们讲解了OAuth2的一些基本概念,对OAuth2有了基本的认识.这一节内容我们就讲解一下OAuth2实战,围绕OAuth2的基本类型进行实战讲解. 授权码模式(authorizati ...

  8. k8s redis集群_K8S系列二:实战入门

    写在前面 本文是K8S系列第二篇,主要面向对K8S新手同学,阅读本文需要读者对K8S的基本概念,比如Pod.Deployment.Service.Namespace等基础概念有所了解.尚且不熟悉的同学 ...

  9. Android列表用法之二:实战ListView高级用法

    在我们的项目中,并不是所有列表都是简单的使用.类似于新闻列表.QQ聊天列表等,具有图文并排的列表,每个item都有它不同的布局类型,都有其不同的实现方式.这类复杂的列表表现形式,在各类知名应用当中,不 ...

  10. redis(二)redis实战 使用redis进行文章的排序

    2019独角兽企业重金招聘Python工程师标准>>> http://www.beckbi.cn/?p=172 redis实战使用redis进行文章的排序 转载于:https://m ...

最新文章

  1. 【播放器SDK】Android如何实现固定竖屏播放视频
  2. OSGI企业应用开发(二)Eclipse中搭建Felix运行环境
  3. 深度学习笔记一:稀疏自编码器
  4. 内存申请与一级二级指针
  5. java 去除 quot,JAVA去除web页面传入后台的特殊字符工具类 | 学步园
  6. java actionscript_ActionScript(对比Java)学习笔记二
  7. mybatis Table book.t_abmin not find
  8. psql sql语法
  9. 数据结构之红黑树插入案例详解
  10. 江苏省南京市谷歌高清卫星地图下载
  11. 云主服务器排行榜_国内云服务器排名
  12. PAT-求特殊方程的正整数解(简单编程题)
  13. 本科毕业论文外文翻译必须要翻译全文吗?
  14. 安装Dreamweaver CS5遇到的问题
  15. FIFO读数据异常分析
  16. 网易-资深iOS开发工程师
  17. 使用python turtle库13行代码实现奥运五环
  18. 反垃圾邮件系统|基于Springboot+vue 实现反垃圾邮件系统
  19. 重来之大学版|卸负篇——破除光环效应,学长学姐、教授老师真的有这么厉害吗?
  20. [黑马IOS自学第十四篇]Foundation框架学习

热门文章

  1. ptx760功能图解_摩托罗拉ptx760写频软件
  2. JS 事件冒泡、捕获。学习记录
  3. Kotlin-简约之美-基础篇(三):基本控制语句
  4. 亚丁号云控之发布云控地址
  5. 揭秘香港房地产,未来中国之我见
  6. 原神如何修改服务器,原神PC端界面太大怎么修改 pc窗口界面调整方法分享[多图]...
  7. 捕鱼来了2017系列游戏
  8. JAVA热部署神器,JRebel破解版,JRebel免费实用插件
  9. [教程]Windows下使用Ladon批量爆破SSH弱口令
  10. C语言I博客作业05