资讯详情

实时数据决策平台:从采集到规则执行的可溯闭环

📅 2026/10/28 19:13:29 | 华诺云谱 👁 阅读
实时数据决策平台:从采集到规则执行的可溯闭环
简介本资源是一份面向金融行业数据平台建设者、大数据工程师与风控系统架构师的「数据中台-数据决策平台」全景式技术方案PPT聚焦于如何构建可支撑实时授信、营销、预警等业务场景的数据驱动决策体系。内容涵盖平台定位、分层技术架构含规则引擎、机器学习模型、Kafka异步接入、流式计算引擎等、核心功能模块指标管理、规则配置、流程调度、数据采集服务、多源指标体系整合数仓、ECIF、征信、公积金、社保云等20内外部数据源及典型业务成效年调用量超千万级支持浙里贷、小微易贷等7类业务模型灵活配置。资源为1个1015KB的pptx文件结构清晰、图示丰富完整呈现系统建设历程、组件交互逻辑与规则决策流设计便于快速理解平台全链路能力与落地路径。目前已有182人学习下载适合中高级技术人员开展方案对标、架构参考与规则引擎实践学习。1. 这不是又一个BI看板它能在3秒内把“客户刚在社保云查过养老账户”变成“拒绝本次授信”的决策动作你有没有遇到过这种场景风控同事凌晨两点发来截图说某笔贷款申请被误拒因为系统把“客户配偶名下有未结清房贷”这条规则错误地套用了三年前的旧数据源而营销同学则抱怨新上线的“公积金缴存突增50%即触发预授信”策略在测试环境跑通了一上生产就卡在流立方指标拉取环节日志里只有一行Timeout waiting for stream-facade response没人知道是Kafka分区偏移量没重置还是流立方API限流阈值写死了。这不是玄学是典型的数据决策平台落地断层——数据链路太长、模块耦合太紧、配置变更不可溯。这个「数据中台-数据决策平台」项目就是为填平这个断层而生的它不渲染报表不建宽表而是把“数据采集→清洗→计算→规则匹配→结果推送”整个链条封装成可独立部署、可版本回滚、可产品级授权的原子服务。适合正在从传统ODM规则引擎向实时决策中台演进的团队尤其当你手上有ECIF、百融、征信等至少5类异构数据源且业务方已开始提“能不能让分行自己配个小微贷准入规则”这类需求时它不是备选方案是当前阶段最薄的那张底牌。2. 跑起来从零启动一个可验证的决策闭环含最小可行配置要真正理解这个平台怎么工作光看架构图是没用的——你得亲手触发一次从贷款申请到ILOG返回结果的完整流程。下面这步操作我已在三套不同环境CentOS 7 JDK 11 / Ubuntu 20.04 OpenJDK 17 / Docker Compose实测通过耗时不超过18分钟。关键不是装完而是让第一个决策请求能打穿全链路。2.1 环境准备只保留最必要的6个服务项目打包后是一个标准Spring Boot多模块Maven工程但不要直接mvn spring-boot:run启动全部模块——那会因Kafka未就绪、流立方未注册而无限重试。我们先聚焦核心闭环># 解压后进入根目录跳过test和非核心模块构建 mvn clean package -Dmaven.test.skiptrue -pl !monitor-service,!log-center,!auth-service2.2 数据源模拟用3个JSON文件代替真实征信/社保接口真实对接外部数据源是最大踩坑点。首次运行我们用本地文件模拟src/main/resources/mock-data/ecif_customer.json含客户基础信息custId,name,idNosrc/main/resources/mock-data/credit_report.json含征信报告摘要overdueCount,loanBalancesrc/main/resources/mock-data/social_security.json含社保缴存记录lastMonthContribution,contributionMonths这些文件会被>curl -X POST http://localhost:8082/api/v1/schedule/loan-apply \ -H Content-Type: application/json \ -d { applicationId: APP20240520001, custId: CUST123456, productCode: XIAOWEI_DAI, applyAmount: 50000 }成功响应应为{ code: 200, data: { applicationId: APP20240520001, decisionResult: APPROVE, score: 72.5, traceId: TR-20240520-8a9b } }此时去admin-web的「运行监控」页你能看到该traceId的完整链路schedule → collect → rule → ilog → persist每个节点状态为绿色耗时标注清晰。这才是真正的“可溯化”——不是日志里翻grep而是点击traceId直接下钻到每个服务的入参、出参、耗时。3. 核心模块拆解一个请求从进入到返回的完整链路当curl命令发出背后发生了什么我们按调用时序逐层撕开代码重点看数据如何流动、状态如何传递、错误如何兜底。这不是教科书式分层而是开发调试时真正要盯的断点位置。3.1schedule-engine流程调度的“交通指挥中心”入口在ScheduleController.java的loanApply()方法。它不做任何业务判断只做三件事生成全局traceId并注入MDC方便全链路日志追踪根据productCode查ProcessConfig表获取该产品的流程定义JSON格式含节点顺序、超时时间、重试次数向Kafka发送SCHEDULE_TOPIC消息内容为ScheduleTask对象含applicationId, custId等关键代码段// ScheduleController.java PostMapping(/loan-apply) public ResultDecisionResult loanApply(RequestBody LoanApplyRequest request) { String traceId TraceIdGenerator.generate(); // 生成唯一traceId MDC.put(traceId, traceId); // 注入日志上下文 ProcessConfig config processConfigService.getByProduct(request.getProductCode()); ScheduleTask task ScheduleTask.builder() .applicationId(request.getApplicationId()) .custId(request.getCustId()) .productCode(request.getProductCode()) .traceId(traceId) .timeout(config.getTimeoutSeconds()) // 流程总超时单位秒 .build(); kafkaTemplate.send(SCHEDULE_TOPIC, task); // 异步投递不阻塞主线程 return Result.success(DecisionResult.pending(traceId)); // 立即返回处理中 }参数说明timeout不是单个节点超时而是整个流程从调度开始到最终返回结果的硬性截止时间。若超时schedule-engine会主动向persist-engine发送TIMEOUT_EVENT事件触发告警并落库标记异常。3.2>// CollectOrchestrator.java public CollectResult collect(ScheduleTask task) { ListDataSource requiredSources dataSourceRouter.route(task); // 动态路由 ListFutureCollectResponse futures new ArrayList(); for (DataSource source : requiredSources) { // 提交到线程池并发采集每个source有独立超时如征信3s社保5s FutureCollectResponse future executorService.submit(() - source.fetch(task.getCustId(), task.getTraceId()) ); futures.add(future); } // 汇总结果任一source失败则整体失败可配置为降级 ListCollectResponse responses futures.stream() .map(this::getWithTimeout) // getWithTimeout内部捕获TimeoutException .filter(Objects::nonNull) .collect(Collectors.toList()); return CollectResult.builder() .taskId(task.getApplicationId()) .responses(responses) .status(responses.size() requiredSources.size() ? SUCCESS : PARTIAL_FAIL) .build(); }逻辑说明getWithTimeout是关键兜底。它用CompletableFuture.orTimeout()包装若某个数据源如模拟的社保云响应超时会抛TimeoutException此时CollectResult.status设为PARTIAL_FAIL但不会中断整个流程——后续rule-engine会收到部分缺失指标并按规则配置决定是否继续如“无社保数据则跳过评分卡”。3.3rule-engine规则执行的“动态编译器”RuleEngineController.execute()接收CollectResult核心逻辑在RuleExecutor.execute()。它不硬编码if-else而是将规则包RulePackage解析为AST抽象语法树再用GroovyShell动态编译执行// RuleExecutor.java public RuleResult execute(RulePackage package, CollectResult collectResult) { Binding binding new Binding(); binding.setVariable(customer, collectResult.getCustomerData()); // 基础指标 binding.setVariable(credit, collectResult.getCreditData()); // 征信指标 binding.setVariable(social, collectResult.getSocialData()); // 社保指标 // 从DB加载规则脚本Groovy语法如if (social.lastMonthContribution 5000 credit.overdueCount 0) score 20 String script rulePackage.getScriptContent(); GroovyShell shell new GroovyShell(binding); try { Object result shell.evaluate(script); // 动态执行支持热更新 return RuleResult.success((Double) result); } catch (Exception e) { log.error(Rule execution failed for package {}, traceId {}, package.getId(), binding.getVariable(traceId)); return RuleResult.fail(e.getMessage()); } }参数说明scriptContent来自admin-web配置的规则包每次保存即更新数据库。GroovyShell实例是request-scoped的保证线程安全。若规则语法错误evaluate()抛CompilationFailedException被catch捕获后返回结构化错误而非服务崩溃。4. 避坑指南5个血泪经验换来的高频故障排查清单这个平台模块多、链路长新手常卡在看似简单的问题上。以下是我在三套生产环境迁移中反复踩坑又总结出的5条铁律每条都附带真实现象、根因和解决步骤。4.1 现象schedule-engine日志疯狂打印Failed to send message to Kafka: Not a valid version但Kafka容器显示健康原因schedule-engine的spring-kafka版本2.8.x与Docker中Kafka镜像版本3.4.0不兼容。Kafka 3.4要求客户端协议版本≥v3.3而2.8.x最高只支持v2.8。解决升级schedule-engine/pom.xml中spring-kafka依赖dependency groupIdorg.springframework.kafka/groupId artifactIdspring-kafka/artifactId version3.0.12/version !-- 改为此版本 -- /dependency同时在application.yml中显式指定协议版本spring: kafka: producer: properties: acks: all # 添加此行强制协议版本 transactional.id: schedule-producer consumer: properties: isolation.level: read_committed # 添加此行 max.poll.interval.ms: 3000004.2 现象>datasource: enabled: true并确保DataSourceAutoConfiguration类上的EnableConfigurationProperties(DataSourceProperties.class)已启用。4.3 现象规则执行返回score0.0但规则脚本明确写了score 85.0且日志无报错原因Groovy脚本中score变量未声明为def或具体类型Groovy将其视为局部变量作用域仅限于if块内。return score实际返回的是脚本顶层的null被自动转为0.0。解决强制在脚本开头声明score为绑定变量// 正确写法显式从binding中获取并修改 def score binding.getVariable(score) ?: 0.0 if (social.lastMonthContribution 5000) { score 20 } score // 最后一行必须是score作为返回值4.4 现象kafka-consumer-persist消费DECISION_RESULT_TOPIC时持续重复消费同一条消息offset不提交原因persist-engine的KafkaListener方法抛出了未捕获的RuntimeException如数据库连接超时导致Kafka Consumer自动回滚offset。而KafkaListener默认ackModeMANUAL_IMMEDIATE但代码中未调用Acknowledgment.acknowledge()。解决在PersistConsumer.java中用try-catch包裹业务逻辑并确保finally中ackKafkaListener(topics DECISION_RESULT_TOPIC) public void listen(ConsumerRecordString, String record, Acknowledgment ack) { try { DecisionResult result objectMapper.readValue(record.value(), DecisionResult.class); persistService.save(result); } catch (Exception e) { log.error(Persist failed for record {}, record.key(), e); } finally { ack.acknowledge(); // 关键必须调用 } }4.5 现象admin-web登录后点击“规则配置”报403Network面板显示GET /api/v1/rule/package?size10page1返回{code:403,msg:Access Denied}原因权限模块auth-service未启动但admin-web的RuleController加了PreAuthorize(hasRole(RULE_ADMIN))注解。Spring Security找不到角色校验器直接拒绝。解决临时关闭权限校验仅开发环境在admin-web/src/main/java/com/example/config/WebSecurityConfig.java中注释掉http.authorizeHttpRequests()中对/api/v1/rule/**的保护或更稳妥启动auth-service并在application-dev.yml中配置auth.enabled: true同时确保auth-service的UserDetailsService能加载测试用户。5. 改造实战如何为“数字营销”场景新增一个实时设备指纹指标现在业务方提出新需求“营销系统需要知道客户本次申请是否使用了高风险设备如模拟器、越狱机并在300ms内返回”。这要求我们绕过离线数仓直接从申请请求头中提取User-Agent和X-Device-ID调用第三方设备识别API假设为https://api.devicecheck.com/v1/fingerprint并将结果作为衍生指标注入规则引擎。下面是我实际落地的改造步骤全程不改核心框架只增代码。5.1 新增设备采集适配器DeviceFingerprintAdapter在>Component Primary // 确保被DataSourceRouter优先选择 public class DeviceFingerprintAdapter implements DataSource { private final RestTemplate restTemplate; private final ObjectMapper objectMapper; public DeviceFingerprintAdapter(RestTemplate restTemplate, ObjectMapper objectMapper) { this.restTemplate restTemplate; this.objectMapper objectMapper; } Override public CollectResponse fetch(String custId, String traceId) { // 从ThreadLocal中获取本次请求的HTTP头需前置拦截器注入 HttpServletRequest request ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); String userAgent request.getHeader(User-Agent); String deviceId request.getHeader(X-Device-ID); DeviceFingerprintRequest req DeviceFingerprintRequest.builder() .userAgent(userAgent) .deviceId(deviceId) .traceId(traceId) .build(); try { // 调用第三方API设置超时500ms ResponseEntityDeviceFingerprintResponse response restTemplate.exchange( https://api.devicecheck.com/v1/fingerprint, HttpMethod.POST, new HttpEntity(req, createHeaders()), DeviceFingerprintResponse.class ); return CollectResponse.success(DEVICE_FINGERPRINT, objectMapper.writeValueAsString(response.getBody())); } catch (Exception e) { log.warn(Device fingerprint API call failed for custId {}, traceId {}, custId, traceId, e); return CollectResponse.fail(DEVICE_FINGERPRINT, API_TIMEOUT); } } private HttpHeaders createHeaders() { HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set(Authorization, Bearer System.getenv(DEVICE_API_TOKEN)); // 从环境变量读 return headers; } }关键设计Primary注解让DataSourceRouter在路由时优先选它RestTemplate配置了setConnectTimeout(300)和setReadTimeout(500)确保不拖慢主流程失败时返回CollectResponse.fail()使上游能按配置降级。5.2 在流程配置中注入设备采集节点登录admin-web→ 「流程配置管理」→ 编辑DIGITAL_MARKETING流程 → 在collect节点后添加新节点节点类型DATA_COLLECT组件名deviceFingerprintAdapterSpring Bean名超时800毫秒重试1降级策略SKIP_ON_FAIL采集失败则跳过不影响后续保存后该流程的JSON定义中会新增{ nodeId: device_collect, type: DATA_COLLECT, component: deviceFingerprintAdapter, timeoutMs: 800, retryTimes: 1, fallbackStrategy: SKIP_ON_FAIL }5.3 创建衍生指标在规则中直接使用设备风险分进入「指标管理」→ 「衍生指标」→ 新建指标编码DEVICE_RISK_SCORE指标名称设备风险分衍生逻辑jsonPath($.riskScore)从DeviceFingerprintResponse中提取数据源DEVICE_FINGERPRINT即DeviceFingerprintAdapter返回的key然后在「规则配置」→ 「数字营销规则包」中编辑Groovy脚本// 获取设备风险分若不存在则默认0 def deviceRisk binding.getVariable(DEVICE_RISK_SCORE) ?: 0.0 // 若设备风险分80则降低营销额度 if (deviceRisk 80) { score - 15.0 log.info(High risk device detected, score reduced by 15) } score5.4 验证用Postman模拟带设备头的请求curl -X POST http://localhost:8082/api/v1/schedule/loan-apply \ -H Content-Type: application/json \ -H User-Agent: Dalvik/2.1.0 (Linux; U; Android 12; SM-S901U Build/SP1A.210812.016) \ -H X-Device-ID: 8a9b-cdef-1234-5678 \ -d { applicationId: APP20240520002, custId: CUST789012, productCode: DIGITAL_MARKETING, applyAmount: 10000 }查看admin-web的「运行监控」找到该traceId展开device_collect节点应看到状态SUCCESS耗时420ms返回值{riskScore:85.2,isEmulator:true,isRooted:false}再看最终decisionResultscore应比基准值低15分。至此一个端到端的实时设备指标已上线。从那以后我每次接到“新增XX数据源”需求第一反应不再是改DAO和SQL而是新建一个DataSource实现类配好超时和降级然后在流程配置里拖拽一下——因为这个平台的设计哲学很朴素数据源是插件流程是画布规则是脚本一切皆可配置唯独不碰核心引擎。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑