第十节 SpringBoot Starter 实战之 redis 滑动窗口

使用 redis 实现滑动窗口,我们会基于这个场景,建立一个 Starter,在这之前,我们需要先。理解这个场景。

关键字:滑动窗口、流式计算、lua脚本、redis、zset、starter

概要:本文封装 redis 的API,实现简易滑动窗口,分别从业务背景、窗口理解、redis 的 zset 结构,lua 脚本,注意事项,不足等进行讲解

一、业务背景

规则预警,在特定时间触发规则达到 n 次后发出告警信息,例如:5 分钟之内失败 2 次,当满足条件后会发一条通知告警;数值可以根据实际情况动态配置。

下图是动态展示滑动窗口的示意图,按照黄色线固定窗口进行移动,窗口内会出现各种数值点,对窗口数字进行统计:

借助 redis 的 zset 有序集合能力,其中 score 字段要求有序,因此使用时间戳做 score,这样既保证顺序也能根据时间窗口计算窗口内的个数,通过计算时间窗口内的个数再与业务做判断;另外为了保障原子能力,使用lua脚本

二、redis版功能实现

通过 Lua 脚本实现 CAS(check-and-set)命令。

关于窗口在业务上的诉求,我分了三种场景,分别如下所示:

2.1 场景一、统计时间窗口内是否达到预定阈值,返回true和false, 并且达到阈值后清除

描述:1. 添加计数,2.将时间窗口外的数据移除;3.统计当前窗口的个数;4.判断是否超过阈值,5.超过清理并返回,否则返回false

redis.pcall('zadd', KEYS[1], ARGV[1], ARGV[1]);
redis.pcall('zremrangebyscore', KEYS[1], 0, ARGV[2]);
redis.pcall("expire", KEYS[1], ARGV[3]);
if tonumber(redis.pcall('zcard',KEYS[1])) >= tonumber(ARGV[4]) 
    redis.pcall('zremrangebyscore', KEYS[1], 0, ARGV[1]);
    then return true end;
return false;

注意:不要使用下面这种方式。 集群方式下,不支持local变量,另外尽量少用变量,减少lua脚本占用过多内存

local key           = KEYS[1]; 
local current_time  = ARGV[1]; 
local pre_time      = ARGV[2]; 
local expire_second = ARGV[3]; 
local threshold     = ARGV[4]; 
redis.pcall("zadd", key, current_time, current_time);
redis.pcall("zremrangebyscore", key, 0, pre_time);;
local count = redis.pcall("zcard",key);
redis.pcall("expire", key, expire_second);
if tonumber(count) >= tonumber(threshold) then 
    redis.pcall("zremrangebyscore", key, 0, current_time);
    return true end;
return false;

2.2 场景二、统计时间窗口内是否达到预定阈值,返回true和false, 满足true的时候不做清理

redis.pcall('zadd', KEYS[1], ARGV[1], ARGV[1]);
redis.pcall('zremrangebyscore', KEYS[1], 0, ARGV[2]);
redis.pcall("expire", KEYS[1], ARGV[3]);
if tonumber(redis.pcall('zcard',KEYS[1])) >= tonumber(ARGV[4]) 
    then return true end;
return false;

2.3 场景三、统计时间窗口内的个数

只统计个数,不做其他的

redis.pcall('zadd', KEYS[1], ARGV[1], ARGV[1]);
redis.pcall('zremrangebyscore', KEYS[1], 0, ARGV[2]);
redis.pcall("expire", KEYS[1], ARGV[3]);
return redis.pcall("zcard",KEYS[1]);

当然在实际落地的过程,会遇到一些其他问题,比如使用lua限制,分布式限制等

有了上面的三个场景后,接下来我们开始实战一个 Starter

三、Starter 实现

3.1 自定义一个 Starter 需要的流程(关键步骤)

  1. 选择一个合理的业务场景。比如我选择了 滑动窗口这个场景。
  2. 创建新的Maven项目,并引入依赖,通常命名需要遵循Spring Boot的命名规范,通常是<your-module-name>-spring-boot-starter
  3. 代码实现,以及其他类的引入
  4. 编写自动配置类。 xxxAutoConfiguration
  5. 编写 spring.factories **文件,**在src/main/resources/META-INF/spring.factories中注册自定义的自动配置类
  6. 打包并发布到仓库,并在其他项目测试

3.2 本 Starter 的项目工程结构

本文的源码地址:uzong-starter-learning: 学习 SpringBoot Starter 的工程案例

四、代码实现

4.1 lua 脚本

本文给出三个 lua 脚本,分别应对三个场景。

lua 是一种非常简单的脚本语言,如果想了解更多,可以在菜鸟教程中学习,非常轻量:Lua 教程 | 菜鸟教程

4.2 核心逻辑

com.uzong.sliding.window.calculate.CalculateCore

细节描述:

  1. CalculateCore 的创建,需要交给 xxxAutoConfiguration 类。不可在 CalculateCore 上添加 @Resource, @Service 等类。所有 Starter 类的创建都尽量交给 xxxAutoConfiguration,用来控制类的加载。 这是一种规范
  2. 此处依赖 RedisTemplate,DefaultRedisScript、RedisSerializer 等。用例处理接口调用、序列化等。
  3. 调用的是 redisTemplate.execute方法,参数中包含了执行脚本、序列化、业务参数、过期时间等等。最核心也是最基础的接口。用于执行Redis脚本

4.3 对外的 service api

用于上层可以直接使用的 api,目前只提供了3个。对于 api 尽量包含详细的说明。以及注意实现

其实现类,则主要依赖 CalculateCore 类,就不过多介绍

4.4 配置类 SlidingWindowAutoConfiguration

注意细节:

  1. 通过 ConditionalOnClass,否则无法被创建
  2. 通过构造方式在此自动装配类中创建 SlidingWindowServiceImpl 等对象。方便管理所有相关的 bean 对象

4.5 spring.factories

前缀都是 org.springframework.boot.autoconfigure.EnableAutoConfiguration

4.6 打包并发布到仓库,并在其他项目测试

先发布到本地 Maven 仓库,测试没问题,再发布到公司的私服。需要注意版本管理。

到这里,Starter 实战结束了。更多细节可以参考本项目。

执行测试

http://localhost:8080/api/sl/calculateCount?bizCodeKey=001&windowSeconds=10
http://localhost:8080/api/sl/clearOnCondition?bizCodeKey=001&windowSeconds=3&threshold=5
http://localhost:8080/api/sl/keepCalculate?bizCodeKey=001&windowSeconds=3&threshold=5

已同步发布到公众号:面汤放盐 第十节 Starter 实战之 redis 滑动窗口 (qq.com)

掘金账号:第十节 SpringBoot Starter 实战之 redis 滑动窗口 - 掘金 (juejin.cn)

相关推荐
菜鸡且互啄692 小时前
Spring Boot Security自定义AuthenticationProvider
java·jvm·spring boot
青花锁2 小时前
Springboot实战:AI大模型+亮数据代理助力短视频时代
人工智能·spring boot·后端·短视频·亮数据
Mr.Aholic2 小时前
水果商城系统 SpringBoot+Vue
vue.js·spring boot·后端
云烟成雨TD2 小时前
Redis 7.x 系列【19】管道
redis·缓存·高性能
一个小浪吴啊3 小时前
Java SpringBoot MongoPlus 使用MyBatisPlus的方式,优雅的操作MongoDB
java·spring boot·mongodb
just-julie3 小时前
Redis 分布式集群方案 Cluster
数据库·redis·分布式
泡芙冰淇淋ya3 小时前
【redis】redis知识点学习目录整理及简介
数据库·redis·学习
java6666688885 小时前
如何在Spring Boot中实现实时通知
java·spring boot·后端
虫小宝5 小时前
Spring Boot与Jenkins的集成
spring boot·后端·jenkins
java6666688886 小时前
在Spring Boot中集成分布式日志收集方案
spring boot·分布式·jenkins