基于LiteFlow的风控系统指标版本控制

个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview


更新日志

最近关于https://github.com/wnhyang/coolGuard此项目更新了如下内容:https://github.com/wnhyang/coolGuard/commits/main/
在这里插入图片描述

继前文GeoHash后项目又有了一些更新主要有:

1、【一般】增加了新的指标类型,历史取值。

2、【重要】更改表的关联关系,使用唯一索引而不是自增id。

这里很有必要展开一下,之前的所有表关系都是通过数据库自增id关联的,这有很大的弊端,尤其影响之后的计划(如:策略/规则/指标等导入导出,chain版本控制等),所以这个变化很有必要。

3、【一般】增加缓存删除,增加了一些场景的缓存删除,保障缓存-数据库一致性。

4、【一般】增加基础参数查询,主要包括一些常量枚举的查询,如指标类型、字段类型、逻辑操作等等。

5、【一般】完善注解校验。

6、【重要】增加指标版本控制。

如题,本篇文章将围绕指标版本控制详细展开,匆匆做完,就来分享,如有问题请指正!

版本控制

为什么要版本控制?

简单来说就几个重点:追踪变更、恢复与回滚、CICD、数据完整性。。。

常见的版本控制有哪些方法呢?

最常用的方法“主表+历史表”的设计,已经在《风控系统之事件溯源,决策流程记录与版本控制》中提到,在这次将其详细展开,通过实践检验真理。

主表+历史表的设计,就算没有了解过,听名字也大概知道怎么做了。

无非就是主表存储的是最新的、有效的数据记录。通常情况下,主表中会包含业务关键字段以及一些基本的元数据信息(如创建时间、最后修改时间等)。

历史表则用来保存所有历史版本的数据记录。每当主表中的数据发生变化时,旧的数据会被复制到历史表中,以便长期保存。

但是如果细心思考的话,其实还可以将其分为两种模式。

1、主表即运行

主表即当下运行,即running,历史表即historyversion

下图画的也不是很好(其中chain的变化没有体现出来),大概意思是:规则引擎运行的永远都是主表的数据,历史纯粹是历史,是用来回溯恢复的,历史表数据会和主表差一个版本。

这样设计完全没问题,可以满足基本的版本控制需要。

如果非要缺点可能就是主表一定要是确认的运行流程,只能存在终态。可以加上开关表示运行与停止,状态切换不记入历史表,其他任意的修改都需要生成新的版本(可以使用hash算法对比本次修改是否真的有变化)。
在这里插入图片描述

2、历史既是历史也是运行

主表表示编辑区,即edit,历史表才是真正的运行区,当然同时也是历史,即historyrunningversion

这样的设计会相对复杂一点,除了基本的新增、修改、删除,还会有发版、下线。本来也想尝试画图的,但发现好像达不到我要的效果,放弃了😂

但还是要补充说明一下,主表和历史表都需要一个状态标识,而且意义不同。主表的状态标识有两种p0p1p0标识编辑中,与运行状态不一致,发版后历史表创建新记录并运行;p1表示与运行状态一致不可发版,只能修改后再提交。历史表状态标识也有两种,0是历史,即真正历史表的作用,保留过去版本数据;1是当下运行状态。

新增、编辑、删除只是操作主表,下面直接引用项目代码了。

这里与上面最大的区别就是不用操作历史表。

@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = RedisKey.INDICATOR, allEntries = true)
public Long createIndicator(IndicatorCreateVO createVO) {
    // 1、指标名重复抛异常
    if (indicatorMapper.selectByName(createVO.getName()) != null) {
        throw exception(INDICATOR_NAME_EXIST);
    }
    // 2、变换并插入主表
    Indicator indicator = IndicatorConvert.INSTANCE.convert(createVO);
    indicator.setCode(IdUtil.fastSimpleUUID());
    indicator.setReturnType(IndicatorUtil.getReturnType(indicator.getType(), indicator.getCalcField()));
    indicator.setTimeSlice(WinSize.getWinSizeValue(indicator.getWinSize()));
    indicator.setCondStr(JsonUtils.toJsonString(createVO.getCond()));
    indicatorMapper.insert(indicator);
    return indicator.getId();
}

@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = RedisKey.INDICATOR, allEntries = true)
public void updateIndicator(IndicatorUpdateVO updateVO) {
    // 1、校验存在和name重复
    // TODO 校验存在和name
    // 2、hash确认是否真的有更改
    // TODO hash确认是否真的有更改
    Indicator indicator = IndicatorConvert.INSTANCE.convert(updateVO);
    indicator.setReturnType(IndicatorUtil.getReturnType(indicator.getType(), indicator.getCalcField()));
    indicator.setCondStr(JsonUtils.toJsonString(updateVO.getCond()));
    // 注意:状态只能变为fase即与运行区不一致,可以提交,不然没有做修改就是和运行区一致,不可提交
    indicator.setStatus(Boolean.FALSE);
    indicatorMapper.updateById(indicator);
}

@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = RedisKey.INDICATOR, allEntries = true)
public void deleteIndicator(Long id) {
    // 1、检查存在
    Indicator indicator = indicatorMapper.selectById(id);
    // 2、确认此指标是否在运行,运行不可删除,
    // 感觉有点没必要了
    IndicatorVersion indicatorVersion = indicatorVersionMapper.selectRunningByCode(indicator.getCode());
    if (indicatorVersion != null) {
        throw exception(INDICATOR_IS_RUNNING);
    }
    indicatorMapper.deleteById(id);
}

提交版本

提交版本会复杂一些,同时操作了指标、指标历史、chain表

@Override
@Transactional(rollbackFor = Exception.class)
public BatchVersionSubmitResultVO submit(VersionSubmitVO submitVO) {
    BatchVersionSubmitResultVO result = new BatchVersionSubmitResultVO().setId(submitVO.getId());
    // 提交后就同时在version表中增加一条记录,表示该指标在运行状态
    // 检查指标存在
    Indicator indicator = indicatorMapper.selectById(submitVO.getId());
    if (ObjectUtil.isNull(indicator)) {
        throw exception(INDICATOR_NOT_EXIST);
    }
    // 校验当前提交是不是和运行区一致,一致无需提交
    if (indicator.getStatus()) {
        throw exception(INDICATOR_VERSION_EXIST);
    }
    // 1、更新当前指标为已提交
    indicatorMapper.updateById(new Indicator().setId(submitVO.getId()).setStatus(Boolean.TRUE));
    // 2、查询是否有已运行的,有版本+1,没有版本1
    IndicatorVersion indicatorVersion = indicatorVersionMapper.selectRunningByCode(indicator.getCode());
    int version = 1;
    if (indicatorVersion != null) {
        version = indicatorVersion.getVersion() + 1;
        // 关闭已运行的
        indicatorVersionMapper.updateById(new IndicatorVersion().setId(indicatorVersion.getId()).setStatus(Boolean.FALSE));
    }
    // 3、插入新纪录并加入chain
    IndicatorVersion convert = IndicatorVersionConvert.INSTANCE.convert(indicator);
    convert.setVersion(version);
    convert.setVersionDesc(submitVO.getVersionDesc());
    convert.setStatus(Boolean.TRUE);
    indicatorVersionMapper.insert(convert);
    // 4、更新chain
    String iChain = StrUtil.format(LFUtil.INDICATOR_CHAIN, indicator.getCode());
    // 构造指标el
    String condEl = LFUtil.buildCondEl(convert.getCondStr());
    if (chainMapper.selectByChainName(iChain)) {
        Chain chain = chainMapper.getByChainName(iChain);
        chain.setElData(StrUtil.format(LFUtil.IF_EL, condEl,
                LFUtil.INDICATOR_TRUE_COMMON_NODE,
                LFUtil.INDICATOR_FALSE_COMMON_NODE));
        chainMapper.updateById(chain);
    } else {
        chainMapper.insert(new Chain().setChainName(iChain).setElData(StrUtil.format(LFUtil.IF_EL, condEl,
                LFUtil.INDICATOR_TRUE_COMMON_NODE,
                LFUtil.INDICATOR_FALSE_COMMON_NODE)));
    }
    result.setSuccess(Boolean.TRUE);
    return result;
}

下线

下线相对简单

@Override
@Transactional(rollbackFor = Exception.class)
public void offline(Long id) {
    IndicatorVersion indicatorVersion = indicatorVersionMapper.selectById(id);
    indicatorVersionMapper.updateById(new IndicatorVersion().setId(id).setStatus(Boolean.FALSE));
    chainMapper.deleteByChainName(StrUtil.format(LFUtil.INDICATOR_CHAIN, indicatorVersion.getCode()));
}

查编辑区和运行区就不用展示了。

LiteFlow流程变化

我前面提到的最近更新标注了【重要】的真的很关键,没错说的就是“更改表的关联关系,使用唯一索引而不是自增id”。没有这次改造也不会有这篇指标版本控制。

因为我的LiteFlow流程设计中,所有指标运行的核心EL是这样的FOR(i_fn).parallel(true).DO(i_cn);使用的是异步次数循环,对于我只需要更改i_fn组建循环的部分就可以完成从主表表示运行到历史表表示运行的切换。

如图,只是修改了查询当前运行指标的代码。
在这里插入图片描述

关于项目进度

我是如何测试的?

如果你看过项目代码,发现极少的单元测试,确实,测试这部分很不完善,仅有的就是通过apifox同步的接口,运行几个接口保存为测试集合,改完代码后通常要做这些事情:1、Reformat Code,必须要格式化一下,强迫症受不了;2、mvn clean install,因为使用mapstruct等其他插件,为了避免依赖的一些问题;3、如果有接口更新,同步一下apifox;4、运行apifox测试集合;5、针对修改的点mock数据发一下接口;6、如果有更改表结构,或是必要数据,导出sql到项目替换;7、确认没问题后,commit-push

git代码分支,为什么都在main上?

项目还未发版,而且没有协作者,自己随意了一些。

下一步?

乘胜追击,指标版本控制做了,下一步策略、规则版本控制。

至于发版,我感觉遥遥无期🤔

写在最后

拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。


个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/922420.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring:AOP切入点表达式

对于AOP中切入点表达式,我们总共会学习三个内容,分别是语法格式、通配符和书写技巧。 语法格式 首先我们先要明确两个概念: 切入点:要进行增强的方法切入点表达式:要进行增强的方法的描述方式 对于切入点的描述,我们其实是有两中方式的&a…

docker搭建私有的仓库

docker搭建私有仓库 一、为什么要搭建私有的仓库? 因为在国内,访问:https://hub.docker.com/ 会出现无法访问页面。。。。(已经使用了魔法) 当然现在也有一些国内的镜像管理网站,比如网易云镜像服务、Dao…

大白话讲Promise(最详细)

学前端的大家都知道promise是重中之重,也是面试的必考项。但是刚接触promise我一直很晕头晕脑的,搜集文章前看后看基本都是讲解promise的状态它的方法就没有再深入了,以至于面试时候面试官一旦往深问我就懵了。所以今天我们就详细的说一下pro…

【笔记】自动驾驶预测与决策规划_Part7_数据驱动的预测方法

文章目录 0. 前言1. 多模态传感器的编码方式1.1 栅格化表示1.2 向量化表示 Vectornet1.3 基于点云或者多模态输入的预测1.4 基于Transformer的方法 2. 网络输出的表达形式2.1 多模态轨迹回归2.2 轨迹分类2.3 轨迹回归轨迹分类2.4 目标点预测 3.场景级别的预测和决策3.1 论文&am…

回溯法经典难题解析

本文将通过几个经典的回溯问题,展示回溯算法的应用及其在解决问题时的核心思想和技巧。这些问题包括全排列、全排列II、N皇后以及数独问题,本文将分别介绍每个问题的思路与实现。 46. 全排列 给定一个不含重复数字的数组 nums ,返回其 所有…

无线图传下的低延迟视频传输播放技术探讨

技术背景 无线图传技术即无线图像传输技术,是指不用布线(线缆)利用无线电波来传输图像数据的技术。 一、工作原理 无线图传技术主要涉及图像采集、编码、调制、发射、接收、解调、解码和图像显示等环节。 图像采集:通过摄像头…

软件测试面试之常规问题

1.描述一下测试过程 类似题目:测试的生命周期 思路:这是一个“范围”很大的题目,而且回答时间一般在3分钟之内,不可能非常详细的描述整个过程,因此答题的思路要从整体结构入手,不要过细。为了保证答案的准确性,可以引…

C++从零到满绩——类和对象(中)

目录 1>>前言 2>>构造函数(我称之为初始化函数) 3>>析构函数(我称之为销毁函数) 4>>拷贝构造函数(我称之为复制函数) 5>>运算符重载 5.2>>赋值运算符重载 ​编辑…

内网渗透横向移动1

1.信息收集 (1)判断域控 shell net time /domain shell ping OWA2010CN-God.god.org (2)主机探测 浏览探测->网络探测 主机列表显示: (3)域用户收集: shell net user /domain…

Edify 3D: Scalable High-Quality 3D Asset Generation 论文解读

目录 一、概述 二、相关工作 1、三维资产生成 2、多视图下的三维重建 3、纹理和材质生成 三、Edify 3D 1、文本生成多视角图像的扩散模型 2、文本和多视角图像生成法线图像的ControlNet 3、重建与渲染模型 4、多视角高分辨率RGB图像生成 四、训练 1、训练过程 2、…

微软正在测试 Windows 11 对第三方密钥的支持

微软目前正在测试 WebAuthn API 更新,该更新增加了对使用第三方密钥提供商进行 Windows 11 无密码身份验证的支持。 密钥使用生物特征认证,例如指纹和面部识别,提供比传统密码更安全、更方便的替代方案,从而显著降低数据泄露风险…

词云图大师(WordCloudMaster): 探索创意无限的词云世界!

在信息化时代,如何以一种新颖且富有创意的方式表达数据、文字或想法?答案是词云图!而词云图大师(WordCloudMaster),正是您的绝佳选择。 无论是个人创意项目,还是专业工作中的数据可视化,词云图大师都能以强…

pycharm使用debug的时候遇到断点不停的问题

1.首先尝试在程序最开头打断点,检查是否能停下,如果可以,看第二步 2.尝试在你打期望停下的代码附近print("1111111")看看是否输出了这个字符串,验证程序确实走到这一步了 3.如果能走到那一步,但是依然没有…

Epipolar-Free 3D Gaussian Splatting for Generalizable Novel View Synthesis 论文解读

目录 一、概述 二、相关工作 1、单场景3DGS 2、跨场景生成3DGS 3、几何方法解决3D任务 三、eFreeSplat 1、预训练跨视角模块 2、无外极线跨视角交互感知模块 3、迭代跨视角高斯对齐 4、高斯参数预测 一、概述 该论文设计了一种不依赖于极线约束的情况实现可推广的新视…

c++视频图像处理

打开视频或摄像头 打开指定视频 /*VideoCapture(const String &filename, apiPreference);filename:读取的视频或者图像序列的名称apiPreference:读取数据时设置的属性*/ VideoCapture video; //定义一个空的视频对象 video.open("H:/BaiduNetdiskDownlo…

青少年强网杯线上ctf-crypto-wp

目录 AliceAES Classics AliceAES 进入环境,给一个key值和一个iv值 意思是,用这两个值AES编码‘Hello,Bob!’,然后把结果输入进去 把key值和iv值带入解得 然后得出flag Classics 题目是下面这个 根据他解码的顺序,反着写出编码顺序 一开…

工具使用_docker容器_crossbuild

1. 工具简介 2. 工具使用 拉取 multiarch/crossbuild 镜像&#xff1a; docker pull multiarch/crossbuild 创建工作目录和示例代码&#xff1a; mkdir -p ~/crossbuild-test cd ~/crossbuild-test 创建 helloworld.c &#xff1a; #include <stdio.h>int main() …

【Linux系统】—— 基本指令(三)

【Linux系统】—— 基本指令&#xff08;三&#xff09; 1 一切皆文件2 重定向操作2.1 初始重定向2.2 重定向的妙用2.3 追加重定向2.4 输入重定向2.5 一切皆文件与重定向结合 3 Linux 中的文件类型4 日志5 「more」命令6 「less」命令7 「head」与「tail」7.1 查看文件开头和结…

搜索引擎中广泛使用的文档排序算法——BM25(Best Matching 25)

在搜索场景中&#xff0c;BM25能计算每个文档与查询的匹配度&#xff0c;从中找出最相关的文档&#xff0c;并按相关性高低排序展示。 要理解BM25&#xff0c;需要掌握以下几个关键概念&#xff1a; 1. 词频&#xff08;Term Frequency, TF&#xff09;&#xff1a;某关键词在文…

Jupyter Notebook的安装和配置提示功能

Python开发环境搭建conda管理环境-CSDN博客 安装anaconda和对接到编译器的教程可以看上面这一篇 Jupyter Notebook是一种交互式计算环境&#xff0c;它允许用户在单个文档中编写和执行代码、方程、可视化和文本。与其他编译器相比&#xff0c;Jupyter Notebook的突出点在于其交…