您现在的位置是:首页 > 技术人生 > 后端技术后端技术
通过Redis限制API调用次数
高晓波2020-02-18【后端技术】人已围观
简介最近在做微信公众号返利机器人,通过曲线救国的方式拿到的淘宝客工具商权限(用别人的App Key),在服务器搭建了透传程序。
使用过程中发现一个问题:阿里妈妈对淘宝API的调用频率有限制,报错信息反馈是40次/秒。
最近在做微信公众号返利机器人,通过曲线救国的方式拿到的淘宝客工具商权限(用别人的App Key),在服务器搭建了透传程序。
使用过程中发现一个问题:
阿里妈妈对淘宝客API的调用频率有限制,报错信息反馈是40次/秒。
那么需要对透传程序进行限流操作,因为以后可能还要开放给朋友们一起使用,好东西大家一起分享嘛。
进入正题,查阅相关资料,通过redis的INCR操作可实现所需功能。
什么是INCR操作?
限流Aspect
测试截图:
使用过程中发现一个问题:
{
"error_response": {
"msg": "Remote service error",
"code": 15,
"sub_msg": "您的账号因调用不符合要求,当前触发限流。为保障广大推广者正常使用系统,会有账号维度的限流机制:通过API、淘宝客PC后台、联盟APP或使用第三方工具获取数据,同账号会统一限流。同一个账号1秒总请求次数约200次,其中调用API约40次,超过或接近临界值,会触发限流。建议您逐一排查各个渠道的总调用量,降低调用频次。此外,在淘宝客PC后台_效果报表_推广者推广明细页面,注意事项第3条会有订单查询方式建议,违反建议也会触发限流。请按建议调整请求策略。",
"sub_code": "9100",
"request_id": "xxxxx"
}
}
阿里妈妈对淘宝客API的调用频率有限制,报错信息反馈是40次/秒。
那么需要对透传程序进行限流操作,因为以后可能还要开放给朋友们一起使用,好东西大家一起分享嘛。
进入正题,查阅相关资料,通过redis的INCR操作可实现所需功能。
什么是INCR操作?
INCR key
将 key 中储存的数字值增一。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
本操作的值限制在 64 位(bit)有符号数字表示之内。
思路:当API被调用时,在调用淘宝API前进行INCR key,key可以是ip地址相关,用户相关,或是全局的一个key。如果返回值为1,则表示刚开始调用,赋予key过期时间,然后判断返回值是否大于设定的limit,如果大于抛异常,伪代码如下:
let limit = 100;
let limit_seconds_time = 60
let count = redis.incr(key);
if count == 1
redis.expire(key, 60)
if count > limit
throw exception;
service()
实现代码如下:
APILimit注解:
package com.gaoxiaobo.common.annotations;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface APILimit {
/**
*
* @Description: 限制某时间段内可以访问的次数,默认设置100
* @return
*
*/
int limitCounts() default 100;
/**
*
* @Description: 限制访问的某一个时间段,单位为秒,默认值1分钟即可
* @return
*
*/
int timeSecond() default 60;
}
限流Aspect
package com.gaoxiaobo.common.aspect;
import com.gaoxiaobo.common.annotations.APILimit;
import com.gaoxiaobo.common.exception.BusiException;
import com.gaoxiaobo.service.modules.RedisService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import static com.gaoxiaobo.common.constants.ApiConstants.*;
@Aspect
@Component
public class APILimitAspect {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private RedisService redisService;
@Pointcut("execution(* com.gaoxiaobo.controller.modules.*.*(..))")
public void before(){
}
@Before("before()")
public void requestLimit(JoinPoint joinPoint) throws Exception {
// 获取HttpRequest
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 判断request不能为空
if (request == null) {
throw new BusiException("HttpServletRequest有误...");
}
APILimit limit = this.getAnnotation(joinPoint);
//controller 方法限流
if(limit != null) {
String ip = request.getRemoteAddr();
String uri = request.getRequestURI();
logger.debug("ip:{}, uri:{}", ip, uri);
String redisKey = METHOD_LIMIT_KEY_PRE + uri + ":" + ip;
long methodCounts = redisService.incr(redisKey);
// 如果该key不存在,则从0开始计算,并且当count为1的时候,设置过期时间
if (methodCounts == 1) {
redisService.expire(redisKey, limit.timeSecond());
}
// 如果redis中的count大于限制的次数,则报错
if (methodCounts > limit.limitCounts()) {
throw new BusiException("api调用超限");
}
}
//全局限流,每秒30次
long count = redisService.incr(GLOBAL_LIMIT_KEY);
if (count == 1) {
redisService.expire(GLOBAL_LIMIT_KEY, 1);
}
if (count > GLOBAL_LIMIT) {
throw new BusiException("api调用超限");
}
}
/**
*
* @Description: 获得注解
* @param joinPoint
* @return
* @throws Exception
*/
private APILimit getAnnotation(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(APILimit.class);
}
return null;
}
}
测试截图:
很赞哦! ()
随机图文
-
mybatis plus生成实体entity没有id
总是写很多重复的增删改查毫无意义,浪费生命,于是乎准备弄一个增删改查代码生成工具。网上查了一下mybatis plus挺好的,于是乎打开官网 Quick Start!按照官网的演示栗子:// 演示 -
【转】GPT 应用开发和思考
在过去几个月的时间中,我们似乎正处于人工智能的革命中。除了大多数人了解的 OpenAI ChatGPT 之外,许多非常新颖、有趣、实用的 AI 应用也是层出不穷,并且在使用这些应用时时,笔者也确确实实的感受到了生产力的提高。 -
SpringBoot排除自动配置
SpringBoot的自动配置给我们开发带来了极大的便利,但有些时候也带来了一些问题。 问题场景: 该项目是基于Springboot + dubbo的微服务架构,框架结构web + facade + service,某 -
Springboot集成quartz定时任务可视化配置
使用quartz定时任务已经有一段时间了,今天记录一下Springboot 2.x集成Quartz。