一个极简化的风控

 之前给很多公司和部门做过风控系统的技术分享,听的时候大家都很过瘾,不过不久以后好像都忘记了,没一个人记得。

去年公司风控部门采购的风控系统不好用了,要换,我帮他们写了一个新的。后来部门调整,我不负责了,就交接出去了;近日听说他们部门又采购了一个风控系统,我的也不用了。问原因说:只有后台,没页面。

没页面开发一个就好了,几个页面有什么复杂的,不比招标、采购、熟悉一个新的巨无霸风控系统简单?

我估计真实的原因是:技术不好意思说自己没弄懂,毕竟代码写的还是有点复杂的。于是我就又写了一个极简的风控系统,大概就不到20个类,代码不超过1000行。

 

虽然较小,功能可不弱

1,,支持接入任何业务,配置任何规则,接入任何数据

2,性能很高,单机tps1.5w+(我个人的2核,小米pro笔记本)

48c699f824a32d60e102466f94f0811cdeb.jpg

——————先讲风控系统指标要求和特点——————————————————————————————

如果要给风控系统打个标签的话,区别于其他业务系统的核心标签有几个

标签1,博弈,快速变化,高灵活性
标签2,高性能,高并发,大数据

风控的性质本身就是博弈,就是一个变化的过程。风控系统必须如实反映,适应这种性质。只有将规则的变化成本降到最低,才能满足博弈的快速变化的要求。所以多数的风控系统本身都是基于规则的,规则都是可配的。并且我们要求这种可配置的规则,能够满足任何可能的逻辑表达式。

风控的业务性质决定了他是一个请求,检测N个规则,需要M笔数据(每条规则m笔),从交易到规则到数据是一个IO和计算不断放大的过程,而风控系统本身是一个长尾系统,它最多有核心系统零点几或者几倍但不可能有超出核心系统N*M倍的预算,这意味着我们对风控系统的性能要求也是很高的——我们不能以核心系统同样的代码质量和技术设计来要求他,必须精心设计它,它才能满足大数据,高并发,高性能和低成本低预算的要求。

但是,对于大多数的公司来说,TPS就那么几个,数据就那么几条,即使放大N*M倍也不过几百,随便找台机器做风控硬件基础都绰绰有余。即使代码写的稍微烂一点,加点机器也抗的住。但是要招一个精通风控,同时能优化性能几倍几十倍的技术架构,是比较难的,是不划算的,也是没意义的。

同时,在这种情况下,反而风控系统的可伸缩性要求很高。风控系统能不能适应这种只部署一台或者几台的极简化部署?

对于这99%的需求,现有市面上的绝大多数风控系统,都显得的太臃肿了。他们吹嘘了太多无用的概念,带来了太高的运维,和扩展成本,而这些,对于这99%的用户,基本毫无意义。

 

 

————————————————————————核心设计图

6d26bf91e1ca1746cc211c4d4d8afde4deb.jpg

里面关键几个设计部分是

1,基于事件,数据和规则|逻辑分离(VarHandler接口和varNotify皆苦)。这样这个系统才有很高的性能
2,基于引擎接口,实现各种简单|复杂,效率和功能的平衡适配
3,统一了传统风控系统中的事前事中事后风控系统设计,三者合一(即使ResultProcessor部分设计)————事实上在技术上根本不用区分什么事前,事后。只需要指定一个timeout,如果在timeout以前检测出风险了,不需要区分这个规则是事前规则还是时候规则,实时返回就可以了,这就是所谓的事前。这里的本质是:检测速度到底能有多快,多准确。如果所有规则都能100%命中,纳秒级返回,那所有规则都可以是实时。这是传统风控一个误区很大的地方。是技术被业务误导很严重的地方。

——————————————核心代码

package wanglin.inspect;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Set;
import java.util.concurrent.*;

@Service
@Slf4j
public class InspectServiceImpl extends BaseService implements InspectService {
    @Autowired
    RuleService ruleService;
    ConcurrentMap<InspectContext, CountDownLatch> locks = new ConcurrentHashMap<>();

    @Override
    public Object inspect(String bizType, Object request) throws TimeoutException {
        Object         result = null;
        InspectContext context = createInspectContext(bizType, request);
        try {
            log.info("受理检测请求{},{},{}", context.uuid, context.bizType, context, request);
            execute(context);
            result = getInspectResult(context);
            return result;
        } catch (TimeoutException e){
            log.info("检测请求超时{},{},{}", context.uuid, e);
            throw e;
        } finally {
            log.info("检测请求结果{},{},{}", context.uuid, result);
        }
    }

    @Override
    public void varValueNotify(String uuid, String varName, Object value) {
        log.info("收到变量回调{},{},{}", uuid, varName,value);
        InspectContext context = getInspectContext(uuid);
        ruleService.varNotify(uuid, varName, context);
        //set request result
        configuration.getResultProcessor(context.bizType.resultProcessorName).process(context);
        //notify实时线程
        if (null != context.result) {
            if (null != locks.get(context)) {
                locks.get(context).countDown();
            }
        }
    }

    private Object getInspectResult(InspectContext context) throws TimeoutException {
        CountDownLatch cdl = new CountDownLatch(1);
        try {
            locks.put(context, cdl);
            if (cdl.await(context.bizType.timeout, TimeUnit.MILLISECONDS)) {
                return context.result;
            } else {
                throw new TimeoutException("交易检测超时");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("交易检测侧异常");
        } finally {
            locks.remove(context);
        }
    }

    private void execute(InspectContext context) {
        context.vars.forEach((varName, var) -> {
            configuration.getVarHandler(var).handle(context.uuid, varName, context.bizType, context.request);
        });
    }

    private InspectContext createInspectContext(String bizType, Object request) {
        BizType   bzType = configuration.getBizType(bizType);
        Set<Rule> rules  = configuration.getRules(bizType);
        Set<Var>  vars   = configuration.getVars(bizType);
        return new InspectContext(bzType, request, rules, vars);
    }
}
package wanglin.inspect;

import org.springframework.stereotype.Service;

import java.util.Set;

@Service
public class RuleService extends BaseService {
    public void varNotify(String uuid, String varName,InspectContext context) {
        Set<Rule> rules = referRules(uuid, varName);
        rules.forEach(rule -> {
            try {
                EngineService engine      = configuration.getEngine(rule.engine);
                Object        ruleContext = engine.buildRuleContext(context);
                Object        ruleResult  = engine.execute(rule, ruleContext);
                context.getRule(rule).setResult(ruleResult);
            }catch (Exception e){
                context.getRule(rule).setException(e);
            }
        });
    }

    private Set<Rule> referRules(String uuid, String varName) {
        return null;
    }
}

 

 

其他类不贴了,这两个类包含了一个极简化的风控核心的逻辑。

任何一个设计良好的系统的核心代码,都是极简的。dubbo其实原型就几十行,其他都是在进行一些工程上的处理。

这个核心麻雀虽小,五张俱全。

可以通过VarHandler接口进行数据扩展

可以通过EngineService进行引擎扩展

可以通过ResultProcessor接口进行结果处理方式的扩展

 

比如业务需要规则,任何用户非大学毕业就拒绝交易

假设请求数据{username:xxx,cardid:xxxxxxx....}biztype为testBizType

1,设定规则 $user. schooling < 4  //1小学,2初中,3,高中,4,大学

2,写一个UserHandler 实现 VarHandler

public UserHandler implements VarHandler{
InspectService inspectService;

public void handle(...){

     //从第三方获取用户学历

         inspectService.varNotify(uuid,"user",xxxx);

}

}

 

EngineService如下

public interface EngineService {
    /**
     * 解析规则中需要的变量
     * @param rule
     * @return
     */
    Set<String> analyze(Rule rule);

    /**
     * 执行规则
     * @param rule
     * @param ruleContext
     * @return
     */
    Object execute(Rule rule, Object ruleContext);

    /**
     * 构建引擎上下文
     * @param inspectContext
     * @return
     */
    Object buildRuleContext(InspectContext inspectContext);
}

一般上会有两个实现:EL,脚本。

脚本可以扩展出很多其他类型,比如决策树,积分卡等,现在比较火的机器学习用这个实现一个扩展就可以了。

 

 

哦,漏放接口了 

如图

5828300b73d6b0a2bd3ce780aacd3152b53.jpg

 

 

 

 

转载于:https://my.oschina.net/u/3364724/blog/1976066

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值