###阿里的Sentinel进行分布式限流,请看之前文章~
1本地限流,根据注解切面(Guava)
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>xxx</version>
</dependency>
创建自定义注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LocalLimiting {
// 默认每次放入桶中的token
double limitNum() default 20;
//名称
String name() default "";
//是否根据用户token拦截
boolean userRateLimit() default false;
}
创建aop:
@Order(-10000)
@Aspect
@Component
public class WfLocalLimitingAop {
private volatile Map<String, RateLimiter> RATE_LIMITER = new ConcurrentHashMap<>();
@Pointcut("@annotation(localLimiting)")
public void pointCut(LocalLimiting localLimiting) {
}
@Around("pointCut(localLimiting)")
public Object around(ProceedingJoinPoint point, LocalLimiting localLimiting) throws Throwable {
String limitName = localLimiting.name();
if (StringUtils.isBlank(limitName)) {
limitName = point.getSignature().toShortString();
}
boolean userRateLimit = localLimiting.userRateLimit();
if (userRateLimit) {
Object userId = getRequest().getAttribute(TokenCheckInterceptor.LOGIN_USER_KEY);
if (userId == null) {
userId = WebHttpUtils.getRequestIP(getRequest());
}
String source = getRequest().getHeader("t-source");
if (source == null) {
source = String.valueOf(WfCurrentMemberUtil.getSourseByUserAgent());
}
limitName += userId + source;
}
RateLimiter rateLimiter = RATE_LIMITER.computeIfAbsent(limitName, key -> RateLimiter.create(localLimiting.limitNum()));
if (rateLimiter.tryAcquire()) {
return point.proceed();
} else {
return NoRepeatSubmitInterceptor.checkReturnError(point);
}
}
public static HttpServletRequest getRequest() {
try {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
} catch (Exception e) {
return null;
}
}
}
2.分布式限流,根据注解切面(Redisson)
依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>xxxx</version>
</dependency>
配置redisson:
@Bean
public RedissonClient getRedisson() {
Config config = new Config();
config.setCodec(new JsonJacksonCodec());
config.useSingleServer().
setDatabase(10).
setAddress("redis://" + hostname + ":" + port).
setPassword(password).
setRetryInterval(5000).
setTimeout(10000).
setConnectionMinimumIdleSize(8).
setConnectionPoolSize(16).
setConnectTimeout(10000);
return Redisson.create(config);
}
创建自定义注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLimiting {
//名称
String name() default "";
//是否根据用户token拦截
boolean userRateLimit() default false;
//每次给的令牌数
long rate() default 10;
//时间周期,单位秒
long rateInterval() default 1;
}
创建工具类:
@Component
public class RedissonLockUtil {
@Qualifier("getRedisson")
@Autowired(required = false)
private RedissonClient redissonClient;
private static RedissonClient client;
private static final String KEY_PREFIX = "Rate_Limit:";
@PostConstruct
public void init() {
client = redissonClient;
}
public static RRateLimiter getRateLimit(String name, long rate, long rateInterval) {
RRateLimiter rateLimiter = client.getRateLimiter(KEY_PREFIX + name);
rateLimiter.trySetRate(RateType.OVERALL, rate, rateInterval, RateIntervalUnit.SECONDS);
return rateLimiter;
}
//可重入锁
public static RLock getLock(String key) {
return client.getLock(key);
}
private static HttpServletRequest getRequest() {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
return request;
}
}
创建aop:
@Order(-99999)
@Aspect
@Component
public class WfRedisLimitingAop {
@Pointcut("@annotation(redisLimiting)")
public void pointCut(RedisLimiting redisLimiting) {
}
@Around("pointCut(redisLimiting)")
public Object around(ProceedingJoinPoint point, RedisLimiting redisLimiting) throws Throwable {
String limitName = redisLimiting.name();
boolean userRateLimit = redisLimiting.userRateLimit();
if (StringUtils.isBlank(limitName)) {
limitName = point.getSignature().toShortString();
}
if (userRateLimit) {
Object userId = getRequest().getAttribute(TokenCheckInterceptor.LOGIN_USER_KEY);
if (userId == null) {
userId = WebHttpUtils.getRequestIP(getRequest());
}
String source = getRequest().getHeader("t-source");
if (source == null) {
source = String.valueOf(WfCurrentMemberUtil.getSourseByUserAgent());
}
limitName += userId + source;
}
RRateLimiter rateLimit = RedissonLockUtil.getRateLimit(limitName, redisLimiting.rate(), redisLimiting.rateInterval());
if (rateLimit.tryAcquire()) {
return point.proceed();
} else {
return NoRepeatSubmitInterceptor.checkReturnError(point);
}
}
public static HttpServletRequest getRequest() {
try {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
} catch (Exception e) {
return null;
}
}
}
3动态url限流,根据mysql动态加载(Redisson)
1.创建表
CREATE TABLE `wf_rate_limit_config` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`url` varchar(50) NOT NULL COMMENT '需要限流的请求地址',
`user_rate_limit` tinyint(255) NOT NULL DEFAULT '0' COMMENT '0不拦截用户 1拦截用户 维度(用户id或IP 客户端)',
`rate` int(255) NOT NULL DEFAULT '1' COMMENT '每次的令牌数量',
`rate_interval` int(255) NOT NULL DEFAULT '1' COMMENT '时间周期(秒)',
`client` varchar(20) NOT NULL DEFAULT '' COMMENT '多个逗号分隔 1 (ios) 2 (android) 3 (公众号) 4(PC) 6 (小程序) 7(安卓tv)',
`client_type` tinyint(255) NOT NULL DEFAULT '2' COMMENT '0不区分客户端 1区分部分客户端 2全部区分',
`remark` varchar(100) NOT NULL DEFAULT '' COMMENT '备注',
`status` tinyint(255) NOT NULL DEFAULT '0' COMMENT '0无效 1有效',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
2.定时异步加载限流规则到缓存,避免实时加载增加请求时间
@Scheduled(fixedDelay = 1000 * 60, initialDelay = 1000 * 3)
public void rateLimit() {
try {
wfRateLimitService.refresh();
} catch (Exception e) {
log.error("刷新规则错误", e);
}
}
@Service
public class WfRateLimitService {
private Logger log = LoggerFactory.getLogger(WfRateLimitService.class);
public final static Map<String, WfRateLimitConfig> CACHE_RATELIMIT_MAP = new ConcurrentHashMap<>();
@Resource
WfRateLimitConfigMapper wfRateLimitConfigMapper;
public void refresh() {
List<WfRateLimitConfig> allList = wfRateLimitConfigMapper.getWfRateLimitConfigAllListByParam(null);
if (allList.size() < 1) {
return;
}
CACHE_RATELIMIT_MAP.clear();
allList.forEach(l -> {
Integer clientType = l.getClientType();
if (clientType == 1) {
String client = l.getClient();
if (StringUtils.isNotBlank(client)) {
try {
String[] split = client.split(",");
if (split.length > 0) {
l.setClientList(Arrays.asList(split));
}
} catch (Exception e) {
log.error("切分客户端来源失败", e);
}
}
}
CACHE_RATELIMIT_MAP.put(l.getUrl(), l);
});
}
}
3.动态切面控制请求限流
@Order(-99999)
@Aspect
@Component
public class WfDynamicRedisLimitingAop {
@Pointcut("@annotation(getMapping)")
public void pointCutGet(GetMapping getMapping) {
}
@Pointcut("@annotation(postMapping)")
public void pointCutPost(PostMapping postMapping) {
}
@Pointcut("@annotation(requestMapping)")
public void pointCutRequest(RequestMapping requestMapping) {
}
@Around("pointCutRequest(requestMapping)")
public Object aroundRequest(ProceedingJoinPoint point, RequestMapping requestMapping) throws Throwable {
return around(point);
}
@Around("pointCutGet(getMapping)")
public Object aroundGet(ProceedingJoinPoint point, GetMapping getMapping) throws Throwable {
return around(point);
}
@Around("pointCutPost(postMapping)")
public Object aroundPost(ProceedingJoinPoint point, PostMapping postMapping) throws Throwable {
return around(point);
}
public Object around(ProceedingJoinPoint point) throws Throwable {
Map<String, WfRateLimitConfig> cacheRatelimitMap = WfRateLimitService.CACHE_RATELIMIT_MAP;
if (cacheRatelimitMap.size() < 1) {
return point.proceed();
}
HttpServletRequest request = getRequest();
if (request == null) {
return point.proceed();
}
String requestURI = request.getRequestURI();
WfRateLimitConfig wfRateLimitConfig = cacheRatelimitMap.get(requestURI);
if (wfRateLimitConfig == null) {
return point.proceed();
}
String limitName = requestURI;
//用户维度
Integer userRateLimit = wfRateLimitConfig.getUserRateLimit();
if (userRateLimit == 1) {
Object userId = getRequest().getAttribute(TokenCheckInterceptor.LOGIN_USER_KEY);
if (userId == null) {
userId = WebHttpUtils.getRequestIP(getRequest());
if ("0:0:0:0:0:0:0:1".equals(userId)) {
userId = "localhost";
}
}
limitName += ":" + userId;
}
//客户端来源
Integer clientType = wfRateLimitConfig.getClientType();
switch (clientType) {
case 0://不区分客户端
break;
case 1://区分部分客户端
String currentSource = getClientSource();
List<String> clientList = wfRateLimitConfig.getClientList();
if (CollectionUtils.isEmpty(clientList)) {
return point.proceed();
}
if (!clientList.contains(currentSource)) {
return point.proceed();
}
limitName += "-" + getClientSource();
break;
case 2://全部区分
limitName += "-" + getClientSource();
break;
}
Integer rate = wfRateLimitConfig.getRate();
Integer rateInterval = wfRateLimitConfig.getRateInterval();
if (rate < 1 || rateInterval < 1) {
return point.proceed();
}
RRateLimiter rateLimit = RedissonLockUtil.getRateLimit(limitName, wfRateLimitConfig.getRate(), wfRateLimitConfig.getRateInterval());
if (rateLimit.tryAcquire()) {
return point.proceed();
} else {
return NoRepeatSubmitInterceptor.checkReturnError(point);
}
}
public String getClientSource() {
HttpServletRequest request = getRequest();
if (request == null) {
return "0";
}
String source = getRequest().getHeader("t-source");
if (source == null) {
source = String.valueOf(WfCurrentMemberUtil.getSourseByUserAgent());
}
return source;
}
public static HttpServletRequest getRequest() {
try {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
} catch (Exception e) {
return null;
}
}
}
4.删除无用限流规则 手动执行脚本删除无用redis规则
本文由 GY 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2022/09/27 14:27