风控系统指标计算/特征提取分析与实现01,Redis、Zset、模版方法

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

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview


引用AI对于风控系统的介绍

风控系统是一种用于在线业务的安全管理系统,它帮助企业和平台防范潜在的欺诈、信用风险以及不合规行为。简单来说,它的核心作用就是“保安全、防欺诈、控风险”。

最近也一直在研究风控系统体系、功能等,看了一些有关的文章,并且也在实践尝试中。

其实前一篇可配置“输入参数的接口如何设计”就是实践尝试的一部分,未来还会有更多的。

而本篇文章就风控系统的指标计算,或者说是特征提取做一些探讨,以下统一称呼为“指标”。

指标不仅可以作为风控系统的一部分配合风控规则或是模型/机器学习使用,而且可以用于离线分析、事后追查、用户画像标签等方面。

参考文章

风控笔记06:一个完整的风控引擎,需要有哪些功能?

风控笔记07:最常用的风控工具-特征库

指标分类

指标是由数据流支撑的,指标是时间纬度的数据提取计算。

根据指标分类举几个例子:

  • 次数统计:最近24小时 客户号向 {客户号}向 客户号{银行卡卡号}转账笔数
  • 求和:最近2天 客户号向 {客户号}向 客户号{银行卡卡号}转账金额之和
  • 平均:最近1个月 客户号向 {客户号}向 客户号{银行卡卡号}转账金额的平均数
  • 关联次数:最近72小时 客户号关联 {客户号}关联 客户号关联{设备mac地址}的次数
  • 等等

指标类型枚举

/*** @author wnhyang* @date 2024/3/13**/
@AllArgsConstructor
@Getter
public enum IndicatorType {COUNT(0, "count", "次数统计"),SUM(1, "sum", "求和"),AVG(2, "avg", "平均"),MAX(3, "max", "最大值"),MIN(4, "min", "最小值"),ASS(5, "ass", "关联次数");private final Integer code;private final String name;private final String desc;
}

指标实体类

目前filterScript条件还未确实如何做,使用Groovy脚本还是什么,之后再定吧。

/*** @author wnhyang* @date 2024/3/13**/
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("de_indicator")
public class IndicatorPO extends BasePO {@Serialprivate static final long serialVersionUID = 1L;/*** 自增编号*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 指标名*/@TableField("name")private String name;/*** 状态*/@TableField("status")private Boolean status;/*** 类型*/@TableField("type")private Integer type;/*** 计算字段*/@TableField("calc_field")private String calcField;/*** 窗口大小*/@TableField("win_size")private String winSize;/*** 窗口类型*/@TableField("win_type")private String winType;/*** 窗口数量*/@TableField("win_count")private Integer winCount;/*** 时间片*/@TableField("time_slice")private Long timeSlice;/*** 主字段*/@TableField("master_field")private String masterField;/*** 从字段*/@TableField("slave_fields")private String slaveFields;/*** 过滤脚本*/@TableField("filter_script")private String filterScript;/*** 版本号*/@TableField("version")private Integer version;/*** 描述*/@TableField("description")private String description;
}

指标窗口枚举

/*** @author wnhyang* @date 2024/3/13**/
@AllArgsConstructor
@Getter
public enum WinType {LAST(0, "last", "最近"),CUR(1, "cur", "本");private final Integer code;private final String name;private final String desc;
}

指标存储

指标数据如何存储呢?下面是Redis方案。

1、使用Redis的有序集合(Sorted Set)结构,有序集合中的每个元素都有一个分数(score),这里的分数我们可以设置为时间戳。

2、添加数据:每当有新的请求到来时,将当前时间戳作为score,用一个固定的字符串(如"request")或者其他唯一标识符作为member,插入到有序集合中。

3、清理过期数据:每次添加新数据后,通过ZRANGEBYSCORE命令获取并删除窗口范围之外的数据。

4、统计指标:要得到窗口内的请求次数,可以直接使用ZCARD命令获取有序集合中元素的数量。

5、定时任务:为了确保过期数据能够自动清除,可以结合RedisKey空间通知机制(Keyspace Notifications)或者外部定时任务定期执行上述清理操作。

计算类指标

计算类指标比较通用,需要存储的数据很容易分析。

但还是有些差别的,次数统计可以在zset中存储value为${事件id},score为时间戳,计算时为zsetsize,但是对于平均、最大值、最小值、求和不能这么做,因为这些都是有计算字段的,并不是如次数统计那样取size就行,所以对于这类指标数据存储是不一样的。

zsetzset+hash两种方案。

单zset

zset作为时间窗口,value为${事件id}+{计算字段},score为时间戳,变化就是value变为事件id与计算字段的组合,计算字段这样就存储下来了,之后计算平均、最大值、最小值、求和取出来再算就可以了,事件id是为了在zset中存储时防重,另外也方便找到原始数据。

优点:直接从zset中获取计算字段,可以独立手动过期删除。

缺点:value存和取需要设计,数据冗余大。

zset+hash

zset作为时间窗口,value为${事件id},score为时间戳,这里没有变化,另外需要hash存储对应事件下需要计算字段的数据。

优点:hash可以存储多项数据,数据冗余少。

缺点:无法确定事件过期删除时间,每次需要多步查询。

指标计算与查询

指标计算可以简单梳理如下。

以下根据此流程分析,其实编程(也不全是指编程)有趣的地方是设计和实现的过程。

模版方法

设计模式-模版方法可以在这应用,从上面的流程并结合指标的分类来分析,主流程中的判断指标状态、指标条件、获取当前时间戳、获取redis数据设置过期、清理过期数据都是通用的,根据指标类型变化的只有添加事件这个步骤,所以定义模版抽象类如下:

@Setter
@Getter
@Slf4j
public abstract class AbstractIndicator {/*** 指标*/protected IndicatorPO indicator;/*** 指标类型*/protected final IndicatorType INDICATOR_TYPE;/*** redisson客户端*/protected final RedissonClient redissonClient;protected AbstractIndicator(IndicatorType indicatorType, RedissonClient redissonClient) {INDICATOR_TYPE = indicatorType;this.redissonClient = redissonClient;}/*** 获取指标类型** @return 指标类型*/public Integer getType() {return INDICATOR_TYPE.getCode();}/*** 获取指标状态** @return true/false*/public boolean getStatus() {return indicator.getStatus();}/*** 指标过滤** @return true/false*/public boolean filter(Map<String, String> eventDetail) {// 1、主属性、从属性不为空if (indicator.getMasterField() != null && eventDetail.get(indicator.getMasterField()) != null) {if (indicator.getSlaveFields() != null) {String[] split = indicator.getSlaveFields().split(",");for (String s : split) {if (eventDetail.get(s) == null) {return false;}}// 2、过滤脚本if (indicator.getFilterScript() == null) {return true;} else {// TODO 脚本过滤return true;}}}return false;}/*** 获取redis key** @param eventDetail 事件详情* @return redis key*/public String getRedisKey(Map<String, String> eventDetail) {return RedisKeys.INDICATOR + indicator.getId() + ":" + INDICATOR_TYPE.getName() + ":" + eventDetail.get(indicator.getMasterField()) + "-" + eventDetail.get(indicator.getSlaveFields());}/*** 获取计算指标结果** @param currentTime 当前时间戳* @param set         redis set* @return 计算指标结果*/public abstract BigDecimal getResult(long currentTime, RScoredSortedSet<String> set);/*** 获取计算指标结果** @param eventDetail 事件详情* @return 计算指标结果*/public BigDecimal getResult(Map<String, String> eventDetail) {// 1、获取当前时间戳long currentTime = System.currentTimeMillis();// 2、获取redis中数据RScoredSortedSet<String> set = redissonClient.getScoredSortedSet(getRedisKey(eventDetail));// 3、清理过期数据if ("last".equals(indicator.getWinType())) {set.removeRangeByScore(-1, true, currentTime - Duration.ofSeconds(indicator.getTimeSlice()).toMillis(), false);} else {set.removeRangeByScore(-1, true, calculateEpochMilli(LocalDateTime.now()), false);}return getResult(currentTime, set);}/*** 计算指标** @param indicator   指标* @param eventDetail 事件详情*/public void compute(IndicatorPO indicator, Map<String, String> eventDetail) {if (indicator == null) {return;} else {this.indicator = indicator;}// 1、状态检查和过滤if (getStatus() && filter(eventDetail)) {// 2、获取当前时间戳long currentTime = System.currentTimeMillis();// 3、获取redis中数据log.info("redisKey:{}", getRedisKey(eventDetail));RScoredSortedSet<String> set = redissonClient.getScoredSortedSet(getRedisKey(eventDetail));if ("last".equals(this.indicator.getWinType())) {set.expire(Duration.ofSeconds(this.indicator.getTimeSlice() * this.indicator.getWinCount()));} else {set.expire(Duration.ofSeconds(this.indicator.getTimeSlice()));}// 4、添加事件addEvent(currentTime, set, eventDetail);// 5、清理过期数据cleanExpiredDate(currentTime, set);}}/*** 添加事件** @param currentTime 当前时间戳* @param set         redis set* @param eventDetail 事件详情*/public abstract void addEvent(long currentTime, RScoredSortedSet<String> set, Map<String, String> eventDetail);/*** 清理过期数据** @param currentTime 当前时间戳* @param set         redis set*/public void cleanExpiredDate(long currentTime, RScoredSortedSet<String> set) {if ("last".equals(indicator.getWinType())) {set.removeRangeByScore(-1, true, currentTime - Duration.ofSeconds(indicator.getTimeSlice()).toMillis(), false);} else {set.removeRangeByScore(-1, true, calculateEpochMilli(LocalDateTime.now()), false);}}/*** 计算时间戳** @param now 当前时间* @return 时间戳*/public long calculateEpochMilli(LocalDateTime now) {ZoneId zoneId = ZoneId.systemDefault();// 这个default分支仅处理WindowSize枚举中未包含的情况return switch (indicator.getWinSize()) {case "M" -> now.withDayOfMonth(1).with(LocalTime.MIN).atZone(zoneId).toInstant().toEpochMilli();case "d" -> now.with(LocalTime.MIN).atZone(zoneId).toInstant().toEpochMilli();case "H" -> now.withMinute(0).withSecond(0).withNano(0).atZone(zoneId).toInstant().toEpochMilli();case "m" -> now.withSecond(0).withNano(0).atZone(zoneId).toInstant().toEpochMilli();case "s" -> now.withNano(0).atZone(zoneId).toInstant().toEpochMilli();default -> throw new IllegalArgumentException("Unsupported window size: " + indicator.getWinSize());};}
}

次数统计指标

因为篇幅原因,这里只贴次数统计指标实现类了。

/*** @author wnhyang* @date 2024/3/11**/
@Component
public class CountIndicator extends AbstractIndicator {public CountIndicator(RedissonClient redissonClient) {super(IndicatorType.COUNT, redissonClient);}@Overridepublic BigDecimal getResult(long currentTime, RScoredSortedSet<String> set) {return BigDecimal.valueOf(set.size());}@Overridepublic void addEvent(long currentTime, RScoredSortedSet<String> set, Map<String, String> eventDetail) {set.add(currentTime, eventDetail.get("seqId"));}}

总结

先到这里吧,还有其他内容到下次吧。

写在最后

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


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

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3.2 Beautiful Soup 的使用

目录 一、Beautiful Soup 的简介 二、解析器 三、基本使用 四、节点选择器 1 选择元素 2 获取名称、属性、文本内容 五、方法选择器 1 find_all 传入 name 节点名 传入 attrs 属性 传入 text 2 find 六、CSS 选择器 1 实例 2 获取属性 3 获取文本 七、结语 一…

HSCCTF-2024-Crypto 复现

文章目录 EZ_MATHSTAR_CHASING_DIARYRSATESTYOUQU 复现参考来源&#xff1a; lazzzaro佬写的题解 EZ_MATH 题目描述&#xff1a; from Crypto.Util.number import *flag HSCCTF{*****************************************} x bytes_to_long(flag.encode()) y getPrime(2…

蓝桥杯第三期模拟赛(java版)

&#x1f4d1;前言 本文主要是【蓝桥杯第三期练习题】的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&#x…

Java17 --- SpringCloud之Consul

目录 一、consul的使用 1.1、主要功能 1.2、安装及运行 1.3、添加微服务到consul 1.3.1、8001微服务添加相关pom、配置文件、注解 1.3.2、80微服务添加相关pom、配置文件、注解 1.4、三个注册中心异同 1.5、consul进行分布式配置 1.5.1、修改8001的yml配置文件 1.5.2…

map、set模拟(底层封装红黑树)

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 前言 前面我们对红黑树进行了模拟实现&#xff1a; 现在我们将使用我们模拟的map和set对我们模拟的红黑树进行封装。 并且&#xff0c;本篇将增加红黑树的迭代器&#xff0c;模拟迭代器&#xff08;这里理解原理即可&…

重建大师模型构建精细网格失败是什么原因导致的呢?(如下图)

出现图中的报错一般是显存溢出、瓦块过大造成的。 重建大师是一款专为超大规模实景三维数据生产而设计的集群并行处理软件&#xff0c;输入倾斜照片&#xff0c;激光点云&#xff0c;POS信息及像控点&#xff0c;输出高精度彩色网格模型&#xff0c;可一键完成空三、自动建模和…

flex弹性盒子实现左中右居中布局

1、效果展示 2、布局与样式 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>测试弹性盒子</title> </head> <body> <div class"out-parent"><div class"…

计算机网络 —— 运输层

运输层 5.1 运输层概述 运输层的主要任务是&#xff0c;如何为运行在不同主机上的应用进程提供直接的通信服务。运输层协议又称为端到端协议。 根据应用需求的不同&#xff0c;因特网的运输层为应用层提供了两种不同的运输协议&#xff0c;即面向连接的TCP和无连接的UDP 5.2…

Vant中<van-button>中icon的位置设置

一般在<van-button>按钮中加入icon&#xff0c;其位置都在左侧 我们可以用&#xff1a;van-position&#xff0c;改变图标展示位置 如&#xff1a;icon-position"right"

【30天】Python从入门到精通详解版—第一天—Python编程语言简介

Python编程语言简介 Python 简介Python 发展历史Python 特点Python 环境搭建Python下载Python安装环境变量配置在 Unix/Linux 设置环境变量在 Windows 设置环境变量 运行Python Python 简介 Python是一种高级编程语言&#xff0c;由Guido van Rossum于1989年底发明。Python在设…

React入门 学习全记录(适合和我一样有Vue经验想学习react的同学~)

前端目前的三大框架&#xff1a;Vue、React、Angular比较 都采用了组件化开发的方式&#xff0c;都是基于MVVM的框架有着虚拟DOM&#xff0c;Vue 和 Angular 都采用了响应式设计的方式&#xff0c;当数据发生变化时会自动更新视图。React 和 Angular 的复杂性也使得它们更加适…

MongoDB黑窗口操作(CRUD)

目录 连接数据库 插入数据 for循环插入数据 根据条件查询 修改数据 删除数据 连接数据库 对应路径下cmd中输入命令mongo即可 插入数据 j{name:"mongo"} t{x:3} 提交&#xff1a;db.things.svae(j);db.things.svae(t); 查询&#xff1a;db.things.find(); …