使用redis限流

编程入门 行业动态 更新时间:2024-10-28 16:25:08

使用<a href=https://www.elefans.com/category/jswz/34/1771249.html style=redis限流"/>

使用redis限流

springboot集成redisson

请 点这里

应用场景

能精确的针对指定接口进行限流

说明

  1. redisson使用redis + lua脚本实现的漏桶算法进行限流
  2. 可以进行分布式限流和单机限流

使用RRateLimiter进行限流

@GetMapping("/rateLimit")
public String rateLimit(HttpServletRequest request) {RRateLimiter rateLimiter = redissonClient.getRateLimiter("redisson_rate_limiter_method1");//RateType.OVERALL 全局,RateType.PER_CLIENT每个客户端, trySetRate初始化限流策略到redis,再次执行不会进行修改rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);try {// 获取令牌boolean res = rateLimiter.tryAcquire(1);if (!res) {throw new RuntimeException("获取许可失败");}// 执行逻辑Thread.sleep(1000L);} catch (InterruptedException e) {e.printStackTrace();}return "获取许可";
}

注释: 当前配置策略为 1 秒填充2个令牌, 执行代码需要获取1个令牌

测试

使用压测工具本人使用apifox,开3线程执行,结果一个返回500,两个返回200,实现了接口业务的限流

优化成注解方式

引入aop依赖

<!-- SpringBoot 拦截器 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

自定义注解

import com.example.redissiontest.enums.LimitType;import java.lang.annotation.*;/*** 限流注解** @author*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter {public static final String RATE_LIMIT_KEY = "rate_limit:";/*** 限流key*/public String key() default RATE_LIMIT_KEY;/*** 限流时间,单位秒*/public int time() default 60;/*** 限流次数*/public int count() default 100;/*** 限流类型*/public LimitType limitType() default LimitType.DEFAULT;
}

定义枚举 – 限流类型

/*** 限流类型** @author*/public enum LimitType
{/*** 默认策略全局限流*/DEFAULT,/*** 根据请求者IP进行限流*/IP
}

aspectj实现

package com.example.redissiontest.aspectj;import com.example.redissiontest.annotation.RateLimiter;
import com.example.redissiontest.enums.LimitType;
import com.example.redissiontest.util.IpUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;/*** 限流处理** @author*/
@Aspect
@Component
public class RateLimiterAspect {private RedissonClient redissonClient;@Autowired@SuppressWarnings(value = { "unchecked", "rawtypes" })public void setRedissonClient1(RedissonClient redissonClient){this.redissonClient = redissonClient;}@Before("@annotation(rateLimiter)")public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable{int time = rateLimiter.time();int count = rateLimiter.count();RRateLimiter rRateLimiter = redissonClient.getRateLimiter(getCombineKey(rateLimiter,point));// 设置限流策略rRateLimiter.trySetRate(RateType.OVERALL, count, time, RateIntervalUnit.SECONDS);try{// 可调整等待时间和消费令牌数boolean acquire = rRateLimiter.tryAcquire(1, 1, TimeUnit.SECONDS);if (!acquire){throw new RuntimeException("访问过于频繁,请稍候再试");}}catch (RuntimeException e){throw e;}catch (Exception e){throw new RuntimeException("服务器限流异常,请稍候再试");}}/*** 拼接key* key[ip-]className-methodName 策略为限流策略为IP限流的才会拼接ip* @param rateLimiter* @param point* @return*/public String getCombineKey(RateLimiter rateLimiter, JoinPoint point){StringBuilder stringBuffer = new StringBuilder(rateLimiter.key());if (rateLimiter.limitType() == LimitType.IP){stringBuffer.append(IpUtils.getIpAddr()).append("-");}MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();Class<?> targetClass = method.getDeclaringClass();stringBuffer.append(targetClass.getName()).append("-").append(method.getName());return stringBuffer.toString();}}

方法中的IpUtil工具

package com.example.redissiontest.util;import cn.hutool.core.util.ArrayUtil;
import io.micrometer.core.instrument.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.InetAddress;
import java.UnknownHostException;/*** 获取IP方法** @author*/
public class IpUtils {/*** 获取request*/public static HttpServletRequest getRequest(){return getRequestAttributes().getRequest();}public static ServletRequestAttributes getRequestAttributes(){RequestAttributes attributes = RequestContextHolder.getRequestAttributes();return (ServletRequestAttributes) attributes;}public static String getIpAddr() {return getIpAddr(getRequest());}/*** 获取客户端IP** @param request 请求对象* @return IP地址*/public static String getIpAddr(HttpServletRequest request){if (request == null){return "unknown";}String ip = request.getHeader("x-forwarded-for");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("X-Forwarded-For");}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("X-Real-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ip = request.getRemoteAddr();}return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);}/*** 检查是否为内部IP地址** @param ip IP地址* @return 结果*/public static boolean internalIp(String ip){byte[] addr = textToNumericFormatV4(ip);return internalIp(addr) || "127.0.0.1".equals(ip);}/*** 检查是否为内部IP地址** @param addr byte地址* @return 结果*/private static boolean internalIp(byte[] addr){if (ArrayUtil.isEmpty(addr) || addr.length < 2){return true;}final byte b0 = addr[0];final byte b1 = addr[1];// 10.x.x.x/8final byte SECTION_1 = 0x0A;// 172.16.x.x/12final byte SECTION_2 = (byte) 0xAC;final byte SECTION_3 = (byte) 0x10;final byte SECTION_4 = (byte) 0x1F;// 192.168.x.x/16final byte SECTION_5 = (byte) 0xC0;final byte SECTION_6 = (byte) 0xA8;switch (b0){case SECTION_1:return true;case SECTION_2:if (b1 >= SECTION_3 && b1 <= SECTION_4){return true;}case SECTION_5:switch (b1){case SECTION_6:return true;}default:return false;}}/*** 将IPv4地址转换成字节** @param text IPv4地址* @return byte 字节*/public static byte[] textToNumericFormatV4(String text){if (text.length() == 0){return null;}byte[] bytes = new byte[4];String[] elements = text.split("\\.", -1);try{long l;int i;switch (elements.length){case 1:l = Long.parseLong(elements[0]);if ((l < 0L) || (l > 4294967295L)){return null;}bytes[0] = (byte) (int) (l >> 24 & 0xFF);bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);bytes[3] = (byte) (int) (l & 0xFF);break;case 2:l = Integer.parseInt(elements[0]);if ((l < 0L) || (l > 255L)){return null;}bytes[0] = (byte) (int) (l & 0xFF);l = Integer.parseInt(elements[1]);if ((l < 0L) || (l > 16777215L)){return null;}bytes[1] = (byte) (int) (l >> 16 & 0xFF);bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);bytes[3] = (byte) (int) (l & 0xFF);break;case 3:for (i = 0; i < 2; ++i){l = Integer.parseInt(elements[i]);if ((l < 0L) || (l > 255L)){return null;}bytes[i] = (byte) (int) (l & 0xFF);}l = Integer.parseInt(elements[2]);if ((l < 0L) || (l > 65535L)){return null;}bytes[2] = (byte) (int) (l >> 8 & 0xFF);bytes[3] = (byte) (int) (l & 0xFF);break;case 4:for (i = 0; i < 4; ++i){l = Integer.parseInt(elements[i]);if ((l < 0L) || (l > 255L)){return null;}bytes[i] = (byte) (int) (l & 0xFF);}break;default:return null;}}catch (NumberFormatException e){return null;}return bytes;}/*** 获取IP地址** @return 本地IP地址*/public static String getHostIp(){try{return InetAddress.getLocalHost().getHostAddress();}catch (UnknownHostException e){}return "127.0.0.1";}/*** 获取主机名** @return 本地主机名*/public static String getHostName(){try{return InetAddress.getLocalHost().getHostName();}catch (UnknownHostException e){}return "未知";}/*** 从多级反向代理中获得第一个非unknown IP地址** @param ip 获得的IP地址* @return 第一个非unknown IP地址*/public static String getMultistageReverseProxyIp(String ip){// 多级反向代理检测if (ip != null && ip.indexOf(",") > 0){final String[] ips = ip.trim().split(",");for (String subIp : ips){if (false == isUnknown(subIp)){ip = subIp;break;}}}return ip;}/*** 检测给定字符串是否为未知,多用于检测HTTP请求相关** @param checkString 被检测的字符串* @return 是否未知*/public static boolean isUnknown(String checkString){return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);}
}

测试

@GetMapping("/annotationLimit")
@RateLimiter(time = 1, count = 2)
public String annotationLimit() {// 执行逻辑try {Thread.sleep(1000L);} catch (InterruptedException e) {e.printStackTrace();}return "执行成功";
}

更多推荐

使用redis限流

本文发布于:2023-11-15 15:18:53,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1601850.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:redis   限流

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!