乐趣区

关于风险控制:风控决策引擎决策流构建实战

引言

本篇次要聚焦介绍风控决策引擎中决策树编排能力的构建。决策引擎是风控的大脑,而决策树的编排能力和体验是构建大脑的伎俩,如何构建 高效 丝滑 稳固牢靠 的决策树编排能力,是对风控决策引擎的一大挑战,本篇文章和大家分享一下过往构建心得。

背景

任何零碎在初期构建必定不是往“一步到位 ”的方向去构建的,只是架构设计者尽量向前期 可扩大 可保护 的方向去搭建。好的底层设计,不怕产品前期疯狂迭代,且改变调整不便。蹩脚的“填鸭式 ”代码,可能在过后为了尽快实现了性能,最终也会逐渐倒退成“ 屎山”,保护老本越来越高,要么跑路,要么只能重整旗鼓。

MVP 小步迭代 1.0

此阶段指标:最小化可行产品(MVP);小布迭代,疾速上线;一人分饰多角色。

风控部门成立初期,人员少,短少 UED 和 前端,毕竟风控自身对视觉设计和前端不是刚需,次要是后端研发和策略经营反抗黑产即可。此时为了能尽快上线决策树性能,研发人员本着 小步快跑 的思维,间接在代码层资源目录 resource 下搁置决策树动态配置文件(具体实现在下文合成),每次更改都须要发版。自身引擎的构建也是不欠缺的,须要增加的性能很多,一周发个几版也是粗茶淡饭的事,此阶段大家也是能承受的。

“由静转动”2.0

此阶段指标:无需发版,生产疾速变更;稳定性相干思考。

随着部门队伍的逐渐壮大,以及研发流程的标准,风控策略经营人员对于决策编排的 响应时效 可视化能力 需要越来越迫切,对于研发须要发版能力部署新的决策 能力现状不满,黑产是高效的,然而研发发版又是须要编排和工夫的,大家都要发版,且集中在一个发版周期,策略周一提出的批改,待到周三和大家的需要一起上,此时黑产早撸完跑路了。同时发版是有肯定的危险的,出错了须要立刻回滚,此时又延误了策略上线的工夫。

基于上述,咱们思考到是时候凋谢生产环境间接可视化的编排决策树能力了,然而咱们没有前端的同学,找别的部门借可能又不相熟决策引擎这一套流程标准,沟通老本还高。那折中了一个计划:将动态配置文件挪到 DB 存储中去,且配置以文本字符的模式展示在前端即可,不须要简单的前端设计,只须要简略的表单文本框填充即可满足研发批改决策流的诉求。这样 让本来动态的配置“动”起来 ,间接在生产可配置, 大大提高了生产部署的效率

可视化决策流编排 3.0

此阶段指标:高效、稳固、智能的可视化决策树编排能力产品构建

接入风控的业务线越来越多,研发人员忙于危险场景对应的变量开发迭代,此时还须要分出一部分精力负责批改决策树。2.0 版本的决策树对经营来说就是一段字符串,不是一棵树,策略经营是没方法批改,也不敢批改,出错的危险太大。思考到整个风控的体量和模式曾经十分稳固了,也有肯定的工夫去思考将决策编排做成一个可视化的产品 交付策略人员应用 了,毕竟决策树的调整自身也是策略的职责之一,须要将此 积淀为一个高可用的产品

咱们参照了业内 BPMN 工作流的前端款式设计规范,摘取了在风控决策树种须要用到的元素,构建了本人的决策引擎智能编排能力,可视化的拖拽节点,可齐全交付 给策略人员自行配置应用。

设计实现

技术选型

决策树,实际上就是一个变种 DAG(有向无环图),图中的节点在业务层面有不同的属性及性能。

那么如何存储这个 DAG 构造呢?用二维数组存储,是不能满足节点属性及边属性要求的,一是边界没法定义,可能这棵树很大,二是假如属性由关联表来实现,就会很割裂,没法直观看失去。

其实图能够用 链表 示意,链表的存储构造第一反馈就是 JSON 或者 XML 来示意。能够设想,如果用 JSON 来示意的话,层级嵌套关系会十分繁琐,毕竟 JSON 是用来序列化数据用的,展现方面,还是 XML 增加属性更为不便直观。

数据结构

举例繁难决策树如下

如上决策树用 XML 数据结构示意如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<flow id="test01" desc="倡议决策流">

  <!-- 开始节点 -->
  <start id="start">

    <!-- 链接到下一节点 -->
    <link to="black01"/>
  </start>

  <!-- 名单节点 -->
  <nameList id="black01" desc="黑名单">

    <!-- 名单属性:名单类型:黑 / 白 / 灰;畛域类型;适用范围 -->
    <field key="type" val="black"/>
    <field key="domain" val="10001,10002"/>
    <field key="scope" val="deviceHash,phone,uid"/>
    <link to="split01"/>
  </nameList>

  <!-- 分流节点 -->
  <split id="split01" desc="是否为微信渠道">

    <!-- 条件分支 -->
    <condition order="0" desc="是" expr="system =='wechat'"to="strategy01"/>
    <condition order="10" desc="否" to="strategy02"/>
  </split>

  <!-- 策略节点 -->
  <strategy id="strategy01" desc="微信专属策略">

    <!-- 关联专属策略元数据 -->
    <field key="strategyGuid" val="25F7C71A5F834F24A12C478CEE4CB9EB"/>
    <link to="end"/>
  </strategy>
  <strategy id="strategy02" desc="非微信渠道策略">
    <field key="strategyGuid" val="0FC8A95A4D6A4F169C77950BB4A98D80"/>
    <link to="end"/>
  </strategy>

  <!-- 完结节点 -->
  <end id="end" desc="完结"/>
</flow>

上述数据结构十分直观的示意了以后须要绘制的决策树数据结构,相较于 JSON 的数据表现形式,XML 更灵便,扩大更不便,在横向和深度上能够有较好的均衡

决策流解析

XML 是很成熟的技术实现了,市面上有很多解析 XML 的开源实现,如上数据结构我应用 common-digester解析,POM 中引入如下依赖即可:

<!-- https://mvnrepository.com/artifact/commons-digester/commons-digester -->
<dependency>
    <groupId>commons-digester</groupId>
    <artifactId>commons-digester</artifactId>
    <version>1.8.1</version>
</dependency>

实体关系如下:

XML 数据解析如下:

@Data
public class FlowEntity {
    private String id;
    private String desc;

    private INode startNode;

    private Map<String, INode> nodeMap = new HashMap<>();}
Digester digester = new Digester();

// parse flow node
digester.addObjectCreate("flow", FlowEntity.class);
digester.addSetProperties("flow");

// parse start node
digester.addObjectCreate("flow/start", StartNode.class);
digester.addSetProperties("flow/start");

// 在 FlowEntity 实现 addNode 办法,将以后节点录入
digester.addSetNext("flow/start", "addNode");
digester.addObjectCreate("flow/start/link", LinkBranch.class);
digester.addSetProperties("flow/start/link");

// 在 StartNode 实现 addLink 办法,将以后边录入
digester.addSetNext("flow/start/link", "addLink");

// parse split node
digester.addObjectCreate("flow/split", SplitNode.class);
digester.addSetProperties("flow/split");
digester.addSetNext("flow/split", "addNode");
digester.addObjectCreate("flow/split/condition", ConditionBranch.class);
digester.addSetProperties("flow/split/condition");

// 在 SplitNode 实现 addCondition 办法,将以后条件录入
digester.addSetNext("flow/split/condition", "addCondition");

// 省略...

InputStream inputStream = new ByteArrayInputStream(xmlResource.getBytes());
return (FlowEntity) digester.parse(inputStream);

其中 addNode 逻辑为将所有节点都存储在一个 nodeMap 构造内,并且如果以后节点是开始节点,则赋值到
startNode 节点。

当 XML 解析完后,此时关联关系还没有建设,轮询每个节点后将节点与节点之间分割起来,并且校验节点是够存在,确保能关联成一个树。

public void assembleToNode(Map<String, INode> nodeMap) {if (Objects.isNull(nodeMap)) {return;}

    if (!nodeMap.containsKey(this.to)) {throw new RuntimeException(String.format("%s to: %s can't find node from nodeMap", this.desc, this.to));
    }

    this.toNode = nodeMap.get(this.to);
}

决策流执行

决策的执行只须要从 startNode 执行开始,递归执行 ,直到找到惟一的进口弹出即可。留神,策略接口是有输入决策后果的,如果是回绝的话,此时能够 间接中断流程 执行,返回后果即可。

@Override
public void execute(FlowContext context) {

    // 进口
    if (this instanceof EndNode) {return;}

    // 递归执行
    this.execute(context);
}

其中,SplitNode节点执行须要计算 条件表达式 ,只有满足一个条件,即可确定往下走的节点, 子类笼罩实现 如下:

注:条件表达式我之前独自发了一篇文章,感兴趣的话欢送关注,可在我的 历史文章归档 中查找,此处就不在开展阐明了。

@Override
public void execute(FlowContext context) {Validate.notEmpty(condition, "node id: {} desc: {} [condition] is empty", this.getId(), this.getDesc());

    // 被动判断
    Optional<ConditionBranch> target = condition.stream().filter(c -> c.evaluate(context)).findFirst();

    // TODO: 思考返回默认兜底分支节点
    if (!target.isPresent()) {throw new RuntimeException("node id: {} ConditionBranch expr execute find nothing, please check your expr condition");
    }

    target.get().getToNode().execute(context);
}

StrategyNode 节点执行原理和 SplitNode 统一,只须要子类覆写实现办法,去执行相应的规定引擎,获取到决策后果,即可判断走向,此处就不在列出。

如上设计好了决策树的存储构造,再配合前端同学构建的基于 BPMN 流图的款式配合,定制风控须要的节点 信息和表白,即可随时构建一棵现实的树(此处一句话带过,但在丝滑编排和辅助校验上,前端同学付出了很多,当然这不是本篇文章的重点)。

总结

本文分享了决策引擎中决策流图的思考及构建过程,从最小可用产品上线撑持业务倒退到积淀出可视化编排能力的工作区。当然,本文仅仅展现了通用决策流的思考构建过程,显示业务中还是会遇到各种挑战,比方 对性能的要求 对老本的管制 等等,挑战十分多,我将在后续一一分享进去,欢送关注。

往期精彩

  • 从 0 到 1 智能风控决策引擎构建
  • 性能优化必备——火焰图
  • 我是怎么入行做风控的

欢送关注公众号:咕咕鸡技术专栏
集体技术博客:https://jifuwei.github.io/

退出移动版