基于Lua实现滑动窗口统计实现

基于Lua脚本实现滑动窗口统计功能

 目前风险防控、营销等领域很多场景涉及较多规则的应用,底层技术实现大多依赖决策引擎。决策引擎提供专家规则的可视化维护功能和决策服务,很多专家规则依赖实时累计特征,例如(客户维度当天交易笔数,一小时内汇总交易金额等),实时累计特征一般通过流式计算、内存计算等方式实现。本文介绍基于redis 通过lua脚本实现实时统计,包含简单统计和基于滑动窗口实现的统计功能的实现。

总体实现思路

  1. 明确需求:1)实时累计特征配置后即可生效,交易过程实时累计,决策时支持实时查询。2)实时特征类型较多,本文局限介绍简单次数统计,滑动窗口次数统计和关联次数统计。
  2. 合理设计:1)明确各类特征使用的数据结构,数据清理方式。2)设计入参(查询,更新,简单统计还是滑动窗口统计,过期时间,开始时间片等)
  3. 工程研发:1)环境搭建 :redis ,GUI for redis,IntelliJ IDEA 2)搭建java工程 和测试类 3)编写lua脚本;
  4. 总结: 实时统计在流式计算外,可以考虑采用缓存计算实现,目前各类缓存软件支持lua脚本的高效执行。

明确需求

简单统计

举例:客户维度1小时转账的次数,

滑动窗口统计

举例:客户维度最近30分钟转账的次数,

关联统计

举例:设备维度最近1小时登录的账户数量,1个账户登录多次,重复计算账户数量。

合理设计

1、总体流程

在这里插入图片描述

2、脚本设计逻辑

简单次数统计

数据结构:key-value
实现逻辑:1)查询:当key存在时,返回key对应的value值,key不存在时返回0.
2)更新:当key存在时,调用inrc命令 值自增1.当key不存在时设置值为1.

滑动窗口次数统计

数据结构:每个时间片值存放在hash,key为时间片名称,value为次数。 时间片冗余存储在sorted set。
实现逻辑:1)查询:开始时间片为传入参数,结束时间默认为当前时间,根据 开始时间片和当前时间片过滤sorted set,获取列表后,遍历hash获取各时间片数量,做累加获取结果。
2)更新:当Hash key和当前时间片key存在时,获取当前时间片对应次数,增加一后再写入hash,同时更新sorted set key。当Hash key和当前时间片key存在时,hash写入key为当前时间片,value值1,同时更新sorted set key。
3)数据清理:时间片保留最大数量有特征配置时确定,当时间片超过最大值时,将最旧的数据删除。

关联次数统计

数据结构:sorted set。 key为关联字段名称,score为当前时间。
实现逻辑:1)查询:开始时间片为传入参数,结束时间默认为当前时间,根据 开始时间片和当前时间片过滤sorted set,返回过滤结果的统计数量。
2)更新:当Item存在时,更新Item的score为当前时间。Item不存在时,增加Item,score为当前时间。
举例: 统计设备维度1小时登录的账户数量。 key为设备ID,sorted set key:账户ID,score为当前时间。
3)数据清理:特征配置时确定Item最大数量,当Item数量超过最大值时,删除score最小的项【最旧的项】

工程研发

环境搭建 :redis ,GUI for redis,IntelliJ IDEA

pom文件引入redis依赖

   <dependency>
     <groupId>redis.clients</groupId>
     <artifactId>jedis</artifactId>
     <version>2.8.0</version>
   </dependency>

REDIS 命令参考:https://www.redis.net.cn/order/3528.html
LUA语法参考:https://www.runoob.com/lua/lua-tables.html
GUI for redis :搜索RESP.app 下载。

次数统计lua脚本,包含简单统计和滑动窗口统计

---- 查询  非滑动窗口统计次数
local function querySimpleCount(cacheKey)
    if (redis.call('exists', cacheKey) == 1) then
        return redis.call('get', cacheKey);
    else
        return 0;
    end;
end;
---- 更新  非滑动窗口统计次数
local function updateSimpleCount(cacheKey, expireTTL)
    if (redis.call('exists', cacheKey) == 1) then
        redis.call('incr', cacheKey);
    else
        redis.call('set',cacheKey,1);
        redis.call('EXPIRE',cacheKey,tonumber(expireTTL));
    end
    return querySimpleCount(cacheKey);
end;

---- 查询  滑动窗口统计次数
local function querySlideWindowCount(cacheKey,beginTimeSlice)
    local cacheSliceZSettKey=cacheKey..'ZSet'
    local filterItmeTable=redis.call('ZRANGEBYLEX',cacheSliceZSettKey,'['..beginTimeSlice,'[ZZZ');
    local totalResult=0;
    for k,v in ipairs(filterItmeTable) do
        totalResult=totalResult+tonumber(redis.call('HGET',cacheKey,v))
    end;
    return totalResult;
end;


---- 更新  滑动窗口统计次数
local function updateSlideWindowCount(cacheKey, beginTimeSlice,timeSliceName,timeSliceCnt,expireTTL)
    local cacheSliceZSettKey=cacheKey..'ZSet'
    if (redis.call('HEXISTS', cacheKey,timeSliceName) == 1) then
        redis.call('HINCRBY', cacheKey,timeSliceName,1);
        redis.call('ZADD', cacheSliceZSettKey,100,timeSliceName);
    else
        redis.call('hset',cacheKey,timeSliceName,1);
        redis.call('EXPIRE',cacheKey,tonumber(expireTTL));
        redis.call('ZADD', cacheSliceZSettKey,100,timeSliceName);
        redis.call('EXPIRE',cacheSliceZSettKey,tonumber(expireTTL));
    end
    if redis.call('ZCARD',cacheSliceZSettKey)>tonumber(timeSliceCnt) then
       local tempTimeSliceNameTable=redis.call('ZRANGE',cacheSliceZSettKey,0,0);
       if #tempTimeSliceNameTable>0 then
           redis.call('HDEL',cacheKey,tempTimeSliceNameTable[1]);
           redis.call('ZREM',cacheSliceZSettKey,tempTimeSliceNameTable[1]);
       end
    end
    return querySlideWindowCount(cacheKey,beginTimeSlice);
end;

----KEY1 键值,arg1[1:查询,2:更新 ]; arg2[1:简单统计次数,2:滑动窗口统计];arg3[beginTimeSlice 开始时间片]
----arg4[timeSliceName 当前时间片],arg5[timeSliceCnt 时间片保留数量],arg6[expireTTL key保留周期]
   if (ARGV[2] == '1' ) then
       if (ARGV[1] == '1' ) then
           return querySimpleCount(KEYS[1]);
       elseif (ARGV[
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值