背景介紹
風(fēng)控簡介
二十一世紀(jì)走孽,信息化時代到來钝的,互聯(lián)網(wǎng)行業(yè)的發(fā)展速度遠快于其他行業(yè)翁垂。一旦商業(yè)模式跑通,有利可圖扁藕,資本立刻蜂擁而至,助推更多企業(yè)不斷的入場進行快速的復(fù)制迭代疚脐,企圖成為下一個“行業(yè)領(lǐng)頭羊”亿柑。
帶著資本入場的玩家因為不會有資金的壓力,只會更多的關(guān)注業(yè)務(wù)發(fā)展棍弄,卻忽略了業(yè)務(wù)上的風(fēng)險點望薄。強大如拼多多也被“薅羊毛”大軍光顧損失千萬。
風(fēng)控呼畸,即風(fēng)險管理(risk management)痕支,是一個管理過程,包括對風(fēng)險的定義蛮原、測量卧须、評估和應(yīng)對風(fēng)險的策略。目的是將可避免的風(fēng)險儒陨、成本及損失極小化[1]花嘶。
特征平臺簡介
互聯(lián)網(wǎng)企業(yè)每時每刻都面臨著黑灰產(chǎn)的各種攻擊。業(yè)務(wù)安全團隊需要事先評估業(yè)務(wù)流程中有風(fēng)險的地方蹦漠,再設(shè)置卡點椭员,用來采集相關(guān)業(yè)務(wù)信息,識別當(dāng)前請求是否有風(fēng)險笛园。專家經(jīng)驗(防控策略)就是在長期以往的對抗中產(chǎn)生的隘击。
策略的部署需要一個個特征來支持侍芝,那什么是特征?
特征分為基礎(chǔ)型特征埋同、衍生型特征州叠、統(tǒng)計型特征等,舉例如下:
- 基礎(chǔ)型特征:可以直接從業(yè)務(wù)獲取的莺禁,如訂單的金額留量、買家的手機號碼、買家地址哟冬、賣家地址等
- 衍生特征:需要二次計算楼熄,如買家到買家的距離、手機號前3位等
- 統(tǒng)計型特征:需要實時統(tǒng)計的浩峡,如5分鐘內(nèi)某手機號下購買訂單數(shù)可岂、10分鐘內(nèi)購買金額大于2w元訂單數(shù)等
隨著業(yè)務(wù)的迅猛發(fā)展,單純的專家經(jīng)驗已不能滿足風(fēng)險識別需求翰灾,算法團隊的加入使得攔截效果變得更加精準(zhǔn)缕粹。算法部門人員通過統(tǒng)一算法工程框架,解決了模型和特征迭代的系統(tǒng)性問題纸淮,極大地提升了迭代效率平斩。
根據(jù)功能不同,算法平臺可劃分為三部分:模型服務(wù)咽块、模型訓(xùn)練和特征平臺绘面。其中,模型服務(wù)用于提供在線模型預(yù)估侈沪,模型訓(xùn)練用于提供模型的訓(xùn)練產(chǎn)出揭璃,特征平臺則提供特征和樣本的數(shù)據(jù)支撐。本文將重點闡述特征平臺在建設(shè)過程中實時計算遇到的挑戰(zhàn)以及優(yōu)化思路亭罪。
挑戰(zhàn)與方案
面臨的挑戰(zhàn)
業(yè)務(wù)發(fā)展的初期瘦馍,我們可以通過硬編碼的方式滿足策略人員提出的特征需求,協(xié)同也比較好应役。但隨著業(yè)務(wù)發(fā)展越來越快情组,業(yè)務(wù)線越來越多,營銷玩法越來越復(fù)雜箩祥,用戶數(shù)和請求量成幾何倍上升呻惕。適用于早期的硬編碼方式出現(xiàn)了策略分散無法管理、邏輯同業(yè)務(wù)強耦合滥比、策略更新迭代率受限于開發(fā)亚脆、對接成本高等多種問題。此時盲泛,我們急需一套線上可配置濒持、可熱更新键耕、可快速試錯的特征管理平臺。
舊框架的不足
實時框架1.0:基于 Flink DataStream API構(gòu)建
如果你熟悉 Flink DataStream API柑营,那你肯定會發(fā)現(xiàn) Flink 的設(shè)計天然滿足風(fēng)控實時特征計算場景屈雄,我們只需要簡單的幾步即可統(tǒng)計指標(biāo),如下圖所示:
Flink DataStream 流圖
實時特征統(tǒng)計樣例代碼如下:
// 數(shù)據(jù)流官套,如topic
DataStream<ObjectNode> dataStream = ...
SingleOutputStreamOperator<AllDecisionAnalyze> windowOperator = dataStream
// 過濾
.filter(this::filterStrategy)
// 數(shù)據(jù)轉(zhuǎn)換
.flatMap(this::convertData)
// 配置watermark
.assignTimestampsAndWatermarks(timestampAndWatermarkAssigner(config))
// 分組
.keyBy(this::keyByStrategy)
// 5分鐘滾動窗口
.window(TumblingEventTimeWindows.of(Time.seconds(300)))
// 自定義聚合函數(shù)酒奶,內(nèi)部邏輯自定義
.aggregate(AllDecisionAnalyzeCountAgg.create(), AllDecisionAnalyzeWindowFunction.create());
1.0框架不足:
- 特征強依賴開發(fā)人員編碼,簡單的統(tǒng)計特征可以抽象奶赔,稍微復(fù)雜點就需要定制
- 迭代效率低惋嚎,策略提需求、產(chǎn)品排期站刑、研發(fā)介入另伍、測試保障、一套流程走完交付最少也是兩周
- 特征強耦合绞旅,任務(wù)拆分難摆尝,一個 JOB 包含太多邏輯,可能新上的特征邏輯會影響之前穩(wěn)定的指標(biāo)
總的來說因悲,1.0在業(yè)務(wù)初期很適合堕汞,但隨著業(yè)務(wù)發(fā)展,研發(fā)速度逐漸成為瓶頸晃琳,不符合可持續(xù)讯检、可管理的實時特征清洗架構(gòu)。
實時框架2.0:基于 Flink SQL 構(gòu)建
1.0架構(gòu)的弊端在于需求到研發(fā)采用不同的語言體系蝎土,如何高效的轉(zhuǎn)化需求视哑,甚至是直接讓策略人員配置特征清洗邏輯直接上線绣否?如果按照兩周一迭代的速度誊涯,可能線上早被黑灰產(chǎn)薅的“面目全非”了。
此時我們研發(fā)團隊注意到 Flink SQL蒜撮,SQL 是最通用的數(shù)據(jù)分析語言暴构,數(shù)分、策略段磨、運營基本必備技能取逾,可以說 SQL 是轉(zhuǎn)換需求代價最小的實現(xiàn)方式之一。
看一個 Flink SQL 實現(xiàn)示例:
-- error 日志監(jiān)控
-- kafka source
CREATE TABLE rcp_server_log (
thread varchar,
level varchar,
loggerName varchar,
message varchar,
endOfBatch varchar,
loggerFqcn varchar,
instant varchar,
threadId varchar,
threadPriority varchar,
appName varchar,
triggerTime as LOCALTIMESTAMP,
proctime as PROCTIME(),
WATERMARK FOR triggerTime AS triggerTime - INTERVAL '5' SECOND
) WITH (
'connector.type' = 'kafka',
'connector.version' = '0.11',
'connector.topic' = '${sinkTopic}',
'connector.startup-mode' = 'latest-offset',
'connector.properties.group.id' = 'streaming-metric',
'connector.properties.bootstrap.servers' = '${sinkBootstrapServers}',
'connector.properties.zookeeper.connect' = '${sinkZookeeperConnect}}',
'update-mode' = 'append',
'format.type' = 'json'
);
-- 此處省略 sink_feature_indicator 創(chuàng)建苹支,參考 source table
-- 按天 按城市 各業(yè)務(wù)線決策分布
INSERT INTO sink_feature_indicator
SELECT
level,
loggerName,
COUNT(*)
FROM rcp_server_log
WHERE
(level <> 'INFO' AND `appName` <> 'AppTestService')
OR loggerName <> 'com.test'
GROUP BY
TUMBLE(triggerTime, INTERVAL '5' SECOND),
level,
loggerName;
我們在開發(fā) Flink SQL 支持平臺過程中砾隅,遇到如下問題:
- 一個 SQL 如果清洗一個指標(biāo),那么數(shù)據(jù)源將極大浪費
- SQL merge债蜜,即一個檢測如果同源 SQL 則進行合并晴埂,此時將極大增加作業(yè)復(fù)雜度究反,且無法定義邊界
- SQL 上線需要停機重啟,此時如果任務(wù)中包含大量穩(wěn)定指標(biāo)儒洛,會不會是臨界點
技術(shù)實現(xiàn)
痛點總結(jié)
業(yè)務(wù)&研發(fā)痛點圖
實時計算架構(gòu)
策略/算法人員每天需要觀測實時和離線數(shù)據(jù)分析線上是否存在風(fēng)險精耐,針對有風(fēng)險的場景,會設(shè)計防控策略琅锻,透傳到研發(fā)側(cè)其實就是一個個實時特征的開發(fā)卦停。所以實時特征的上線速度、質(zhì)量交付恼蓬、易用性完全決定了線上風(fēng)險場景能否及時堵漏的關(guān)鍵惊完。
在統(tǒng)一實時特征計算平臺構(gòu)建之前,實時特征的產(chǎn)出上主要有以下問題:
- 交付速度慢滚秩,迭代開發(fā):策略提出到產(chǎn)品专执,再到研發(fā),提測郁油,在上線觀測是否穩(wěn)定本股,速度奇慢
- 強耦合,牽一發(fā)動全身:怪獸任務(wù)桐腌,包含很多業(yè)務(wù)特征拄显,各業(yè)務(wù)混在一起,沒有優(yōu)先級保證
- 重復(fù)性開發(fā):由于沒有統(tǒng)一的實時特征管理平臺案站,很多特征其實已經(jīng)存在躬审,只是名字不一樣,造成極大浪費
平臺話建設(shè)蟆盐,最重要的是“整個流程的抽象”承边,平臺話的目標(biāo)應(yīng)該是能用、易用石挂、好用博助。基于如上思想痹愚,我們嘗提取實時特征研發(fā)痛點:模板化 + 配置化富岳,即平臺提供一個實時特征的創(chuàng)建模板,用戶基于該模板拯腮,可以通過簡單的配置即可生成自己需要的實時特征窖式。
Flink 實時計算架構(gòu)圖
計算層
數(shù)據(jù)源清洗:不同數(shù)據(jù)源抽象 Flink Connector,標(biāo)準(zhǔn)輸出供下游使用
數(shù)據(jù)拆分:1拆N动壤,一條實時消息可能包含多種消息萝喘,此時需要數(shù)據(jù)裂變
動態(tài)配置:允許在不停機 JOB 情況下,動態(tài)更新或新增清洗邏輯,涉及特征的清洗邏輯下發(fā)
腳本加載:Groovy 支持阁簸,熱更新
**RTC: **即 Real-Time Calculate弦蹂,實時特征計算,高度抽象的封裝模塊
任務(wù)感知:基于特征業(yè)務(wù)域强窖、優(yōu)先級凸椿、穩(wěn)定性,隔離任務(wù)翅溺,業(yè)務(wù)解耦
服務(wù)層
**統(tǒng)一查詢SDK: **實時特征統(tǒng)一查詢SDK脑漫,屏蔽底層實現(xiàn)邏輯
基于統(tǒng)一的 Flink 實時計算架構(gòu),我們重新設(shè)計了實時特征清洗架構(gòu)
Flink 實時計算數(shù)據(jù)流圖
特征配置化 & 存儲/讀取
特征底層的存儲應(yīng)該是“原子性”的咙崎,即最小不可分割單位优幸。為何如此設(shè)計?實時統(tǒng)計特征是和窗口大小掛鉤的褪猛,不同策略人員防控對特征窗口大小有不同的要求网杆,舉例如下:
- 可信設(shè)備判定場景:其中當(dāng)前手機號登錄時長窗口應(yīng)適中,不宜過短伊滋,防擾動
- 提現(xiàn)欺詐判定場景:其中當(dāng)前手機號登錄時長窗口應(yīng)盡量短碳却,短途快速提現(xiàn)的,結(jié)合其它維度笑旺,快速定位風(fēng)險
基于上述昼浦,急需一套通用的實時特征讀取模塊,滿足策略人員任意窗口需求筒主,同時滿足研發(fā)人員快速的配置清洗需求关噪。我們重構(gòu)后特征配置模塊如下:
特征配置抽象模塊
實時特征模塊:
- 特征唯一標(biāo)識
- 特征名稱
- 是否支持窗口:滑動、滾動乌妙、固定大小窗口
- 事件切片單位:分鐘使兔、小時、天藤韵、周
- 主屬性:即分組列虐沥,可以多個
- 從屬性:聚合函數(shù)使用,如去重所需輸入基礎(chǔ)特征
業(yè)務(wù)留給風(fēng)控的時間不多荠察,大多數(shù)場景在 100 ms 以內(nèi)置蜀,實時特征獲取就更短了奈搜,從以往的研發(fā)經(jīng)驗看悉盆,RT 需要控制在 10 ms 以內(nèi),以確保策略執(zhí)行不會超時馋吗。所以我們的存儲使用 Redis焕盟,確保性能不是瓶頸。
清洗腳本熱部署
如上述,實時特征計算模塊強依賴于上游消息內(nèi)傳遞的“主屬性” 和 “從屬性”脚翘,此階段也是研發(fā)需要介入的地方灼卢,如果消息內(nèi)主屬性字段不存在,則需要研發(fā)補全来农,此時不得不加入代碼的發(fā)版鞋真,那又會回到原始階段面臨的問題:Flink Job 需要不停的重啟,這顯然是不能接受的沃于。
此時我們想到了 Groovy涩咖,能否讓 Flink + Groovy,直接熱部署代碼繁莹?答案是肯定的檩互!
由于我們抽象了整個 Flink Job 的計算流圖,算子本身是不需要變更的咨演,即 DAG 是固定不變的闸昨,變得是算子內(nèi)部關(guān)聯(lián)事件的清洗邏輯。所以薄风,只要關(guān)聯(lián)清洗邏輯和清洗代碼本身變更饵较,即不需要重啟 Flink Job 完成熱部署。
Groovy 熱部署核心邏輯如圖所示:
清洗腳本配置與加載圖
研發(fā)或策略人員在管理后臺(Operating System)添加清洗腳本遭赂,并存入數(shù)據(jù)庫告抄。Flink Job 腳本緩存模塊此時會感知腳本的新增或修改(如何感知看下文整體流程詳解)
- warm up:腳本首次運行較耗時,首次啟動或者緩存更新時提前預(yù)熱執(zhí)行嵌牺,保證真實流量進入腳本快速執(zhí)行
- cache:緩存已經(jīng)在好的 Groovy 腳本
- Push/Poll:緩存更新采用推拉兩種模式打洼,確保信息不回丟失
- router:腳本路由,確保消息能尋找到對應(yīng)腳本并執(zhí)行
腳本加載核心代碼:
// 緩存逆粹,否則無限加載下去會 metaspace outOfMemory
private final static Map<String, GroovyObject> groovyObjectCache = new ConcurrentHashMap<>();
/**
* 加載腳本
* @param script
* @return
*/
public static GroovyObject buildScript(String script) {
if (StringUtils.isEmpty(script)) {
throw new RuntimeException("script is empty");
}
String cacheKey = DigestUtils.md5DigestAsHex(script.getBytes());
if (groovyObjectCache.containsKey(cacheKey)) {
log.debug("groovyObjectCache hit");
return groovyObjectCache.get(cacheKey);
}
GroovyClassLoader classLoader = new GroovyClassLoader();
try {
Class<?> groovyClass = classLoader.parseClass(script);
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
classLoader.clearCache();
groovyObjectCache.put(cacheKey, groovyObject);
log.info("groovy buildScript success: {}", groovyObject);
return groovyObject;
} catch (Exception e) {
throw new RuntimeException("buildScript error", e);
} finally {
try {
classLoader.close();
} catch (IOException e) {
log.error("close GroovyClassLoader error", e);
}
}
}
標(biāo)準(zhǔn)消息 & 清洗流程
策略需要統(tǒng)計的消息維度很雜募疮,涉及多個業(yè)務(wù),研發(fā)本身也有監(jiān)控用到的實時特征需求僻弹。所以實時特征對應(yīng)的數(shù)據(jù)源是多種多樣的阿浓。所幸 Flink 是支持多種數(shù)據(jù)源接入的,對于一些特定的數(shù)據(jù)源蹋绽,我們只需要繼承實現(xiàn) Flink Connector 即可滿足需求芭毙,我將拿 Kafka舉例,整體流程是如何清洗實時統(tǒng)計特征的卸耘。
首先介紹風(fēng)控整體數(shù)據(jù)流退敦,多個業(yè)務(wù)場景對接風(fēng)控中臺,風(fēng)控內(nèi)部核心鏈路是:決策引擎蚣抗、規(guī)則引擎侈百、特征服務(wù)。
一次業(yè)務(wù)請求決策,我們會異步記錄下來钝域,并發(fā)送Kafka消息讽坏,用于實時特征計算 & 離線埋點。
風(fēng)控核心數(shù)據(jù)流圖
標(biāo)準(zhǔn)化消息模板
Flink 實時計算 Job 在接收到 MQ 消息后例证,首先是消息模板標(biāo)準(zhǔn)化解析路呜,不同的 Topic 對應(yīng)消息格式不一致,JSON织咧、CSV拣宰、異構(gòu)(如錯誤日志類消息,空格隔斷烦感,對象內(nèi)包含 JSON 對象)等巡社。
為方便下游算子統(tǒng)一處理,標(biāo)準(zhǔn)化后消息結(jié)構(gòu)如下 JSON 結(jié)構(gòu):
public class RcpPreProcessData {
/**
* 渠道手趣,可以直接寫topic即可
*/
private String channel;
/**
* 消息分類 channel + eventCode 應(yīng)唯一確定一類消息
*/
private String eventCode;
/**
* 所有主從屬性
*/
private Map<String, Object> featureParamMap;
/**
* 原始消息
*/
private ObjectNode node;
}
消息裂變
一條“富消息”可能包含大量的業(yè)務(wù)信息晌该,某些實時特征可能需要分別統(tǒng)計。舉例绿渣,一條業(yè)務(wù)請求風(fēng)控的上下文消息朝群,包含本次消息是否拒絕,即命中了多少策略規(guī)則中符,命中的規(guī)則是數(shù)組姜胖,可能包含多條命中規(guī)則。此時如果想基于一條命中的規(guī)則去關(guān)聯(lián)其它屬性統(tǒng)計淀散,就需要用到消息的裂變右莱,由1變N。
消息裂變的邏輯由運營后臺通過 Groovy 腳本編寫档插,定位清洗腳本邏輯則是 channel(父) + eventCode(子)慢蜓,此處尋找邏輯分“父子”,“父”邏輯對當(dāng)前 channel 下所有邏輯適用郭膛,避免單獨配置 N 個 eventCode 的繁瑣晨抡,“子”邏輯則對特定的eventCode適用。
消息清洗 & 剪枝
消息的清洗就是我們需要知道特征需要哪些主從屬性则剃,帶著目的清洗更清晰耘柱,定位清洗的腳本同上,依然依據(jù) channel + eventCode 實現(xiàn)棍现。清洗出的主從屬性存在 featureParamMap 中调煎,供下游實時計算使用。
此處需要注意的是轴咱,我們一直是帶著原始消息向下傳遞的汛蝙,但如果已經(jīng)確認(rèn)了清洗的主從屬性,那么原始消息就沒有存在的必要了朴肺,此時我們需要“剪枝”窖剑,節(jié)省 RPC 調(diào)用過程 I/O 流量的消耗。
至此戈稿,一條原始消息已經(jīng)加工成只包含 channel(渠道)西土、eventCode(事件類型)、featureParamMap(所有主從屬性)鞍盗,下游算子只需要且僅需要這些信息即可計算需了。
實時計算
依然同上面兩個算子,實時計算算子依賴 channel + eventCode 查找到對應(yīng)實時特征元數(shù)據(jù)般甲,一個事件可能存在多個實時特征配置肋乍,運營平臺填寫好實時特征配置后,依據(jù)緩存更新機制敷存,快速分發(fā)到任務(wù)中墓造,依據(jù) Key構(gòu)造器 生成對應(yīng)的 Key,傳遞下游直接 Sink 到 Redis中锚烦。
任務(wù)問題排查&調(diào)優(yōu)思路
任務(wù)的排查是基于完善的監(jiān)控上實現(xiàn)的觅闽,F(xiàn)link 提供了很多有用的 Metric 供我們排查問題涮俄,如下是我羅列的常見的任務(wù)異常,希望對你有所幫助彻亲。
TaskManager Full GC 問題排查
出現(xiàn)上面這個異常的可能原因是因為:
- 大窗口:90% TM 內(nèi)存爆表,都是大窗口導(dǎo)致的
- 內(nèi)存泄漏:如果是自定義節(jié)點苞尝,且涉及到緩存等很容易導(dǎo)致內(nèi)存膨脹
解決辦法:
- 合理制定窗口導(dǎo)線硫惕,合理分配 TM 內(nèi)存(1.10默認(rèn)是1G)野来,聚合數(shù)據(jù)應(yīng)交由 Back State 管理,不建議自己寫對象存儲
- 可 attach heap 快照排查異常曼氛,分析工具如 MAT豁辉,需要一定的調(diào)優(yōu)經(jīng)驗,也能快速定位問題
Flink Job 反壓
出現(xiàn)上面這個異常的可能原因是因為:
- 數(shù)據(jù)傾斜:90%的反壓舀患,一定是數(shù)據(jù)傾斜導(dǎo)致的
- 并行度并未設(shè)置好,錯誤估計數(shù)據(jù)流量或單個算子計算性能
解決辦法:
- 數(shù)據(jù)清洗參考下文
- 對于并行度餐抢,可以在消息傳遞過程中埋點,看各個節(jié)點cost
數(shù)據(jù)傾斜
核心思路:
- key 加隨機數(shù)碳锈,然后執(zhí)行 keyby 時會根據(jù)新 key 進行分區(qū)欺抗,此時會打散 key 的分布,不會造成數(shù)據(jù)傾斜問題
- 二次 keyby 進行結(jié)果統(tǒng)計
打散邏輯核心代碼:
public class KeyByRouter {
private final static String SPLIT_CHAR = "#";
/**
* 不能太散贸人,否則二次聚合還是會有數(shù)據(jù)傾斜
*
* @param sourceKey
* @return
*/
public static String randomKey(String sourceKey) {
int endExclusive = (int) Math.pow(2, 7);
return sourceKey + SPLIT_CHAR + (RandomUtils.nextInt(0, endExclusive) + 1);
}
public static String restoreKey(String randomKey) {
if (StringUtils.isEmpty(randomKey)) {
return null;
}
return randomKey.split(SPLIT_CHAR)[0];
}
}
作業(yè)暫停并保留狀態(tài)失敗
出現(xiàn)上面這個異常的可能原因是因為:
- 作業(yè)本身處于反壓的情況艺智,做 Checkpoint 可能失敗了圾亏,所以暫停保留狀態(tài)的時候做 Savepoint 肯定也會失敗
- 作業(yè)的狀態(tài)很大,做 Savepoint 超時了
- 作業(yè)設(shè)置的 Checkpoint 超時時間較短父晶,導(dǎo)致 SavePoint 還沒有做完弄跌,作業(yè)就丟棄了這次 Savepoint 的狀態(tài)
解決辦法:
- 代碼設(shè)置 Checkpoint 的超時時間盡量的長一些铛只,比如 10min,對于狀態(tài)很大的作業(yè)直撤,可以設(shè)置更大
- 如果作業(yè)不需要保留狀態(tài)蜕着,那么直接暫停作業(yè),然后重啟就行
總結(jié)與展望
這篇文章分別從實時特征清洗框架演進蓖乘,特征可配置韧骗,特征清洗邏輯熱部署等方面介紹了目前較穩(wěn)定的實時計算可行架構(gòu)。經(jīng)過近兩年的迭代些侍,目前這套架構(gòu)在穩(wěn)定性、資源利用率蚂会、性能開銷上有最優(yōu)的表現(xiàn)狈定,給業(yè)務(wù)策略人員及業(yè)務(wù)算法人員提供了有力的支撐习蓬。
未來躲叼,我們期望特征的配置還是回歸 SQL 化,雖然目前配置已經(jīng)足夠簡單让蕾,但是畢竟屬于我們自己打造的“領(lǐng)域設(shè)計語言”或听,對新來的的策略人員 & 產(chǎn)品人員有一定的學(xué)習(xí)成本,我們期望的是能夠通過像 SQL 這種全域通過用語言來配置化顿颅,類似 Hive 離線查詢一樣粱腻,屏蔽了底層復(fù)雜的計算邏輯斩跌,助力業(yè)務(wù)更好的發(fā)展。
參考文獻:
[1] 風(fēng)險管控(https://zh.wikipedia.org/wiki/%E9%A3%8E%E9%99%A9%E7%AE%A1%E7%90%86)