一、背景

在数据治理时,经常会遇到个性化统计分析的场景:基于数据的某些属性进行组合筛选,只有符合条件的数据才进行统计分析。

传统的实现方式是:业务人员提供筛选条件,数据开发人员在ETL任务直接开发。这种方式主要有两个痛点:

  • 需求上线周期长
    开发人员须对ETL任务进行开发调试、发布上线,按天、周、月排期都有可能。
  • 统计口径不直观
    筛选条件都在ETL任务中,业务人员无法直观判断数据是否符合预期,往往在数据量波动比较明显的时候才会发现异常。

为了解决这些问题,数据治理平台提供了给数据打标签的功能:业务人员在数据治理平台上快速批量对数据打上个性化标签,数据开发人员只须筛选打上特定标签的数据,而无须直接对数据进行过滤筛选。通过这种方式,可以解决需求上线周期长、统计口径不直观的问题。

  • 数据开发人员只须一次性开发ETL任务,基于标签进行数据筛选即可,具体的数据筛选交由业务人员在数据治理平台上操作。
  • 业务人员在数据治理平台上面可以直观查看须统计的数据,可视化管理数据统计口径。

随着数据量的不断增长,单靠业务人员人工管理标签的工作会变得越来越繁琐:给10条数据打标签业务人员还可以接受,但要给成千上万条数据打标签时,正常人都会抗拒这种操作了。

所以基于规则自动给数据打标签的需求,自然就会提上议程。当业务人员需要的筛选条件简单时,数据治理平台可以快速开发功能进行支持,但随着业务的发展,筛选条件会变得越来越复杂。
为了支持这些条件,平台功能会变得越来越臃肿,而且还需要投入开发人力排期开发,无法快速满足日新月异的业务需求。

因此,经过调研之后,决定引入规则引擎Aviator,平台基于规则引擎提供可视化配置功能,由业务人员进行自定义配置,平台根据业务配置自动匹配符合条件的数据,打上对应的标签。

本文不涉及规则引擎的调研过程,相关规则引擎的优劣对比不在本文讨论范围。本文主要介绍规则引擎Aviator的基础语法,以及在数据治理中的使用实践。

二、规则引擎Aviator

2.1 简介

Aviator是一个高性能、轻量级的java语言实现的表达式求值引擎,主要用于各种表达式的动态求值。

官方地址:https://github.com/killme2008/aviatorscript

用户手册:https://www.yuque.com/boyan-avfmj/aviatorscript/cpow90

2.2 关键函数介绍

这里主要介绍关键函数:

字符串函数

函数名称 说明
string.contains(s1,s2) 判断 s1 是否包含 s2,返回 Boolean

Sequence 函数(集合处理)

函数名称 说明
seq.set(p1, p2, p3, …) 创建一个 java.util.HashSet 实例,添加参数到这个集合并返回。
seq.contains_key(map, key) 当 map 中存在 key 的时候(可能为 null),返回 true。对于数组和链表,key 可以是 index,当 index 在有效范围[0…len-1],返回 true,否则返回 false
include(seq,element) 判断 element 是否在集合 seq 中,返回 boolean 值,对于 java.uitl.Set 是 O(1) 时间复杂度,其他为 O(n)
seq.every(seq, fun) fun 接收集合的每个元素作为唯一参数,返回 true 或 false。当集合里的每个元素调用 fun 后都返回 true 的时候,整个调用结果为 true,否则为 false。
seq.not_any(seq, fun) fun 接收集合的每个元素作为唯一参数,返回 true 或 false。当集合里的每个元素调用 fun 后都返回 false 的时候,整个调用结果为 true,否则为 false。
seq.some(seq, fun) fun 接收集合的每个元素作为唯一参数,返回 true 或 false。当集合里的只要有一个元素调用 fun 后返回 true 的时候,整个调用结果立即为该元素,否则为 nil。

示例:

// 数据字段名与字段值的映射,字段值有可能为多个,所以统一为数组形式,方便处理
Map<String, List<String>> propNameValueMap = new HashMap<>(1);
tag.put("tagName", Arrays.asList("选项1", "选项4"));
// Aviator输入参数
Map<String, Object> env = new HashMap<>(1);
env.put("tag", propNameValueMap);// Aviator表达式,判断Aviator输入参数中是否有指定属性
String checkPropExpression = "seq.contains_key( tag, 'tagName')";// Aviator表达式,生成符合预期的选项值集合
String expectedValueExpression = "seq.set('选项1','选项2','选项3')";// Aviator表达式,生成一个匿名函数,判断字段取值X是否在选项值集合中
String includeFunc = "lambda(x) -> include( seq.set('选项1','选项2','选项3'), x )";// 数据字段值任意一个符合筛选条件即可:通过 seq.some 函数遍历字段取值,只要任意一个字段取值在选项值集合中即返回true
// 结果:true
System.out.println(AviatorEvaluator.execute("( seq.contains_key( tag, 'tagName') && seq.some( tag.tagNameCn, lambda(x) -> include( seq.set('选项1','选项2','选项3'), x ) end ) != nil )", env));// 数据字段值全部符合筛选条件:通过 seq.every 函数,遍历字段取值,当且仅当所有字段取值都在选项值集合中才会返回true
// 结果:false
System.out.println(AviatorEvaluator.execute("( seq.contains_key( tag, 'tagName') && seq.every( tag.tagName, lambda(x) -> include( seq.set('选项1','选项2','选项3'), x ) end ) )", env));// 数据字段值完全不符合筛选条件:通过 seq.not_any 函数,遍历字段取值,当且仅当所有字段取值都不在选项值集合中才会返回true
// 结果:false
System.out.println(AviatorEvaluator.execute("( seq.contains_key( tag, 'tagName') && seq.not_any( tag.tagName, lambda(x) -> include( seq.set('选项1','选项2','选项3'), x ) end ) )", env));// Aviator表达式,生成一个匿名函数,判断数据字段值是否包含特定的选项值。注意:若选项值有多个,则字段值当中必须包含所有的选项值。
// 比如特定的选项值为两个:“人杰”、“鬼雄”,则字符串“生当作人杰,死亦为鬼雄”是符合要求的;若选项值变成三个“人杰”,“鬼雄”,“英雄”时,则字符串“生当作人杰,死亦为鬼雄”不再符合要求,因为少了“英雄”
String containFunc = "lambda(x) -> seq.every( seq.set('选项1'), lambda(v) -> string.contains(x, v) end )";// 数据字段值全部包含特定选项值
// 结果:false。因为字段的第二个取值“选项2”不包含字符串“选项1”
System.out.println(AviatorEvaluator.execute("( seq.contains_key( tag, 'tagName') && seq.every( tag.tagName, lambda(x) -> seq.every( seq.set('选项1'), lambda(v) -> string.contains(x, v) end ) end ) )", env));

三、规则定义

3.1 数据匹配

目前业务侧暂只需要对数据进行匹配,不涉及数据计算。
因此基于常见的用户场景,数据治理平台抽象出两种运算符:比较运算符、范围运算符。

  • 比较运算符

定义如何比较字段取值与预期值。平台目前提供了三种比较方式:等于不等于包含

  • 范围运算符

由于字段取值与预期值都可能是多个,统一以数组形式存储数据,通过比较运算符得到每个字段值与预期值的比较结果后,须通过范围运算符定义多个比较结果如何进行组合运算,得到最终结果。平台目前提供了三个运算符:任意符合全部符合完全不符合

示例:

字段取值 比较运算符 范围运算符 预期值 匹配结果 说明
[选项1, 选项4] 等于 任意符合 [选项1, 选项2, 选项3] true 只要字段取值任意一个在预期值当中,即为true。选项1在预期值中,所以为true。
[选项1, 选项4] 等于 全部符合 [选项1, 选项2, 选项3] false 当且仅当字段取值全部在预期值当中,才为true。选项4不在预期值中,所以为false。
[选项1, 选项4] 等于 完全不符合 [选项1, 选项2, 选项3] false 当且仅当字段取值全部不在预期值当中,才为true。选项1在预期值中,所以为false。
[选项1, 选项4] 不等于 任意符合 [选项1, 选项2, 选项3] true 只要字段取值任意一个不在预期值当中,即为true。选项4不在预期值中,所以为true。
[选项1, 选项4] 不等于 全部符合 [选项1, 选项2, 选项3] false 当且仅当字段取值全部不在预期值当中,才为true。选项1在预期值中,所以为false。
[选项1, 选项4] 不等于 完全不符合 [选项1, 选项2, 选项3] false 当且仅当字段取值全部都在预期值当中,才为true。选项4不在预期值中,所以为false。
["生当作人杰,死亦为鬼雄", "英雄难过美人关"] 包含 任意符合 [人杰, 鬼雄] true 只要字段取值任意一个包含所有的预期值,即为true。第一个字段取值包含了所有预期值,所以为true。
["生当作人杰,死亦为鬼雄", "英雄难过美人关"] 包含 全部符合 [人杰, 鬼雄] false 当且仅当所有的字段取值都包含所有的预期值,即为true。第二个字段取值不包含预期值,所以为false。
["生当作人杰,死亦为鬼雄", "英雄难过美人关"] 包含 完全不符合 [人杰, 鬼雄] false 当且仅当所有字段取值都不包含所有的预期值,即为true。第一个字段取值包含了所有预期值,所以为false。

3.2 业务逻辑

数据匹配只能针对单个字段,业务往往需要针对多个字段进行组合排列,根据多个字段的匹配结果进行取舍。
因此数据治理平台提供了逻辑运算符,对多个字段的匹配结果进行与或运算。

  • 逻辑运算符

定义如何将多个字段的匹配结果进行与或运算,得到最终匹配结果。平台提供了两个运算符:

3.3 规则参数

基于数据匹配与业务逻辑,数据治理平台抽象出以下数据结构,方便业务进行个性化配置:

/*** 标签规则条件,指定各表达式之间的逻辑关系** @author chriscchen* @createtime 2022-07-30*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@JsonInclude(Include.NON_EMPTY)
@JsonIgnoreProperties(ignoreUnknown = true)
public class TagRuleCondition {/*** 逻辑运算符,定义如何将规则条件与规则表达式进行与或运算 {@link LogicOperator}*/private String logic;/*** 嵌套的规则条件。当需要对多个字段进行组合排列时,须嵌套规则条件。*/private List<TagRuleCondition> conditions;/*** 规则表达式,可直接对字段进行数据匹配*/private List<TagRuleExpression> expressions;
}/*** 标签规则表达式** @author chriscchen* @createtime 2022-07-30*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@JsonInclude(Include.NON_EMPTY)
@JsonIgnoreProperties(ignoreUnknown = true)
public class TagRuleExpression {/*** 字段名*/private String name;/*** 比较运算符 {@link CompareOperator}*/private String comparator;/*** 范围运算符 {@link ScaleOperator}*/private String scale;/*** 预期值*/private List<Object> value;
}

示例:
假设有2条数据如下:

名称 状态 分类 厂商
飞天 销售中 酱香型 茅台
五粮液 销售中 浓香型 五粮液

需要筛选状态为销售中,并且分类为酱香型的数据,生成的数据结构如下:

{"logic": "AND","expressions": [{"name": "状态","comparator": "=","scale": "any","value": ["销售中"]},{"name": "分类","comparator": "=","scale": "any","value": ["酱香型"]}]
}

3.4 表达式转换

基于3.3节的数据结构,参考2.2节的示例,生成Aviator表达式

/*** 根据用户配置的规则条件,生成Aviator的表达式** @param condition 用户配置的规则条件* @param keyPrefix 有嵌套属性时,需要加上属性前缀* @return Aviator的表达式*/private static String convertToAviatorExpression(TagRuleCondition condition, String keyPrefix)throws AbstractDataMapException {if (condition == null) {throw new CommonParamUnacceptableException("标签的规则条件不能为空");}if (StringUtils.isBlank(condition.getLogic())) {throw new CommonParamMissedException("缺少逻辑运算符,请检查参数:logic");}List<String> aviatorExpressions = new ArrayList<>();// 嵌套的规则条件。当需要对多个字段进行组合排列时,须嵌套规则条件if (CollectionUtils.isNotEmpty(condition.getConditions())) {for (TagRuleCondition subCondition : condition.getConditions()) {aviatorExpressions.add(convertToAviatorExpression(subCondition, keyPrefix));}}// 规则表达式,可直接对字段进行数据匹配if (CollectionUtils.isNotEmpty(condition.getExpressions())) {for (TagRuleExpression expression : condition.getExpressions()) {String valueSet = generateValueSetExpression(expression);// 检查属性是否存在String check = "seq.contains_key( " + keyPrefix + expression.getType()+ ", '" + expression.getName() + "')";// 属性字段名String propValue = keyPrefix + expression.getType() + "." + expression.getName();generateTagExpression(aviatorExpressions, expression, valueSet, check, propValue);}}switch (condition.getLogic()) {case LogicOperator.AND:return StringUtils.join(aviatorExpressions, " && ");case LogicOperator.OR:return StringUtils.join(aviatorExpressions, " || ");default:throw new CommonParamUnacceptableException("未知逻辑运算符,请检查参数:" + condition.getLogic());}}/*** 基于用户配置的规则表达式,生成预期值表达式** @param expression 用户配置的规则表达式* @return 预期值表达式*/private static String generateValueSetExpression(TagRuleExpression expression) {List<Object> value = expression.getValue();String valueSet;if (CollectionUtils.isEmpty(value)) {valueSet = "seq.set()";} else {Object elem = value.get(0);if (elem instanceof String) {// 字符串加上引号valueSet = "seq.set('" + StringUtils.join(value, "','") + "')";} else {// 其余类型直接拼接valueSet = "seq.set(" + StringUtils.join(value, ",") + ")";}}return valueSet;}/*** 生成标签类型的Aviator表达式** @param aviatorExpressions 生成的Aviator表达式,添加到此数组中* @param expression         用户配置的规则表达式数据结构* @param valueSet           提前生成的预期值集合表达式* @param check              提前生成的检测属性存在的表达式* @param propValue          属性字段名* @throws AbstractDataMapException exception*/private static void generateTagExpression(List<String> aviatorExpressions,TagRuleExpression expression, String valueSet, String check, String propValue)throws AbstractDataMapException {if (StringUtils.isBlank(expression.getComparator())) {throw new CommonParamMissedException("缺少比较运算符,请检查参数:operation");}if (StringUtils.isBlank(expression.getScale())) {throw new CommonParamMissedException("缺少范围运算符,请检查参数:scale");}switch (expression.getComparator()) {case CompareOperator.E: {generateTagEqualExpression(aviatorExpressions, expression, valueSet, check, propValue);break;}case CompareOperator.NE: {generateTagNotEqualExpression(aviatorExpressions, expression, valueSet, check, propValue);break;}case CompareOperator.CONTAIN: {generateTagContainExpression(aviatorExpressions, expression, valueSet, check, propValue);break;}default: {throw new CommonParamUnacceptableException("未知比较运算符,请检查参数:" + expression.getComparator());}}}/*** 生成标签类型 “等于” 的Aviator表达式** @param aviatorExpressions 生成的Aviator表达式,添加到此数组中* @param expression         用户配置的规则表达式数据结构* @param valueSet           提前生成的预期值集合表达式* @param check              提前生成的检测属性存在的表达式* @param propValue          属性字段名* @throws AbstractDataMapException exception*/private static void generateTagEqualExpression(List<String> aviatorExpressions,TagRuleExpression expression, String valueSet, String check, String propValue)throws AbstractDataMapException {switch (expression.getScale()) {case ScaleOperator.ANY: {aviatorExpressions.add("( " + check + " && seq.some( " + propValue+ ", lambda(x) -> include( " + valueSet + ", x ) end ) != nil )");break;}case ScaleOperator.ALL: {aviatorExpressions.add("( " + check + " && seq.every( " + propValue+ ", lambda(x) -> include( " + valueSet + ", x ) end ) )");break;}case ScaleOperator.NONE: {aviatorExpressions.add("( " + check + " && seq.not_any( " + propValue+ ", lambda(x) -> include( " + valueSet + ", x ) end ) )");break;}default:throw new CommonParamUnacceptableException("未知范围运算符,请检查参数:" + expression.getScale());}}/*** 生成标签类型 “不等于” 的Aviator表达式** @param aviatorExpressions 生成的Aviator表达式,添加到此数组中* @param expression         用户配置的规则表达式数据结构* @param valueSet           提前生成的预期值集合表达式* @param check              提前生成的检测属性存在的表达式* @param propValue          属性字段名* @throws AbstractDataMapException exception*/private static void generateTagNotEqualExpression(List<String> aviatorExpressions,TagRuleExpression expression, String valueSet, String check, String propValue)throws AbstractDataMapException {switch (expression.getScale()) {case ScaleOperator.ANY: {aviatorExpressions.add("( " + check + " && seq.some( " + propValue+ ", lambda(x) -> include( " + valueSet + ", x ) == false end ) != nil )");break;}case ScaleOperator.ALL: {aviatorExpressions.add("( " + check + " && seq.not_any( " + propValue+ ", lambda(x) -> include( " + valueSet + ", x ) end ) )");break;}case ScaleOperator.NONE: {aviatorExpressions.add("( " + check + " && seq.every( " + propValue+ ", lambda(x) -> include( " + valueSet + ", x ) end ) )");break;}default:throw new CommonParamUnacceptableException("未知范围运算符,请检查参数:" + expression.getScale());}}/*** 生成标签类型 “包含” 的Aviator表达式** @param aviatorExpressions 生成的Aviator表达式,添加到此数组中* @param expression         用户配置的规则表达式数据结构* @param valueSet           提前生成的预期值集合表达式* @param check              提前生成的检测属性存在的表达式* @param propValue          属性字段名* @throws AbstractDataMapException exception*/private static void generateTagContainExpression(List<String> aviatorExpressions,TagRuleExpression expression, String valueSet, String check, String propValue)throws AbstractDataMapException {switch (expression.getScale()) {case ScaleOperator.ANY: {aviatorExpressions.add("( " + check + " && seq.some( " + propValue+ ", lambda(x) -> seq.every( " + valueSet+ ", lambda(v) -> string.contains(x, v) end ) end ) != nil )");break;}case ScaleOperator.ALL: {aviatorExpressions.add("( " + check + " && seq.every( " + propValue+ ", lambda(x) -> seq.every( " + valueSet+ ", lambda(v) -> string.contains(x, v) end ) end ) )");break;}case ScaleOperator.NONE: {aviatorExpressions.add("( " + check + " && seq.not_any( " + propValue+ ", lambda(x) -> seq.every( " + valueSet+ ", lambda(v) -> string.contains(x, v) end ) end ) )");break;}default:throw new CommonParamUnacceptableException("未知范围运算符,请检查参数:" + expression.getScale());}}

3.5 数据转换

规则引擎Aviator需要将数据转换为Map<String, Object>类型,作为输入参数。

// 生成原始数据
Map<String, Object> rawData = new ObjectMapper().convertValue(record, new TypeReference<Map<String, Object>>() {});// 字段名与字段取值的映射 { propName -> [ propValue ] },字段取值统一以数组形式
Map<String, Object> nameValueMap = new HashMap<>(rawData.size()));
rawData.forEach((key, value) -> tagNameValueMap.put(key, Collections.singletonList(value)));

3.6 规则匹配

      // 预编译表达式,提高效率Expression aviatorExpression;try {aviatorExpression = AviatorEvaluator.compile("...");} catch (Exception e) {throw new CommonParamUnacceptableException("规则异常,请检查:" + e.getMessage(), e);}// 匹配数据Boolean result = (Boolean) aviatorExpression.execute(nameValueMap);

四、总结

基于规则引擎Aviator开发的自定义规则功能,既能让业务直接进行个性化规则配置,快速对匹配的数据打标签,也极大解放了平台开发的人力,从重复乏味的定制化功能开发抽身出来,为业务实现更有价值的功能。

本文只介绍自定义规则功能中的核心处理模块,还须基于数据结构进行前端开发,支持可视化配置,不再一一展开。

规则引擎在数据治理平台的实践相关推荐

  1. 货拉拉数据治理平台建设实践

    导读:在数据开发和数仓建设过程中,数据治理落地和提升数据质量的重要性逐渐凸显,本文将从货拉拉的数据治理实践出发,分享货拉拉在数据治理体系构建.数据质量平台建设.元数据平台建设方面的实践. 今天的分享会 ...

  2. 2W字剖析数据治理平台建设经验(建议收藏)

    点击上方 "大数据肌肉猿"关注, 星标一起成长 点击下方链接,进入高质量学习交流群 今日更新| 1052个转型案例分享-大数据交流群 00 前言 阿里巴巴一直将数据作为自己的核心资 ...

  3. 2万字揭秘阿里巴巴数据治理平台建设经验

    全网最全大数据面试提升手册! 00 前言 阿里巴巴一直将数据作为自己的核心资产与能力之一,通过多年的实践探索建设数据应用,支撑业务发展.在不断升级和重构的过程中,我们经历了从分散的数据分析到平台化能力 ...

  4. 阿里巴巴数据治理平台建设经验

    阿里巴巴一直将数据作为自己的核心资产与能力之一,通过多年的实践探索建设数据应用,支撑业务发展.在不断升级和重构的过程中,我们经历了从分散的数据分析到平台化能力整合,再到全局数据智能化的时代.如今,大数 ...

  5. 美团酒旅起源数据治理平台的建设与实践

    背景 作为一家高度数字化和技术驱动的公司,美团非常重视数据价值的挖掘.在公司日常运行中,通过各种数据分析挖掘手段,为公司发展决策和业务开展提供数据支持. 经过多年的发展,美团酒旅内部形成了一套完整的解 ...

  6. 一个典型案例:数据治理平台的建设与实践

    背景 作为一家高度数字化和技术驱动的公司,美团非常重视数据价值的挖掘.在公司日常运行中,通过各种数据分析挖掘手段,为公司发展决策和业务开展提供数据支持.经过多年的发展,美团酒旅内部形成了一套完整的解决 ...

  7. 数据治理平台工具前世今生

    前言 伴随着企事业单位信息化不断的深入.各种技术持续的发展以及人们对数据治理的认知不断加深,数据治理工具在过去的20年也不断的发展,笔者以某世界500集团企业案例为原型,介绍数据治理工具发展及变迁及未 ...

  8. 知乎的 Flink 数据集成平台建设实践

    简介:本文由知乎技术平台负责人孙晓光分享,主要介绍知乎 Flink 数据集成平台建设实践.内容如下: 1. 业务场景 : 2. 历史设计 : 3. 全面转向 Flink 后的设计 : 4. 未来 Fl ...

  9. IDC:阿里云获2021中国数据治理平台市场份额第一

    近日,领先的IT市场研究和咨询公司IDC发布<中国数据治理市场份额,2021:广泛落地,持续增长>报告,报告统计显示2021 年中国数据治理平台市场规模达 23.9 亿元.阿里云以23.4 ...

最新文章

  1. ios开发之UIView的frame、bounds跟center属性的区别(附图)
  2. zTree 显示为‘aa’,当选择aa时,传的参数为‘22’
  3. javascript如何释放内存
  4. QT的QTextStream类的使用
  5. 前端笔记-使用vue绑定id使得组件更加灵活(在使用echarts时常用)
  6. 错误913:未能找到ID为13的数据库。可能该数据库尚未激活,也可能正在转换过程中...
  7. 【elasticsearch】Elasticsearch : alias数据类型
  8. C语言 底层IO readwrite
  9. Android的Button监听
  10. 16kb等于多少b_面试官:MySQL索引为何选择B+树?
  11. C++RAII惯用法:C++资源管理的利器
  12. talk record
  13. Java应用题:模拟一个简单的购房商贷月供计算器,按照以下公式计算总利息和每月还款金额,总利息=贷款金额*利率,贷款年限不同利率也不同,这里规定只有三种年限、利率,见表
  14. 一些常用开发软件下载地址-msdn.itellyou.cn
  15. python 实时股票行情_python 实时获取股票行情脚本
  16. html中onfocus和onblur的使用
  17. 单片机的PWM控制,一篇即可学废
  18. 常见的机器学习数据挖掘知识点之Basis
  19. 网络电视html5软件,网络电视软件哪个好
  20. 汇佳学校绿龙冰球队 绽放冰雪强校激情

热门文章

  1. 为应用程序添加图标 ios_38个美丽的iOS应用程序图标设计,激发您的灵感
  2. HTML第一周学习笔记(标题重置版)
  3. 京东API开发系列:京东按关键字搜索商品 API / item_search - 按关键字搜索商品 API返回值说明
  4. 数据结构之ISAM文件和VSAM文件
  5. 插入数据报错:ISAM error:no free disk space
  6. JavaScript声明和使用变量
  7. Microsoft edge 下载阿里云excel
  8. C/C++:实现象棋游戏
  9. nutch-2.1导入eclipse+mysql运行
  10. 关于C++的内存管理