Redis+Lua限流的四种算法

在这里插入图片描述

1. 固定窗口(Fixed Window)

原理:
  • 固定窗口算法将时间划分为固定的时间段(窗口),比如 1 秒、1 分钟等。在每个时间段内,允许最多一定数量的请求。如果请求超出配额,则拒绝。
优点:
  • 实现简单,能够快速处理请求限流。
缺点:
  • 在窗口边界处可能出现流量突增的情况(称为“边界效应”),比如两个窗口交界处可能短时间内允许通过的请求数量翻倍。
Lua脚本:
-- KEYS[1]: 限流的键(通常为用户ID或者API)
-- ARGV[1]: 最大允许请求数
-- ARGV[2]: 窗口时间(以秒为单位)local current = redis.call('GET', KEYS[1])if current and tonumber(current) >= tonumber(ARGV[1]) thenreturn 0  -- 返回0表示超出限流
elsecurrent = redis.call('INCR', KEYS[1])if tonumber(current) == 1 thenredis.call('EXPIRE', KEYS[1], ARGV[2])  -- 设置窗口时间endreturn 1  -- 返回1表示未超限
end
Java模拟限流:
package com.strap.common.redis.demo;import lombok.SneakyThrows;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;/*** 固定窗口lua限流Demo** @author strap*/
public class FixedWindowExample {private static final String LIMIT_SCRIPT ="-- KEYS[1]: 限流的键(通常为用户ID或者API)\n" +"-- ARGV[1]: 最大允许请求数\n" +"-- ARGV[2]: 窗口时间(以秒为单位)\n" +"\n" +"local current = redis.call('GET', KEYS[1])\n" +"\n" +"if current and tonumber(current) >= tonumber(ARGV[1]) then\n" +"    return 0  -- 返回0表示超出限流\n" +"else\n" +"    current = redis.call('INCR', KEYS[1])\n" +"    if tonumber(current) == 1 then\n" +"        redis.call('EXPIRE', KEYS[1], ARGV[2])  -- 设置窗口时间\n" +"    end\n" +"    return 1  -- 返回1表示未超限\n" +"end";@SneakyThrowspublic static void main(String[] args) {JedisPoolConfig config = new JedisPoolConfig();config.setBlockWhenExhausted(true);try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {for (int i = 0; i < 100; i++) {Thread.sleep(100);Object o = jedis.eval(LIMIT_SCRIPT, 1, "FixedWindowExample", "10", "5");if (Long.valueOf(1).equals(o)) {System.out.println(i + "=============================放行");} else {System.out.println(i + "拦截=============================");}}}}}

2. 滑动窗口(Sliding Window)

原理:
  • 滑动窗口改进了固定窗口的“边界效应”问题,它通过更细粒度的时间单位来平滑地控制请求。滑动窗口可以在较短的时间窗口内动态调整请求计数,防止瞬时流量激增。
优点:
  • 能平滑地限制请求,减少流量的突增问题。
缺点:
  • 相对固定窗口来说,滑动窗口实现复杂度更高。
Lua脚本:
-- KEYS[1]: 限流的键(通常为用户ID或者API)
-- ARGV[1]: 时间窗口(秒)
-- ARGV[2]: 最大允许请求数
-- ARGV[3]: 当前时间戳(毫秒)-- 移除窗口外的请求
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[3] - ARGV[1] * 1000)local count = redis.call('ZCARD', KEYS[1])if tonumber(count) >= tonumber(ARGV[2]) thenreturn 0  -- 请求被限制
elseredis.call('ZADD', KEYS[1], ARGV[3], ARGV[3])  -- 添加当前请求的时间戳redis.call('EXPIRE', KEYS[1], ARGV[1])  -- 设置过期时间return 1  -- 请求允许
end
Java模拟限流:
package com.strap.common.redis.demo;import lombok.SneakyThrows;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;/*** 滑动窗口lua限流Demo** @author strap*/
public class SlidingWindowExample {private static final String LIMIT_SCRIPT ="-- KEYS[1]: 限流的键(通常为用户ID或者API)\n" +"-- ARGV[1]: 时间窗口(秒)\n" +"-- ARGV[2]: 最大允许请求数\n" +"-- ARGV[3]: 当前时间戳(毫秒)\n" +"\n" +"-- 移除窗口外的请求\n" +"redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[3] - ARGV[1] * 1000)\n" +"\n" +"local count = redis.call('ZCARD', KEYS[1])\n" +"\n" +"if tonumber(count) >= tonumber(ARGV[2]) then\n" +"    return 0  -- 请求被限制\n" +"else\n" +"    redis.call('ZADD', KEYS[1], ARGV[3], ARGV[3])  -- 添加当前请求的时间戳\n" +"    redis.call('EXPIRE', KEYS[1], ARGV[1])  -- 设置过期时间\n" +"    return 1  -- 请求允许\n" +"end";@SneakyThrowspublic static void main(String[] args) {JedisPoolConfig config = new JedisPoolConfig();config.setBlockWhenExhausted(true);try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {for (int i = 0; i < 100; i++) {Thread.sleep(100);long now = System.currentTimeMillis();Object o = jedis.eval(LIMIT_SCRIPT, 1, "SlidingWindowExample", "5", "10", now + "");if (Long.valueOf(1).equals(o)){System.out.println(i + "=============================放行");}else {System.out.println(i + "拦截=============================");}}}}}

3. 令牌桶(Token Bucket)

原理:
  • 令牌桶算法以恒定速率向桶中添加令牌。每次请求需要消耗一定数量的令牌,如果桶内有足够的令牌,允许请求通过;否则拒绝请求。令牌可以积累,从而允许短时间内的流量突发。
优点:
  • 允许短时间的流量突发,适用于需要应对高峰流量的场景。
缺点:
  • 如果高峰流量持续时间较长,可能导致后续请求被大量拒绝。
Lua脚本:
-- 当前的键
local key = KEYS[1]
-- 令牌桶的容量
local capacity = tonumber(ARGV[1])
-- 令牌的生成速率(个/秒)
local rate = tonumber(ARGV[2])
-- 当前时间戳(毫秒)
local now = tonumber(ARGV[3])
-- 请求的令牌数量
local requestedTokens = tonumber(ARGV[4])
-- 键的最大生命周期
local expire = math.ceil(capacity / rate)-- 获取当前桶内的令牌数量,默认为capacity
local currentTokens = tonumber(redis.call('HGET', key, 'currentTokens') or capacity)
-- 获取上次令牌更新的时间
local lastUpdate = tonumber(redis.call('HGET', key, 'last_update') or 0)-- 首次进来初始化令牌数量
if lastUpdate == 0 thenredis.call('HSET', key, 'last_update', now)redis.call('HSET', key, 'currentTokens', currentTokens)redis.call('EXPIRE', key, expire)
else-- 计算在当前时间段内生成的令牌数量local tokensToAdd = math.floor((now - lastUpdate) / 1000 * rate)currentTokens = math.min(capacity, currentTokens + tokensToAdd)
end-- 计算当前是否能提供请求的令牌数量
local isAllow = 0
if currentTokens >= requestedTokens thenisAllow = 1redis.call('HSET', key, 'last_update', now)redis.call('HSET', key, 'currentTokens', currentTokens - requestedTokens)redis.call('EXPIRE', key, expire)
endreturn {isAllow, currentTokens}
Java模拟限流:
package com.strap.common.redis.demo;import lombok.SneakyThrows;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;import java.util.List;/*** 令牌桶lua限流Demo** @author strap*/
public class TokenBucketExample {private static final String LIMIT_SCRIPT = "-- 当前的键\n" +"local key = KEYS[1]\n" +"-- 令牌桶的容量\n" +"local capacity = tonumber(ARGV[1])\n" +"-- 令牌的生成速率(个/秒)\n" +"local rate = tonumber(ARGV[2])\n" +"-- 当前时间戳(毫秒)\n" +"local now = tonumber(ARGV[3])\n" +"-- 请求的令牌数量\n" +"local requestedTokens = tonumber(ARGV[4])\n" +"-- 键的最大生命周期\n" +"local expire = math.ceil(capacity / rate)\n" +"\n" +"-- 获取当前桶内的令牌数量,默认为capacity\n" +"local currentTokens = tonumber(redis.call('HGET', key, 'currentTokens') or capacity)\n" +"-- 获取上次令牌更新的时间\n" +"local lastUpdate = tonumber(redis.call('HGET', key, 'last_update') or 0)\n" +"\n" +"-- 首次进来初始化令牌数量\n" +"if lastUpdate == 0 then\n" +"    redis.call('HSET', key, 'last_update', now)\n" +"    redis.call('HSET', key, 'currentTokens', currentTokens)\n" +"    redis.call('EXPIRE', key, expire)\n" +"else\n" +"    -- 计算在当前时间段内生成的令牌数量\n" +"    local tokensToAdd = math.floor((now - lastUpdate) / 1000 * rate)\n" +"    currentTokens = math.min(capacity, currentTokens + tokensToAdd)\n" +"end\n" +"\n" +"-- 计算当前是否能提供请求的令牌数量\n" +"local isAllow = 0\n" +"if currentTokens >= requestedTokens then\n" +"    isAllow = 1\n" +"    redis.call('HSET', key, 'last_update', now)\n" +"    redis.call('HSET', key, 'currentTokens', currentTokens - requestedTokens)\n" +"    redis.call('EXPIRE', key, expire)\n" +"end\n" +"\n" +"return {isAllow, currentTokens}";@SneakyThrowspublic static void main(String[] args) {JedisPoolConfig config = new JedisPoolConfig();config.setBlockWhenExhausted(true);try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {int rate = 1;       // 速率(令牌的生成速率(个/秒))int capacity = 10;  // 桶容量(令牌桶的容量)int everyTime = 1;  // 每次请求需要拿走的令牌数量for (int i = 0; i < 100; i++) {Thread.sleep(100);long now = System.currentTimeMillis();Object o = jedis.eval(LIMIT_SCRIPT, 1, "TokenBucketExample", String.valueOf(capacity), String.valueOf(rate), String.valueOf(now), String.valueOf(everyTime));List<Object> resutl = (List)o;if (Long.valueOf(1).equals(resutl.get(0))){System.out.println(i + "请求前桶内剩余令牌数:" + resutl.get(1) + "==============================放行");}else {System.out.println(i + "请求前桶内剩余令牌数:" + resutl.get(1) + "=拦截=============================");}}}}}

4.漏桶(Leaky Bucket)

原理:
  • 漏桶算法将请求流量放入一个“漏桶”中,桶以固定速率漏水(处理请求)。如果流量超过桶的容量,多余的请求将被拒绝。漏桶严格控制输出速率,因此不会出现流量突发。
优点:
  • 严格限制请求速率,适用于要求平滑流量的场景。
缺点:
  • 不允许流量突发,处理效率可能不及令牌桶。
Lua脚本:
-- 当前的键
local key = KEYS[1]
-- 漏桶的容量
local capacity = tonumber(ARGV[1])
-- 漏水速率(个/秒)
local rate = tonumber(ARGV[2])
-- 当前时间戳
local now = tonumber(ARGV[3])
-- 请求计数(进来的tokens数量)
local requestedTokens = tonumber(ARGV[4])
-- 键的最大生命周期
local expire = math.ceil(capacity / rate)-- 获取当前漏桶内的令牌数量,默认为0
local currentTokens = tonumber(redis.call('HGET', key, 'tokens') or 0)
-- 获取上次漏桶令牌数量的更新时间
local lastUpdate = tonumber(redis.call('HGET', key, 'last_update') or now)
-- 漏桶在当前时间范围内已流出的令牌数
local leaks = math.floor((now - lastUpdate) / 1000 * rate)
-- 重新计算当前漏桶内的令牌数量 math.min(capacity, currentTokens + deltaTokens)
currentTokens = math.max(currentTokens - leaks + requestedTokens, 0)
-- 是否允许通过,默认不允许
local isAllow = 0
if currentTokens <= capacity then-- 当前令牌数量还能放进去isAllow = 1redis.call('HSET', key, 'tokens', currentTokens)redis.call('HSET', key, 'last_update', now)redis.call('EXPIRE', key, expire);
end
return {isAllow, currentTokens}
Java模拟限流:
package com.strap.common.redis.demo;import lombok.SneakyThrows;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;import java.util.List;/*** 漏桶lua限流Demo** @author strap*/
public class LeakyBucketExample {private static final String LIMIT_SCRIPT ="-- 当前的键\n" +"local key = KEYS[1]\n" +"-- 漏桶的容量\n" +"local capacity = tonumber(ARGV[1])\n" +"-- 漏水速率(个/秒)\n" +"local rate = tonumber(ARGV[2])\n" +"-- 当前时间戳\n" +"local now = tonumber(ARGV[3])\n" +"-- 请求计数(进来的tokens数量)\n" +"local requestedTokens = tonumber(ARGV[4])\n" +"-- 键的最大生命周期\n" +"local expire = math.ceil(capacity / rate)\n" +"\n" +"-- 获取当前漏桶内的令牌数量,默认为0\n" +"local currentTokens = tonumber(redis.call('HGET', key, 'tokens') or 0)\n" +"-- 获取上次漏桶令牌数量的更新时间\n" +"local lastUpdate = tonumber(redis.call('HGET', key, 'last_update') or now)\n" +"-- 漏桶在当前时间范围内已流出的令牌数\n" +"local leaks = math.floor((now - lastUpdate) / 1000 * rate)\n" +"-- 重新计算当前漏桶内的令牌数量 math.min(capacity, currentTokens + deltaTokens)\n" +"currentTokens = math.max(currentTokens - leaks + requestedTokens, 0)\n" +"-- 是否允许通过,默认不允许\n" +"local isAllow = 0\n" +"if currentTokens <= capacity then\n" +"    -- 当前令牌数量还能放进去\n" +"    isAllow = 1\n" +"    redis.call('HSET', key, 'tokens', currentTokens)\n" +"    redis.call('HSET', key, 'last_update', now)\n" +"    redis.call('EXPIRE', key, expire);\n" +"end\n" +"return {isAllow, currentTokens}";@SneakyThrowspublic static void main(String[] args) {JedisPoolConfig config = new JedisPoolConfig();config.setBlockWhenExhausted(true);try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {int rate = 1;       // 速率(每秒恒定流出的token数量)int capacity = 10;  // 桶容量(可存放token数量)int everyTime = 1;  // 每次进来的token数量for (int i = 0; i < 100; i++) {Thread.sleep(100);long now = System.currentTimeMillis();Object o = jedis.eval(LIMIT_SCRIPT, 1, "LeakyBucketExample", String.valueOf(capacity), String.valueOf(rate), String.valueOf(now), String.valueOf(everyTime));List<Object> resutl = (List)o;if (Long.valueOf(1).equals(resutl.get(0))){System.out.println(i + "当前桶内令牌数:" + resutl.get(1) + "==============================放行");}else {System.out.println(i + "当前桶内令牌数:" + resutl.get(1) + "=拦截=============================");}}}}}

算法对比

算法工作机制优点缺点使用场景
固定窗口固定时间窗口内计数实现简单,快速判断窗口边界可能导致流量突发(边界效应)简单的 API 限流,低要求的场景
滑动窗口滑动时间窗口内计数更精确地控制流量,减少流量突发实现较复杂,较高的性能开销动态限流场景,减少流量突增,如 API 网关
令牌桶令牌以固定速率生成,请求消耗令牌支持流量突发,且易于实现和理解如果高峰流量持续时间过长,会导致后续请求被拒绝适合支持突发流量的场景,如限速下载、API 限流
漏桶固定速率处理请求,严格控制输出流量严格控制流量,平滑输出不允许流量突发严格控制请求速率,如网络流量控制,负载均衡等

总结

  • 固定窗口简单易用,适合对流量要求不高的场景。
  • 滑动窗口平滑控制流量,适合对流量突发有一定需求但又希望平稳控制的场景。
  • 令牌桶允许突发流量,适合需要高效处理短时流量高峰的应用。
  • 漏桶严格控制请求速率,适合对平稳处理请求要求很高的场景。

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

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

相关文章

【linux网络编程】| 网络套接字socket | 初识网络开发

前言&#xff1a;本篇内容将要正式进入网络的编程当中。 本篇的目的是为了能够看完就可以上手写一些网络代码了。 但是本篇也并不会单纯的只讲接口&#xff0c; 前面还是会铺垫一些理论知识更好的认识网络传输。下面&#xff0c; 开始我们的学习吧! ps&#xff1a;本篇内容的某…

摄像头点击器常见问题——摄像头视窗打开慢

【嵌入式开发】可编程4k蓝牙摄像头点击器_能编程的摄像头-CSDN博客 拥有上述文章产品的朋友出现标题所述问题&#xff0c;可继续往下阅读 出现以上问题&#xff0c;摄像头画面打开较慢&#xff0c;可以按以下操作进行设置 在环境变量里设置一下这个参数&#xff0c;值设置为1&…

美国超大型数据泄露事件曝光:超1亿人数据被盗

联合健康&#xff08;UnitedHealth&#xff09;首次证实&#xff0c;在 Change Healthcare 勒索软件攻击中&#xff0c;有超过 1 亿人的个人信息和医疗保健数据被盗&#xff0c;这是近年来最大的医疗保健数据泄露事件。 今年 5 月&#xff0c;UnitedHealth 首席执行官安德鲁-威…

深入理解gPTP时间同步过程

泛化精确时间协议(gPTP)是一个用于实现精确时间同步的协议,特别适用于分布式系统中需要高度协调的操作,比如汽车电子、工业自动化等。 gPTP通过同步主节点(Time Master)和从节点(Time Slave)的时钟,实现全局一致的时间参考。 以下是gPTP实现主从时间同步的详细过程:…

Uni-App-03

登录功能开发 实现POST提交 HTTP协议规定请求消息内容类型(Content-Type)有哪些&#xff1f;—— 只有四种 text/plain 没有编码的普通数据 application/x-www-form-urlencoded 编码后的普通数据 multipart/form-data 请求主体中包含文件上传域 application/json 请求主体是 J…

基于SpringBoot的“高校校园点餐系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“高校校园点餐系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 前台首页功能界面图 用户注册、登录界面图 我…

【C++ | 数据结构】八大常用排序算法详解

1. 排序的稳定性 排序是我们生活中经常会面对的问题&#xff0c;小朋友站队的时候会按照从矮到高的顺序排列&#xff1b;老师查看上课出勤情况时&#xff0c;会按照学生的学号点名&#xff1b;高考录取时&#xff0c;会按照成绩总分降序依次录取等等。那么对于排序它是如何定义…

【react 和 vue】 ---- 实现组件的递归渲染

1. 需求场景 今天遇到了一个需求&#xff0c;就是 HTML 的递归渲染。问题就是商品的可用时间&#xff0c;使用规则等数据是后端配置&#xff0c;然后配置规则则是可以无限递归的往下配置&#xff0c;可以存在很多级。后端实现后&#xff0c;数据返回前端&#xff0c;就需要前端…

ImageSharp报错

错误信息 System.MissingMethodException: Method not found: System.Span1<SixLabors.ImageSharp.PixelFormats.Rgba32> SixLabors.ImageSharp.Memory.Buffer2D1.GetRowSpan(Int32).需要升级项目 原来仅升级了SixLabors.ImageSharp没有升级drawing&#xff0c;都升级到…

paddleocr使用FastDeploy 部署工具部署 rknn 模型

在 PC 端转换 pdmodel 模型为 rknn 模型和在板端使用百度飞浆开发的 FastDeploy 部署工具部署 rknn 模型 以下内容是在 PC 端系统为 Ubuntu20.04&#xff0c;板端系统为ubuntu20.04 的环境下实现的 描述&#xff1a; 官网地址 rknn_zoo RKNPU2_SDK …

算法|40K*15.5 + 40w股票+5w签字费|美团北斗大模型面经 ,已拒offer

关注我&#xff0c;掌握目前面试行情&#xff0c;看面经&#xff0c;平均多拿3个offer 背景&#xff1a; 北京理工大学 985本硕&#xff0c;4篇顶会‍‍‍‍ 两面结束&#xff0c;二面面试官说虽然优秀&#xff0c;但不能够入选人才计划。 【第一次谈薪】 37k*15.5 30w股票5…

【C++面试刷题】快排(quick_sort)和堆排(priority_queue)的细节问题

一、快排的快速选择算法两种思路&#xff08;面试会考&#xff09;O(N) 快排的三数取中思路&#xff1a; 重要的是将它三个数进行排序最左为最小&#xff0c;中间为次小&#xff0c;最右为最大的数。&#xff08;错误原因&#xff1a;我刚开始没有将这三个数进行排序&#xff…

如何认识泛基因组?从单一到多元?

近年来&#xff0c;随着多种动植物参考基因组的不断公布及同种不同个体植物基因组间的相互比较&#xff0c;人们逐渐认识到单一参考基因组不能代表物种内的多样性&#xff0c;在此基础上泛基因组概念应运而生。随着三代测序技术的发展&#xff0c;泛基因组的研究迎来了黄金发展…

Android Activity 启动模式

Standard 启动模式 页面跳转顺序 MainActivity -> StandardActivity -> StandardActivity -> StandardActivity 页面栈 示例图 任务栈中只存在MainActivity时 任务栈中存在MainActivity、StandardActivity MainActivity -> StandardActivity MainActivity…

使用freemarker实现在线展示文档功能开发,包括数据填充

首先&#xff0c;在这个独属于程序员节日的这一天&#xff0c;祝大家节日快乐【求职的能找到心仪的工作&#xff0c;已经工作的工资翻倍】。 ---------------------------------------------------------------回到正文-----------------------------------------------------…

合约门合同全生命周期管理系统:企业合同管理的数字化转型之道

合约门合同全生命周期管理系统&#xff1a;企业合同管理的数字化转型之道 1. 引言 在现代企业中&#xff0c;合同管理已经不再是简单的文件存储和审批流程&#xff0c;而是企业合规性、风险管理和业务流程的关键环节之一。随着企业规模的扩大和合同数量的增加&#xff0c;传统…

web3.0 开发实践

优质博文&#xff1a;IT-BLOG-CN 一、简介 Web3.0也称为去中心化网络&#xff0c;是对互联网未来演进的一种概念性描述。它代表着对现有互联网的下一代版本的设想和期望。Web3.0的目标是通过整合区块链技术、分布式系统和加密技术等新兴技术&#xff0c;构建一个更加去中心化…

双非本秋招成功入职小米软开

大家好&#xff0c;我是程序员阿药。最近有位同学说用了这个刷题工具&#xff0c;入职小米软开了&#xff0c;推荐给大家。 简介 微学时光是一款专为计算机专业学生和IT行业求职者设计的面试刷题小程序&#xff0c;它汇集了丰富的计算机面试题和知识点&#xff0c;旨在帮助用…

Linux CentOS7下创建SFTP服务器

本文详细介绍了在Linux CentOS上部署安全文件传输协议&#xff08;SFTP&#xff09;服务器的全过程。SFTP基于SSH&#xff08;安全壳层协议&#xff09;提供文件传输服务&#xff0c;继承了SSH的安全特性&#xff0c;如数据加密、完整性验证和服务器认证等&#xff0c;确保数据…

速来!未发表!DTW-Kmeans-Transformer-BiLSTM组合模型!时序聚类+状态识别!

速来&#xff01;未发表&#xff01;DTW-Kmeans-Transformer-BiLSTM组合模型&#xff01;时序聚类状态识别&#xff01; 目录 速来&#xff01;未发表&#xff01;DTW-Kmeans-Transformer-BiLSTM组合模型&#xff01;时序聚类状态识别&#xff01;效果一览基本介绍程序设计参考…