追逐代码质量: 决心采用 FIT

追逐代码质量: 决心采用 FIT,第1张

JUnit 假定测试的所有方面都是开发人员的地盘 而集成测试框架(FIT)在编写需求的业务客户和实现需求的开发人员之间做了协作方面的试验 这是否意味着 FIT 和 JUnit 是竞争关系呢?绝对不是!代码质量完美主义者 Andrew Glover 介绍了如何把 FIT 和 JUnit 两者最好的地方结合在一起 实现更好的团队工作和有效的端到端测试 在软件开发的生命周期中 每个人都对质量负有责任 理想情况下 开发人员在开发周期中 用像 Junit 和 TestNG 这样的测试工具保证早期质量 而质量保证团队用功能性系统测试在周期末端跟进 使用像 Selenium 这样的工具 但是即使拥有优秀的质量保证 有些应用程序在交付的时候仍然被认为是质量低下的 为什么呢?因为它们并没有做它们应当做的事

在客户 (编写应用程序需求的)业务部门和(实现需求的)开发团队之间的沟通错误 通常是摩擦的原因 有时还是开发项目彻底失败的常见原因 幸运的是 存在一些方法可以帮助需求作者和实现者之间尽早 沟通

FIT 化的解决方案

集成测试框架 (FIT)是一个测试平台 可以帮助需求编写人员和把需求变成可执行代码的人员之间的沟通 使用 FIT 需求被做成表格模型 充当开发人员编写的测试的数据模型 表格本身充当输入和测试的预期输出

图 显示了用 FIT 创建的结构化模型 第一行是测试名称 下一行的三列是与输入( value 和 value )和预期结果( trend() )有关的标题

图 用 FIT 创建的结构化模型

好消息是 对于编程没有经验的人也能编写这个表格 FIT 的设计目的就是让消费者或业务团队在开发周期中 尽早与实现他们想法的开发人员协作 创建应用程序需求的简单表格式模型 可以让每个人清楚地看出代码和需求是否是一致的

清单 是与图 的数据模型对应的 FIT 代码 不要太多地担心细节 —— 只要注意代码有多么简单 而且代码中没有包含验证逻辑(例如 断言等) 可能还会注意到一些与表 中的内容匹配的变量和方法名称 关于这方面的内容后面介绍

清单 根据 FIT 模型编写的代码

package acme fit impl; import acme sedlp trend Trender; import fit ColumnFixture; public class TrendIndicator extends ColumnFixture { public double value ; public double value ; public String trend(){ return Trender determineTrend(value value ) getName(); } }

清单 中的代码由研究上面表格并插入适当代码的开发人员编写 最后 把所有东西合在一起 FIT 框架读取表 的数据 调用对应的代码 并确定结果

FIT 和 JUnit

FIT 的优美之处在于 它让组织的消费者或业务端能够尽早参与测试过程(例如 在开发期间) JUnit 的力量在于编码过程中的单元测试 而 FIT 是更高层次的测试工具 用来判断规划的需求实现的正确性

例如 虽然 JUnit 擅长验证两个 Money 对象的合计与它们的两个值的合计相同 但 FIT 可以验证总的订单价格是其中商品的价格减去任何相关折扣之后的合计 区别虽然细微 但的确重大!在 JUnit 示例中 要处理具体的对象(或者需求的实现) 但是使用 FIT 时要处理的是高级的 业务过程

这很有意义 因为编写需求的人通常不太考虑 Money 对象 —— 实际上 他们可能根本不知道这类东西的存在!但是 他们确实要考虑 当商品被添加到订单时 总的订单价格应当是商品的价格减去所有折扣

FIT 和 JUnit 之间绝不是竞争关系 它们是保证代码质量的好搭档 正如在后面的 案例研究 中将要看到的

测试用的 FIT 表格

表格是 FIT 的核心 有几种不同类型的表格(用于不同的业务场景) FIT 用户可以用不同的格式编写表格 用 HTML 编写表格甚至用 Microsoft Excel 编写都是可以的 如图 所示

图 用 Microsoft Excel 编写的表格

也有可能用 Microsoft Word 这样的工具编写表格 然后用 HTML 格式保存 如图 所示

图 用 Microsoft Word 编写的表格

开发人员编写的用来执行表格数据的代码叫作 装备(fixture) 要创建一个装备类型 必须扩展对应的 FIT 装备 它映射到对应的表 如前所述 不同类型的表映射到不同的业务场景

用装备进行装配

最简单的表和装备组合 也是 FIT 中最常用的 是一个简单的列表格 其中的列映射到预期过程的输入和输出 对应的装备类型是 ColumnFixture

如果再次查看 清单 将注意到 TrendIndicator 类扩展了 ColumnFixture 而且也与图 对应 请注意在图 中 第一行的名称匹配完全限定名称( acme fit impl TrendIndicator ) 下一行有三列 头两个单元格的值匹配 TrendIndicator 类的 public 实例成员( value 和 value ) 最后一个单元格的值只匹配 TrendIndicator 中的方法( trend )

现在来看清单 中的 trend 方法 它返回一个 String 值 可以猜测得到 对于表中每个剩下的行 FIT 都会替换值并比较结果 在这个示例中 有三个 数据 行 所以 FIT 运行 TrendIndicator 装备三次 第一次 value 被设置成 value 设置成 然后 FIT 调用 trend 方法 并把从方法得到的值与表中的值比较 应当是 decreasing

通过这种方式 FIT 用装备代码测试 Trender 类 每次 FIT 执行 trend 方法时 都执行类的 determineTrend 方法 当代码测试完成时 FIT 生成如图 所示的报告

图 FIT 报告 trend 测试的结果

trend 列单元格的绿色表明测试通过(例如 FIT 设置 value 为 value 为 调用 trend 得到返回值 decreasing )

查看 FIT 运行

可以通过命令行 用 Ant 任务并通过 Maven 调用 FIT 从而简单地把 FIT 测试插入构建过程 因为自动进行 FIT 测试 就像 JUnit 测试一样 所以也可以定期运行它们 例如在持续集成系统中

最简单的命令行运行器 如清单 所示 是 FIT 的 FolderRunner 它接受两个参数 —— 一个是 FIT 表格的位置 一个是结果写入的位置 不要忘记配置类路径!

清单 FIT 的命令行

%>java fit runner FolderRunner /test/fit /target/

FIT 通过插件 还可以很好地与 Maven 一起工作 如清单 所示 只要下载插件 运行 fit:fit 命令 就 OK 了!

清单 Maven 得到 FIT

C:\dev\proj\edoa>maven fit:fit __ __ | \/ |__ _Apache__ ___ | |\/| / _` \ V / _) \ ~ intelligent projects ~ |_| |_\__ _|\_/\___|_||_| v build:start: java:prepare filesystem: java:pile: [echo] Compiling to C:\dev\proj\edoa/target/classes java:jar resources: test:prepare filesystem: test:test resources: test:pile: fit:fit: [java] right wrong ignored exceptions BUILD SUCCESSFUL Total time: seconds Finished at: Thu Feb : : EST

试用 FIT 案例研究

现在已经了解了 FIT 的基础知识 我们来做一个练习 如果还没有 下载 FIT 现在是下载它的时候了!如前所述 这个案例研究显示出可以容易地把 FIT 和 JUnit 测试组合在一起 形成多层质量保证

假设现在要为一个酿酒厂构建一个订单处理系统 酿酒厂销售各种类型的酒类 但是它们可以组织成两大类 季节性的和全年性的 因为酿酒厂以批发方式运作 所以酒类销售都是按桶销售的 对于零售商来说 购买多桶酒的好处就是折扣 而具体的折扣根据购买的桶数和酒是季节性还是全年性的而不同

麻烦的地方在于管理这些需求 例如 如果零售店购买了 桶季节性酒 就没有折扣 但是如果这 桶 不是 季节性的 那么就有 % 的折扣 如果零售店购买 桶季节性酒 那就有折扣 但是只有 % 桶更陈的非季节性酒的折扣达到 % 购买量达到 时 也有类似的规矩

对于开发人员 像这样的需求集可能让人摸不著头脑 但是请看 我们的啤酒 酿造行业分析师用 FIT 表可以很容易地描述出这个需求 如图 所示

图 我的业务需求非常清晰!

表格语义

这个表格从业务的角度来说很有意义 它确实很好地规划出需求 但是作为开发人员 还需要对表格的语言了解更多一些 以便从表格得到值 首先 也是最重要的 表格中的初始行说明表格的名称 它恰好与一个匹配的类对应( acme store discount DiscountStructureFIT ) 命名要求表格作者和开发人员之间的一些协调 至少 需要指定完全限定的表格名称(也就是说 必须包含包名 因为 FIT 要动态地装入对应的类)

请注意表格的名称以 FIT 结束 第一个倾向可能是用 Test 结束它 但要是这么做 那么在自动环境中运行 FIT 测试和 JUnit 测试时 会与 JUnit 产生些冲突 JUnit 的类通常通过命名模式查找 所以最好避免用 Test 开始或结束 FIT 表格名称

下一行包含五列 每个单元格中的字符串都特意用斜体格式 这是 FIT 的要求 前面学过 单元格名称与装备的实例成员和方法匹配 为了更简洁 FIT 假设任何值以括号结束的单元格是方法 任何值不以括号结束的单元格是实例成员

特殊智能

FIT 在处理单元格的值 进行与对应装备类的匹配时 采用智能解析 如 图 所示 第二行单元格中的值是用普通的英文编写的 例如 number of cases FIT 试图把这样的字符串按照首字母大写方式连接起来 例如 number of cases 变成 numberOfCases 然后 FIT 试图找到对应的装备类 这个原则也适用于方法 —— 如图 所示 discount price() 变成了 discountPrice()

FIT 还会智能地猜测单元格中值的具体 类型 例如 在 图 余下的八行中 每一列都有对应的类型 或者可以由 FIT 准确地猜出 或者要求一些定制编程 在这个示例中 图 有三种不同类型 与 number of cases 关联的列匹配到 int 而与 is seasonal 列关联的值则匹配成 boolean

剩下的三列 list price per case discount price() 和 discount amount() 显然代表当前值 这几列要求定制类型 我将把它叫作 Money 有了它之后 应用程序就要求一个代表钱的对象 所以在我的 FIT 装备中遵守少量语义就可以利用上这个对象!

FIT 语义总结

表 总结了命名单元格和对应的装备实例变量之间的关系

表 单元格到装备的关系 实例变量 单元格值 对应的装备实例变量 类型 list price per case listPricePerCase Money number of cases numberOfCases int is seasonal isSeasonal boolean

表 总结了 FIT 命名单元格和对应的装备方法之间的关系

表 单元格到装备的关系 方法

表格单元格的值 对应的装备方法 返回类型 discount price() discountPrice Money discount amount() discountAmount Money

该构建了!

要为酿酒厂构建的订单处理系统有三个主要对象 一个 PricingEngine 处理包含折扣的业务规则 一个 WholeSaleOrder 代表订单 一个 Money 类型代表钱

Money 类

第一个要编写的类是 Money 类 它有进行加 乘和减的方法 可以用 JUnit 测试新创建的类 如清单 所示

清单 JUnit 的 MoneyTest 类

package acme store; import junit framework TestCase; public class MoneyTest extends TestCase { public void testToString() throws Exception{ Money money = new Money( ); Money total = money mpy( ); assertEquals( $ total toString()); } public void testEquals() throws Exception{ Money money = Money parse( $ ); Money control = new Money( ); assertEquals(control money); } public void testMultiply() throws Exception{ Money money = new Money( ); Money total = money mpy( ); Money discountAmount = total mpy( ); assertEquals( $ discountAmount toString()); } public void testSubtract() throws Exception{ Money money = new Money( ); Money total = money mpy( ); Money discountAmount = total mpy( ); Money discountedPrice = total sub(discountAmount); assertEquals( $ discountedPrice toString()); } }

WholeSaleOrder 类

然后 定义 WholeSaleOrder 类型 这个新对象是应用程序的核心 如果 WholeSaleOrder 类型配置了桶数 每桶价格和产品类型(季节性或全年性) 就可以把它交给 PricingEngine 由后者确定对应的折扣并相应地在 WholeSaleOrder 实例中配置它

WholesaleOrder 类的定义如清单 所示

清单 WholesaleOrder 类

package acme store discount engine; import acme store Money; public class WholesaleOrder { private int numberOfCases; private ProductType productType; private Money pricePerCase; private double discount; public double getDiscount() { return discount; } public void setDiscount(double discount) { this discount = discount; } public Money getCalculatedPrice() { Money totalPrice = this pricePerCase mpy(this numberOfCases); Money tmpPrice = totalPrice mpy(this discount); return totalPrice sub(tmpPrice); } public Money getDiscountedDifference() { Money totalPrice = this pricePerCase mpy(this numberOfCases); return totalPrice sub(this getCalculatedPrice()); } public int getNumberOfCases() { return numberOfCases; } public void setNumberOfCases(int numberOfCases) { this numberOfCases = numberOfCases; } public void setProductType(ProductType productType) { this productType = productType; } public String getProductType() { return productType getName(); } public void setPricePerCase(Money pricePerCase) { this pricePerCase = pricePerCase; } public Money getPricePerCase() { return pricePerCase; } }

从清单 中可以看到 一旦在 WholeSaleOrder 实例中设置了折扣 就可以通过分别调用 getCalculatedPrice 和 getDiscountedDifference 方法得到折扣价格和节省的钱

更好地测试这些方法(用 JUnit)!

定义了 Money 和 WholesaleOrder 类之后 还要编写 JUnit 测试来验证 getCalculatedPrice 和 getDiscountedDifference 方法的功能 测试如清单 所示

清单 JUnit 的 WholesaleOrderTest 类

package acme store discount engine junit; import junit framework TestCase; import acme store Money; import acme store discount engine WholesaleOrder; public class WholesaleOrderTest extends TestCase { / Test method for WholesaleOrder getCalculatedPrice() / public void testGetCalculatedPrice() { WholesaleOrder order = new WholesaleOrder(); order setDiscount( ); order setNumberOfCases( ); order setPricePerCase(new Money( )); assertEquals( $ order getCalculatedPrice() toString()); } / Test method for WholesaleOrder getDiscountedDifference() / public void testGetDiscountedDifference() { WholesaleOrder order = new WholesaleOrder(); order setDiscount( ); order setNumberOfCases( ); order setPricePerCase(new Money( )); assertEquals( $ order getDiscountedDifference() toString()); } }

PricingEngine 类

PricingEngine 类利用 业务规则引擎 在这个示例中 是 Drools PricingEngine 极为简单 只有一个 public 方法 applyDiscount 只要传递进一个 WholeSaleOrder 实例 引擎就会要求 Drools 应用折扣 如清单 所示

清单 PricingEngine 类

package acme store discount engine; import drools RuleBase; import drools WorkingMemory; import drools io RuleBaseLoader; public class PricingEngine { private static final String RULES= BusinessRules drl ; private static RuleBase businessRules; private static void loadRules() throws Exception{ if (businessRules==null){ businessRules = RuleBaseLoader loadFromUrl(PricingEngine class getResource(RULES)); } } public static void applyDiscount(WholesaleOrder order) throws Exception{ loadRules(); WorkingMemory workingMemory = businessRules newWorkingMemory( ); workingMemory assertObject(order); workingMemory fireAllRules(); } }

Drools 的规则

必须在特定于 Drools 的 XML 文件中定义计算折扣的业务规则 例如 清单 中的代码段就是一个规则 如果桶数大于 小于 不是季节性产品 则订单有 % 的折扣

清单 BusinessRules drl 文件的示例规则

<rule set name= BusinessRulesSample xmlns= xmlns:java= xmlns:xs= instance xs:schemaLocation= rules xsd java xsd > <rule name= st Tier Discount > <parameter identifier= order > <class>WholesaleOrder</class> </parameter> <java:condition>order getNumberOfCases() > </java:condition> <java:condition>order getNumberOfCases() < </java:condition> <java:condition>order getProductType() == year round </java:condition> <java:consequence> order setDiscount( ); </java:consequence> </rule> </rule set>

标记团队测试

有了 PricingEngine 并定义了应用程序规则之后 可能渴望验证所有东西都工作正确 现在问题就变成 用 JUnit 还是 FIT?为什么不两者都用呢?通过 JUnit 测试所有组合是可能的 但是要进行许多编码 最好是用 JUnit 测试少数几个值 迅速地验证代码在工作 然后依靠 FIT 的力量运行想要的组合 请看看当我这么尝试时发生了什么 从清单 开始

清单 JUnit 迅速地验证了代码在工作

package acme store discount engine junit; import junit framework TestCase; import acme store Money; import acme store discount engine PricingEngine; import acme store discount engine ProductType; import acme store discount engine WholesaleOrder; public class DiscountEngineTest extends TestCase { public void testCalculateDiscount() throws Exception{ WholesaleOrder order = new WholesaleOrder(); order setNumberOfCases( ); order setPricePerCase(new Money( )); order setProductType(ProductType YEAR_ROUND); PricingEngine applyDiscount(order); assertEquals( order getDiscount() ); } public void testCalculateDiscountNone() throws Exception{ WholesaleOrder order = new WholesaleOrder(); order setNumberOfCases( ); order setPricePerCase(new Money( )); order setProductType(ProductType SEASONAL); PricingEngine applyDiscount(order); assertEquals( order getDiscount() ); } }

还没用 FIT?那就用 FIT!

在 图 的 FIT 表格中有八行数据值 可能已经在 清单 中编写了前两行的 JUnit 代码 但是真的想编写整个测试吗?编写全部八行的测试或者在客户添加新规则时再添加新的测试 需要巨大的耐心 好消息就是 现在有了更容易的方法 不过 不是忽略测试 —— 而是用 FIT! 

FIT 对于测试业务规则或涉及组合值的内容来说非常漂亮 更好的是 其他人可以完成在表格中定义这些组合的工作 但是 在为表格创建 FIT 装备之前 需要给 Money 类添加一个特殊方法 因为需要在 FIT 表格中代表当前货币值(例如 像 $ 这样的值) 需要一种方法让 FIT 能够认识 Money 的实例 做这件事需要两步 首先 必须把 static parse 方法添加到定制数据类型 如清单 所示

清单 添加 parse 方法到 Money 类

public static Money parse(String value){ return new Money(Double parseDouble(StringUtils remove(value $ ))); }

Money 类的 parse 方法接受一个 String 值(例如 FIT 从表格中取出的值)并返回配置正确的 Money 实例 在这个示例中 $ 字符被删除 剩下的 String 被转变成 double 这与 Money 中现有的构造函数匹配

不要忘记向 MoneyTest 类添加一些测试来来验证新添加的 parse 方法按预期要求工作 两个新测试如清单 所示

清单 测试 Money 类的 parse 方法

public void testParse() throws Exception{ Money money = Money parse( $ ); assertEquals( $ money toString()); } public void testEquals() throws Exception{ Money money = Money parse( $ ); Money control = new Money( ); assertEquals(control money); }

编写 FIT 装备

现在可以编写第一个 FIT 装备了 实例成员和方法已经在表 和表 中列出 所以只需要把事情串在一起 添加一两个方法来处理定制类型 Money 为了在装备中处理特定类型 还需要添加另一个 parse 方法 这个方法的签名与前一个略有不同 这个方法是个对 Fixture 类进行覆盖的实例方法 这个类是 ColumnFixture 的双亲

请注意在清单 中 DiscountStructureFIT 的 parse 方法如何 比较 class 类型 如果存在匹配 就调用 Money 的定制 parse 方法 否则 就调用父类( Fixture )的 parse 版本

清单 中剩下的代码是很简单的 对于图 所示的 FIT 表格中的每个数据行 都设置值并调用方法 然后 FIT 验证结果!例如 在 FIT 测试的第一次运行中 DiscountStructureFIT 的 listPricePerCase 被设为 $ numberOfCases 设为 isSeasonal 为 true 然后执行 DiscountStructureFIT 的 discountPrice 返回的值与 $ 比较 然后执行 discountAmount 返回的值与 $ 比较

清单 用 FIT 进行的折扣测试

package acme store discount; import acme store Money; import acme store discount engine PricingEngine; import acme store discount engine ProductType; import acme store discount engine WholesaleOrder; import fit ColumnFixture; public class DiscountStructureFIT extends ColumnFixture { public Money listPricePerCase; public int numberOfCases; public boolean isSeasonal; public Money discountPrice() throws Exception { WholesaleOrder order = this doOrderCalculation(); return order getCalculatedPrice(); } public Money discountAmount() throws Exception { WholesaleOrder order = this doOrderCalculation(); return order getDiscountedDifference(); } / required by FIT for specific types / public Object parse(String value Class type) throws Exception { if (type == Money class) { return Money parse(value); } else { return super parse(value type); } } private WholesaleOrder doOrderCalculation() throws Exception { WholesaleOrder order = new WholesaleOrder(); order setNumberOfCases(numberOfCases); order setPricePerCase(listPricePerCase); if (isSeasonal) { order setProductType(ProductType SEASONAL); } else { order setProductType(ProductType YEAR_ROUND); } PricingEngine applyDiscount(order); return order; } }

现在 比较 清单 的 JUnit 测试用例和清单 是不是清单 更有效率?当然 可以 用 JUnit 编写所有必需的测试 但是 FIT 可以让工作容易得多!如果感觉到满意(应当是满意的!) 可以运行构建 调用 FIT 运行器生成如图 所示的结果

图 这些结果真的很 FIT ! 结束语

lishixinzhi/Article/program/Java/gj/201311/27625

1一样的If--Then 句式与Rete引擎

三者都会把原来混乱不堪的if---else---elseif----else谜团,

拆成N条带优先级的"If 条件语句 then 执行语句" 的句式。

三者都主要使用foreward-chaining的Rete引擎,按优先级匹配条件语句,执行规则语句。

规则执行后会引发事实的变化,引擎又会重新进行条件匹配,直到不能再匹配为止,Rete的算法保证了效率的最高。

2开发人员使用的规则语言

21 Drools的XML框架+Java/Groovy/Python嵌入语言

Drools的用XML的<Conditons>、<Consequence> 节点表达If--Then句式,而里面可以嵌入上述语言的代码作为判断语句和执行语句。

其中Java代码会使用Antlr进行解释,而Groovy和Python本身就是脚本语言,可以直接调用。

Drools的聪明之处在于,用XML节点来规范If--Then句式和事实的定义,使引擎干起活来很舒服。

而使用Java,Groovy等原生语言来做判断和执行语句,让程序员很容易过渡、移植,学习曲线很低。

而是在数据库表中进行配置。因此我们常见的业务逻辑层的开发,并不能先设计出一个数据模型,然后再在此基础上抽象逻辑。但我们确实解决了业务逻辑层的业务逻辑配置问题。应该说我们的更实用一些。但是我们却没法去实现JSR94标准。我们不光处理业务逻辑。

我们以JBoss的Drools为例,再打包成一个规则包,也提到了规则引擎。

在我们的印象中,我们感觉规则引擎就是解决业务逻辑层的实现问题的。因此我们理所当然的觉得工作流中的某个节点的逻辑处理,应该可以用规则引擎来解决。但是也使得规则引擎的应用得到了很大的限制。

首先这种抽象本身需要一个复杂的分析过程,这需要有很强的分析设计能力。另外我们平时具体应用中的业务逻辑层,大量的逻辑都是对实际数据的处理,很多时候还是一个批量数据的处理,甚至有些逻辑需要的参数我们并不能定义在规则中。

因此我们发现Drools等规则引擎很难用,根本不是我们所需要的那样。有时候我们发现自己做的规则引擎并不是一个规则引擎。因为我们和像Drools这些规则引擎有很大的差别。

我们研究规则引擎也有一段时间了可能很多人还不了解规则引擎是什么东西,或者不知道规则引擎究竟有什么用。一个规则包相当于一个智能块。首先需要将我们具体应用中的业务逻辑做抽象,抽象成一条条规则之后。

但是当我们在使用上述这些规则引擎,却发现很难和我们实际应用的业务逻辑层的业务逻辑实现相对应。我们都知道工作流引擎,也听说过JBoss下面有个Drools,或者我们知道 weblogic或者Oracle也有自己的Business Rule,我们可能还听说过ILOG被IBM收购了,如果我们研究微软的WWF。当数据传递给这个智能块后,系统会以匹配的方式应用满足条件的逻辑处理。

当采用这种方式时,应该说逻辑更抽象了,在一个更高的层次加以抽象化的定义,那么工作流本身的逻辑也应该可以由规则引擎来解决,可能也知道其中有RuleSet等内容。国内的一些web快速开发平台,由于其规则引擎使用了匹配规则的方式来进行,因此在应用这些规则引擎时。另外我们也会觉得,平时项目当中的业务逻辑应该都可以用规则引擎来解决,还把所有业务逻辑层需要处理的 *** 作全部采用规则配置的形式

qlexpress-typical-demo - Taocode >

支付平台每日早晨会收到账务系统发送的批量扣款指令,这些扣款指令需要通过 支付路由 系统获取到最优的扣款支付渠道,进而再通过 支付网关 系统送往各个支付渠道进行扣款。为了保证扣款成功率及批扣的回盘速度,这些批量扣款指令需要在1小时内全部发出。

系统调用链如下:

在对公司PAY20的支付路由模块(采用Drools5实现)改造过程中,跑批压测发现随着并发的增大,路由处理的速度会急剧下降,单批路由(1000笔/批)的速度为10s左右,但压测过程中,单批路由的速度下降到120s+,这个速度显然是不可接受的。

鉴于目前的问题,在单批发送性能正常,压测多批并发发送性能下降。首先想到的是内存不足导致的FGC频繁引起的性能下降。

验证思路:

关注GC情况,发现在处理过程中,FGC次数并没有上涨,YGC频率大概为1次/每秒。GC情况并无异常,具体GC频率见下方:

根据上述的应用日志,发现在2018-10-25 19:22:09495~2018-10-25 19:22:09559时间段,drools规则引擎执行花费了64ms(正常情况下应该是小于1ms的)

检查该时间段的GC日志,并无CMS GC日志,只有一些YGC日志,YGC执行速度很快,不是引起性能较低的原因。

根据以上的GC分析,认定了 性能的降低与垃圾回收无关

在进行GC排查时,抽样了应用日志,发现StatefulKnowledgeSession的创建速度并不稳定,并发上去后,StatefulKnowledgeSession的创建速度会变得越来越慢,最高甚至达到100ms+的消耗。

写了一个测试类,模拟测试环境不停的进行session创建,并使用jprofiler工具进行监控,发现session创建的耗时在于ClassforName的反射上。如下图所示:

要加快StatefulKnowledgeSession的创建速度,那么就要避免每一次创建都去进行ClassforName的 *** 作。两个方案:

以上的两个方案实际并不友好,虽然提高了StatefulKnowledgeSession创建速度,但“provider为空的情况下创建的ksession”这个逻辑已经在线上验证过一段时间,现在为provider赋值,不知道会不会引起其他的逻辑BUG,所以 此方案放弃

基于以上两点分析,决定采用此方案进行性能优化,StatefulKnowledgeSession池的维护使用Apache旗下的common-pool2的开源工具类实现。common-pool2的使用此处不在赘述,仅说下几个要点:

通过方案二的处理,目前在相同运行环境下,压测每批交易的路由执行速度均能稳定在10s以内。

drools5的官方推荐使用方式,StatefulKnowledgeSession都是新new出来的,但在此我们使用的是共享session方式来解决性能问题(有局限性,需要结合自己业务规则),也算是一个新的解决问题的思路。记录一下,也希望能够帮到大家

drl文件不可以转excel。

drl的文件是一种数据文件,是钻孔文件,drl文件可以用Drools 或者DocObject这两款软件打开。Drools 是一个建立在Rete 模式匹配运算法则基础之上的准则执行引擎。Drools 的开发团队已经采用Rete这个运算法则,所以Drools 能够使用面向对象的程序设计语言,像Java、Python 以及Groovy。

以上就是关于追逐代码质量: 决心采用 FIT全部的内容,包括:追逐代码质量: 决心采用 FIT、drools规则的性能跟if else比较哪个好、正在学习Drools规则引擎,一直出现空指针问题,想请教一下各位大神等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/9581990.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-29
下一篇 2023-04-29

发表评论

登录后才能评论

评论列表(0条)

保存