当前位置: 首页 > news >正文

接口幂等性与限流(二)

下面介绍几种接口限流的方式:

1. 基于自定义注解+Redis实现限流

  • 自定义RateLimit注解
import java.lang.annotation.*;@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {/*** 限制时间段,单位为秒*/int time() default 60;/*** 在限制时间段内允许的最大请求次数*/int count() default 10;/*** 限流的key,支持SpEL表达式*/String key() default "";/*** 提示信息*/String message() default "操作太频繁,请稍后再试";
}
  • 自定义RateLimitAspect切面
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;@Aspect
@Component
@Slf4j
public class RateLimitAspect {@Autowiredprivate StringRedisTemplate redisTemplate;@Around("@annotation(rateLimit)")public Object around(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {// 获取请求的方法名String methodName = pjp.getSignature().getName();// 获取请求的类名String className = pjp.getTarget().getClass().getSimpleName();// 组合限流keyString limitKey = getLimitKey(pjp, rateLimit, methodName, className);// 获取限流参数int time = rateLimit.time();int count = rateLimit.count();// 执行限流逻辑boolean limited = isLimited(limitKey, time, count);if (limited) {throw new RuntimeException(rateLimit.message());}// 执行目标方法return pjp.proceed();}private String getLimitKey(ProceedingJoinPoint pjp, RateLimit rateLimit, String methodName, String className) {// 获取用户自定义的keyString key = rateLimit.key();if (StringUtils.hasText(key)) {// 支持SpEL表达式解析StandardEvaluationContext context = new StandardEvaluationContext();log.error("context:{}", context);MethodSignature signature = (MethodSignature) pjp.getSignature();log.error("signature:{}", signature);String[] parameterNames = signature.getParameterNames();log.error("parameterNames:{}", parameterNames);Object[] args = pjp.getArgs();log.error("args:{}", args);for (int i = 0; i < parameterNames.length; i++) {context.setVariable(parameterNames[i], args[i]);log.error("context new:{}", context);}ExpressionParser parser = new SpelExpressionParser();Expression expression = parser.parseExpression(key);log.error("expression:{}", expression);log.error(" finally context:{}", context);key = expression.getValue(context, String.class);} else {// 默认使用类名+方法名+IP地址作为keyHttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();log.error("request:{}", request);String ip = IpUtils.getIpAddress(request);key = ip + ":" + className + ":" + methodName;}return "rate_limit:" + key;}private boolean isLimited(String key, int time, int count) {// 使用Redis的计数器实现限流try {Long currentCount = redisTemplate.opsForValue().increment(key, 1);// 如果是第一次访问,设置过期时间if (currentCount == 1) {redisTemplate.expire(key, time, TimeUnit.SECONDS);}return currentCount > count;} catch (Exception e) {log.error("限流异常", e);return false;}}
}
  • IpUtils工具类
@Slf4j
public class IpUtils {public static String getIpAddress(HttpServletRequest request) {String ip = request.getHeader("X-Forwarded-For");log.error("ip:{}", ip);try {if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}} catch (Exception e) {log.error("getIpAddress ERROR", e);}return ip;}
}
  • UserController
@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {@Autowiredprivate IUserService userService;@Autowiredprivate UserMapper userMapper;@RateLimit(time = 10, count = 3, message = "请求太频繁,请稍后再试")@GetMapping("/getUserById")public User getUserById(@RequestParam String id) {return userService.getBaseMapper().selectById(id);}//使用SpEL表达式指定key@RateLimit(time = 60, count = 1, key = "#userInfo.id + '_' + #request.remoteAddr")@PostMapping("/userInfo")public int updateUser(@RequestBody UserInfo userInfo, HttpServletRequest request) {return userDetailMapper.updateById(userInfo);}
}

2. Sentinel限流

  • 引入依赖
  <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId><version>2021.0.4.0</version></dependency>
  • SentinelConfig配置类:定义限流规则
@Configuration
public class SentinelConfig {@Beanpublic SentinelResourceAspect sentinelResourceAspect() {return new SentinelResourceAspect();}@PostConstructpublic void init() {// 定义流控规则initFlowRules();}private void initFlowRules() {List<FlowRule> rules = new ArrayList<>();// 为/user/getUserById接口设置流控规则FlowRule userRule = new FlowRule();userRule.setResource("/userDetail/getUserById");userRule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 基于QPS限流userRule.setCount(10); // 每秒允许10个请求rules.add(userRule);// 为/api/order接口设置流控规则FlowRule orderRule = new FlowRule();orderRule.setResource("/api/order");orderRule.setGrade(RuleConstant.FLOW_GRADE_QPS);orderRule.setCount(5); // 每秒允许5个请求orderRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP); // 预热模式orderRule.setWarmUpPeriodSec(10); // 10秒预热期rules.add(orderRule);// 加载规则FlowRuleManager.loadRules(rules);}
}
  • 全局异常处理器
@RestControllerAdvice
public class SentinelExceptionHandler {@ExceptionHandler(BlockException.class)public String handleBlockException(BlockException e) {String message = "请求过于频繁,请稍后再试";if (e instanceof FlowException) {message = "接口限流:" + message;} else if (e instanceof DegradeException) {message = "服务降级:系统繁忙,请稍后再试";} else if (e instanceof ParamFlowException) {message = "热点参数限流:请求过于频繁";} else if (e instanceof SystemBlockException) {message = "系统保护:系统资源不足";} else if (e instanceof AuthorityException) {message = "授权控制:没有访问权限";}return 429 + message;}
}
  • UrlCleaner 类:自定义URL清理逻辑
@Component
public class UrlCleaner implements RequestOriginParser {@Overridepublic String parseOrigin(HttpServletRequest request) {// 获取请求的URL路径String path = request.getRequestURI();// 可以添加更复杂的解析逻辑,例如:// 1. 去除路径变量:/api/user/123 -> /api/user/{id}// 2. 添加请求方法前缀:GET:/api/userreturn path;}
}
  • UserDetailController
@RestController
@Slf4j
@RequestMapping("/userDetail")
public class UserDetailController {@Autowiredprivate IUserDetailService userDetailService;@Autowiredprivate UserDetailMapper userDetailMapper;@GetMapping("/getUserById")// 使用资源名定义限流资源@SentinelResource(value = "/userDetail/getUserById", blockHandler = "getUserBlockHandler",fallback = "getUserFallback")      public UserDetail getUserById(@RequestParam String id) {return userDetailService.getBaseMapper().selectById(id);}// 限流处理方法public UserDetail getUserBlockHandler(String id, BlockException e) {log.warn("Get user request blocked: {}", id, e);throw new RuntimeException("请求频率过高,请稍后再试");}// 异常回退方法public UserDetail getUserFallback(String id, Throwable t) {log.error("Get user failed: {}", id, t);UserDetail fallbackUser = new UserDetail();fallbackUser.setId(id);fallbackUser.setName("Unknown");return fallbackUser;}
}

@SentinelResource注解指定了blockHandlerfallback,则走相应的限流处理逻辑和异常回退逻辑;若没有指定,则会触发全局异常处理器

http://www.dtcms.com/a/404061.html

相关文章:

  • Cherry Studio配置MCP服务全流程解析:让AI自动调用工具处理任务
  • clo3d代做网站电商类网站建设需要多少钱
  • 湖南省邵阳市建设局网站wordpress主题开发视频课程
  • Windows设置默认的两种实现方式:ShellExecuteEx 提权与外部进程调用对比分析
  • 班级网站建设模板下载海口专注海南网站建设
  • Linux:基本指令(一)
  • LeetCode 387 字符串中的第一个唯一字符 Swift 题解:用哈希表快速定位不重复字符
  • 面试复习题---加固技术原理
  • 小蜜蜂网站建设免费摄影网站推荐
  • 青岛网站推广哪家效果好朝阳网站
  • Linux安全机制--网络层安全机制
  • pc端与手机端网站开发的区别国内设计师个人网站欣赏
  • 抽奖机网站怎么做的不用付费的正能量软件
  • Excel——常用函数二
  • 3- 十大排序算法(基数排序、归并排序、快速排序、堆排序)
  • 数码设备存储新搭档 金士顿Select Plus存储卡
  • 好的设计师网站有哪些什么是seo营销
  • pytorch 线性回归
  • 推荐做问卷的网站产品设计方案3000字
  • 商务网站规划建设与管理试卷公众号开发信息什么意思
  • ACE无法启动,腾讯游戏反作弊器无法启动,ACE无法运行,软件无法运行,本次操作由于计算机限制而被取消、请与系统管理员联系
  • golang基础语法(四) 数组
  • 开源手机网站模板刚做的网站为什么百度搜不到
  • 云锁客户端连不上服务器处理
  • 网站建设优化服务熊掌号网站怎么显示百度名片
  • LNMP架构(分离部署)PHP与数据库交互示例
  • Git----常用指令入门
  • 传输无界 金士顿双接口U盘上新抽电脑
  • Real-Time MDNet
  • 从零开始:接入suno api V5.0模型:认识 Suno AI音乐 - 第1篇