找网站建设的企业,国内做服装的网站有哪些方面,wordpress 4.9 正式版,wordpress category 404#x1f604; 19年之后由于某些原因断更了三年#xff0c;23年重新扬帆起航#xff0c;推出更多优质博文#xff0c;希望大家多多支持#xff5e; #x1f337; 古之立大事者#xff0c;不惟有超世之才#xff0c;亦必有坚忍不拔之志 #x1f390; 个人CSND主页——Mi… 19年之后由于某些原因断更了三年23年重新扬帆起航推出更多优质博文希望大家多多支持 古之立大事者不惟有超世之才亦必有坚忍不拔之志 个人CSND主页——Micro麦可乐的博客 《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程入门到实战 《RabbitMQ》专栏主要介绍使用JAVA开发RabbitMQ的系列教程从基础知识到项目实战 《设计模式》专栏以实际的生活场景为案例进行讲解让大家对设计模式有一个更清晰的理解 《Jenkins实战》专栏主要介绍JenkinsDocker的实战教程让你快速掌握项目CI/CD是2024年最新的实战教程 《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧代码样例完整 如果文章能够给大家带来一定的帮助欢迎关注、评论互动 Spring Boot通过自定义注解和RedisLua脚本实现接口限流 前言操作思路这么做有什么优势开始实现❶ 项目初始化❷ 创建限流注解❸ 创建Lua脚本❹ 创建Redis处理器❺ 编写限流切面❻ 编写Controller 接口测试总结 前言
本文源码下载地址https://download.csdn.net/download/lhmyy521125/89412365
在我们日常开发的项目中为了保证系统的稳定性很多时候我们需要对系统接口做限流处理它可以有效防止恶意请求对系统造成过载。常见的限流方案主要有 网关限流NGINX、Zuul 等 API 网关服务器端限流服务端接口限流令牌桶算法通过定期生成令牌放入桶中请求需要消耗令牌才能通过熔断机制Hystrix、Resilience4j 等 之前博主写过了一篇 【使用Spring Boot自定义注解 AOP实现基于IP的接口限流和黑白名单】在一些小型应用中足以满足我们的需求但是在并发量大的时候就会有点力不从心本章节博主将给大家介绍 使用自定义注解和 RedisLua脚本实现接口限流
操作思路
使用redis Redis是一种高性能的键值存储系统支持多种数据结构。由于其高吞吐量和低延迟的特点Redis非常适合用于限流
应用Lua脚本 Lua脚本可以在Redis中原子执行多条命令。通过在Redis中执行Lua脚本可以确保限流操作的原子性和一致性
限流策略 本文我们将采用类似令牌桶算法Token Bucket来实现限流 令牌桶算法的基本思想系统会以固定的速率向桶中加入令牌每次请求都需要消耗一个令牌当桶中没有令牌时拒绝请求 这么做有什么优势
高效性 Redis以其高性能著称每秒可以处理数十万次操作。使用Redis进行限流确保了在高并发场景下的高效性。同时Lua脚本在Redis中的执行是原子的这意味着脚本中的一系列命令要么全部执行要么全部不执行避免了竞争条件确保了限流逻辑的一致性
灵活性 通过自定义注解我们可以为不同的接口设置不同的限流策略而不需要修改大量的代码。这种方法允许开发者根据实际需求灵活地调整限流参数例如每秒允许的请求数和令牌的有效期从而更好地应对不同的业务场景
易于维护和扩展 使用Spring AOP和注解可以方便地将限流逻辑应用于不同的接口。这种方式不仅减少了代码的耦合度还使得限流逻辑的维护和扩展变得更加简单。例如当需要为某个新的接口添加限流时只需在方法上添加相应的注解即可而不需要在代码中加入复杂的限流逻辑
分布式限流 Redis作为一个分布式缓存系统可以方便地部署在集群环境中实现分布式限流。通过将限流数据存储在Redis中可以在多个应用实例之间共享限流状态确保在分布式环境下限流策略的一致性
开始实现
❶ 项目初始化
首先创建一个 Spring Boot 项目并添加必要的依赖。在 pom.xml 文件中添加以下内容
dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency
/dependencies配置 application.yml 加入 redis 配置
spring:#redisredis:# 地址host: 127.0.0.1# 端口默认为6379port: 6379# 数据库索引database: 0# 密码password:# 连接超时时间timeout: 10slettuce:pool:# 连接池中的最小空闲连接min-idle: 0# 连接池中的最大空闲连接max-idle: 8# 连接池的最大数据库连接数max-active: 8# #连接池最大阻塞等待时间使用负值表示没有限制max-wait: -1ms❷ 创建限流注解
定义一个自定义注解RateLimit主要有三个属性 限流的key、允许的请求数、令牌有效期
import java.lang.annotation.*;Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface RateLimit {String key() default ; // 限流的keyint limit() default 10; // 每秒允许的请求数int timeout() default 1; // 令牌有效期秒
}❸ 创建Lua脚本
编写一个Lua脚本用于限流操作
-- rate_limit.lua
-- 获取限流的键标识符
local key KEYS[1]
-- 获取每秒允许的最大请求数
local limit tonumber(ARGV[1])
-- 获取键的过期时间秒
local expire_time tonumber(ARGV[2])-- 获取当前的请求数
local current redis.call(get, key)
-- 如果当前请求数存在且已经超过或达到限制返回0拒绝请求
if current and tonumber(current) limit thenreturn 0
else-- 如果当前请求数不存在或未超过限制增加请求数current redis.call(incr, key)-- 如果这是第一次请求设置过期时间if tonumber(current) 1 thenredis.call(expire, key, expire_time)end-- 返回1允许请求return 1
end脚本工作原理总结 每次请求进来时脚本会首先获取当前的请求数。如果请求数已经达到设定的限制则拒绝该请求。否则增加请求数并在首次请求时设置过期时间。返回结果表示是否允许请求。 ❹ 创建Redis处理器
创建Redis处理器用于执行Lua脚本
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;import java.util.Collections;Component
public class RedisRateLimitHandler {Autowiredprivate StringRedisTemplate redisTemplate;private DefaultRedisScriptLong redisScript;public RedisRateLimitHandler() {redisScript new DefaultRedisScript();redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(rate_limit.lua)));redisScript.setResultType(Long.class);}public boolean isAllowed(String key, int limit, int expireTime) {Long result redisTemplate.execute(redisScript, Collections.singletonList(key), String.valueOf(limit), String.valueOf(expireTime));return result ! null result 1;}
}❺ 编写限流切面
使用 AOP 实现限流逻辑IP判断、模拟用户判断
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;Aspect
Component
public class RateLimitAspect {Autowiredprivate RedisRateLimitHandler redisRateLimitHandler;Autowiredprivate HttpServletRequest request;Around(annotation(rateLimit))public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {String key rateLimit.key();int limit rateLimit.limit();int expireTime rateLimit.timeout();switch (key) {case LimitTypeConstants.IP://获取IP地址key request.getRemoteAddr();break;case LimitTypeConstants.USER:/*** 模拟当前获取当前用户限流配置 比如高级会员 1小时允许请求多少次普通会员允许多少次* key user.token;* limit user.user.token;* expireTime 3600 //1小时;*/key user-token;break;default:key rateLimit.key();break;}boolean allowed redisRateLimitHandler.isAllowed(key, limit, expireTime);if (allowed) {return joinPoint.proceed();} else {throw new RuntimeException(请求太多-超出速率限制);}}
}❻ 编写Controller
创建一个简单的限流测试Controller并在需要限流的方法上使用 RateLimit 注解需要编写异常处理返回 RateLimitAspect 异常信息并以字符串形式返回
import com.toher.lua.limit.LimitTypeConstants;
import com.toher.lua.limit.RateLimit;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;RequestMapping(/api)
RestController
public class RedisLuaController {//由于是简单的测试项目这里就直接定义异常处理并为采用全局异常处理ExceptionHandler(value Exception.class)public String handleException(Exception ex) {return ex.getMessage();}GetMapping(/limit-ip)RateLimit(key LimitTypeConstants.IP, limit 5, timeout 30)public String rateLimitIp() {return IP Request successful!;}GetMapping(/limit-export)RateLimit(key LimitTypeConstants.USER, limit 5, timeout 30)public String rateLimitUser(){return USER Request successful!;}GetMapping(/limit)RateLimit(key customer, limit 5, timeout 30)public String rateLimit(){return customer Request successful!;}
}接口测试
使用接口调试工具请求接口测试博主这里使用的是 Apifox我们30秒内请求5次 前5次均返回 Request successful! 第6次会提示 请求太多-超出速率限制
总结
通过本文的步骤我们成功地在Spring Boot项目中结合Redis和Lua脚本实现了一个灵活高效的接口限流功能。通过自定义注解和AOP切面可以方便地为不同的接口设置不同的限流策略。
如果本文对您有所帮助希望 一键三连 给博主一点点鼓励如果您有任何疑问或建议请随时留言讨论。