一個極簡化的風控

 以前給不少公司和部門作過風控系統的技術分享,聽的時候你們都很過癮,不過不久之後好像都忘記了,沒一我的記得。java

去年公司風控部門採購的風控系統很差用了,要換,我幫他們寫了一個新的。後來部門調整,我不負責了,就交接出去了;近日據說他們部門又採購了一個風控系統,個人也不用了。問緣由說:只有後臺,沒頁面。spring

沒頁面開發一個就行了,幾個頁面有什麼複雜的,不比招標、採購、熟悉一個新的巨無霸風控系統簡單?架構

我估計真實的緣由是:技術很差意思說本身沒弄懂,畢竟代碼寫的仍是有點複雜的。因而我就又寫了一個極簡的風控系統,大概就不到20個類,代碼不超過1000行。併發

 

雖然較小,功能可不弱運維

1,,支持接入任何業務,配置任何規則,接入任何數據機器學習

2,性能很高,單機tps1.5w+(我我的的2核,小米pro筆記本)ide

——————先講風控系統指標要求和特色——————————————————————————————高併發

若是要給風控系統打個標籤的話,區別於其餘業務系統的核心標籤有幾個性能

標籤1,博弈,快速變化,高靈活性
標籤2,高性能,高併發,大數據學習

風控的性質自己就是博弈,就是一個變化的過程。風控系統必須如實反映,適應這種性質。只有將規則的變化成本降到最低,才能知足博弈的快速變化的要求。因此多數的風控系統自己都是基於規則的,規則都是可配的。而且咱們要求這種可配置的規則,可以知足任何可能的邏輯表達式。

風控的業務性質決定了他是一個請求,檢測N個規則,須要M筆數據(每條規則m筆),從交易到規則到數據是一個IO和計算不斷放大的過程,而風控系統自己是一個長尾系統,它最多有核心繫統零點幾或者幾倍但不可能有超出核心系統N*M倍的預算,這意味着咱們對風控系統的性能要求也是很高的——咱們不能以核心系統一樣的代碼質量和技術設計來要求他,必須精心設計它,它才能知足大數據,高併發,高性能和低成本低預算的要求。

可是,對於大多數的公司來講,TPS就那麼幾個,數據就那麼幾條,即便放大N*M倍也不過幾百,隨便找臺機器作風控硬件基礎都綽綽有餘。即便代碼寫的稍微爛一點,加點機器也抗的住。可是要招一個精通風控,同時能優化性能幾倍幾十倍的技術架構,是比較難的,是不划算的,也是沒意義的。

同時,在這種狀況下,反而風控系統的可伸縮性要求很高。風控系統能不能適應這種只部署一臺或者幾臺的極簡化部署?

對於這99%的需求,現有市面上的絕大多數風控系統,都顯得的太臃腫了。他們吹噓了太多無用的概念,帶來了過高的運維,和擴展成本,而這些,對於這99%的用戶,基本毫無心義。

 

 

————————————————————————核心設計圖

裏面關鍵幾個設計部分是

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,腳本。

腳本能夠擴展出不少其餘類型,好比決策樹,積分卡等,如今比較火的機器學習用這個實現一個擴展就能夠了。

 

 

哦,漏放接口了 

如圖

相關文章
相關標籤/搜索