所有文章 > API开发 > Spring Boot + Redis 实现 API 接口防刷限流

Spring Boot + Redis 实现 API 接口防刷限流


一、
开发分布式高并发系统

在开发分布式高并发系统时有三把利器用来保护系统:缓存、降级、限流。

1、缓存 缓存的目的是提升系统访问速度和增大系统处理容量

2、降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开

3、限流 api限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理

二、api限流实战

本文主要讲的是api接口限流相关内容,虽然不是论述高并发概念中的限流, 不过道理都差不多。通过限流可以让系统维持在一个相对稳定的状态,为更多的客户提供服务。

1、api限流主要应用场景:

  • 电商系统(特别是6.18、双11等)中的秒杀活动,使用限流防止使用软件恶意刷单;
  • 各种基础api接口限流:例如天气信息获取,IP对应城市接口,百度、腾讯地图开放平台等对外提供的基础接口,腾讯地图开放平台等都是通过限流来实现免费与付费直接的转换。
  • 被各种系统广泛调用的api接口,严重消耗网络、内存等资源,需要合理限流。

2、api限流实战

01SpringBoot中集成Redis

SpringBoot中集成Redis相对比较简单,步骤如下:

1.1 引入Redis依赖

<!-- springboot redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

1.2 在application.yml中配置Redis

spring:
redis:
database: 3 # Redis数据库索引(默认为0)
host: 127.0.0.1 # Redis服务器地址
port: 6379 # Redis服务器连接端口
password: 123456 # Redis服务器连接密码(默认为空)
timeout: 2000 # 连接超时时间(毫秒)

jedis:
pool:
max-active: 200 # 连接池最大连接数(使用负值表示没有限制)
max-idle: 20 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)

1.3 配置RedisTemplate

/**
* @Description: redis配置类
* @Author oyc
*/

@Configuration
@EnableCaching

public class RedisConfig extends CachingConfigurerSupport {

/**
* RedisTemplate相关配置
* 使redis支持插入对象
*
* @param factory
* @return 方法缓存 Methods the cache
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();

// 配置连接工厂
template.setConnectionFactory(factory);
// 设置key的序列化器
template.setKeySerializer(new StringRedisSerializer());
// 设置value的序列化器
// 使用Jackson 2,将对象序列化为JSON
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);

return template;
}
}

以上,已经完成Redis的集成,后续使用可以直接注入RedisTemplate,如下所示:

@Autowired
private RedisTemplate<String, Object> redisTemplate;

02实现api限流

2.1 添加自定义AccessLimit注解

使用注解方式实现接口的api限流操作,方便而优雅。

/**
* @Description:
* @Author oyc
*/

@Inherited
@Documented
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {

/**
* 指定second时间内API请求次数
*/
int maxCount() default 5;

/**
* 请求次数的指定时间范围 秒数(redis数据过期时间)
*/
int second() default 60;
}

2.2 编写拦截器

api限流的思路

  • 通过路径:ip的作为key,访问次数为value的方式对某一用户的某一请求进行唯一标识
  • 每次访问的时候判断key是否存在,是否count超过了限制的访问次数
  • 若访问超出限制,则应response返回msg:请求过于频繁给前端予以展示
/**
* @Description: 访问拦截器
* @Author oyc
*/

@Component
public class AccessLimitInterceptor implements HandlerInterceptor {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
try { // Handler 是否为 HandlerMethod 实例
if (handler instanceof HandlerMethod) {
// 强转
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取方法
Method method = handlerMethod.getMethod();
// 是否有AccessLimit注解
if (method.isAnnotationPresent(AccessLimit.class)) {
return true;
}
// 获取注解内容信息
AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
if (accessLimit == null) {
return true;
}
int seconds = accessLimit.second();
int maxCount = accessLimit.maxCount();

// 存储key
String key = request.getRemoteAddr() + ":" + request.getContextPath() + ":" + request.getRequestURI();

// 已经访问的次数
Integer count = (Integer) redisTemplate.opsForValue().get(key);
System.out.println("已经访问的次数:" + count);
if (null == count || -1 == count) {
redisTemplate.opsForValue().set(key, 1, seconds, TimeUnit.SECONDS);
return true;
}

if (count < maxCount) {
redisTemplate.opsForValue().increment(key);
return true;
}

if (count >= maxCount) {
logger.warn("请求过于频繁请稍后再试");
return false;
}
}
return true;
} catch (Exception e) {
logger.warn("请求过于频繁请稍后再试");
e.printStackTrace();
return true;
}
}
}

2.3 注册拦截器并配置拦截路径和不拦截路径

/**
* @Description: 访问拦截器配置
* @Author oyc
* @Date 2020/10/22 11:34 下午
*/

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

@Autowired
private AccessLimitInterceptor accessLimitInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(accessLimitInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/static/**", "/login.html", "/user/login");
}
}

2.4 使用AccessLimit

/**
* @Description:
* @Author oyc
*/

@RestController
@RequestMapping("access")
public class AccessLimitController {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

/**
* 限流测试
*/
@GetMapping
@AccessLimit(maxCount = 3, second = 60)
public String limit(HttpServletRequest request) {
logger.error("Access Limit Test");
return "限流测试";
}
}

2.5 测试

已经访问的次数:null
2020-10-23 00:19:47.423 ERROR 1496 --- [nio-8080-exec-7] c.o.s.AccessLimitController : Access Limit Test
已经访问的次数:1
2020-10-23 00:19:48.543 ERROR 1496 --- [nio-8080-exec-6] c.o.s.AccessLimitController : Access Limit Test
已经访问的次数:2
2020-10-23 00:19:49.069 ERROR 1496 --- [nio-8080-exec-8] c.o.s.AccessLimitController : Access Limit Test
已经访问的次数:3
2020-10-23 00:19:53.612 WARN 1496 --- [nio-8080-exec-9] c.o.s.limit.AccessLimitInterceptor : 请求过于频繁请稍后再试

三、API限流如何应用于腾讯地图开放平台

API限流在腾讯地图开放平台的应用主要体现在以下几个方面:

  1. 服务限流策略展示: 腾讯云API网关提供了展示服务限流策略的接口,通过这个接口可以查看服务的限流规则,包括默认接口请求频率限制为20次/秒。这有助于开发者了解和控制对腾讯地图开放平台的调用频率,避免因超出限制而被限制服务。
  2. 多维度精细化限流: 腾讯云API网关支持多维度的限流,包括整个服务、特定用户等,并且支持不同维度间的自由组合,以满足多种业务场景的需求。这对于腾讯地图开放平台来说,意味着可以针对不同的使用情况和用户群体设置不同的限流策略,以保护后端服务的稳定性和可用性。
  3. 流量控制插件: 腾讯云API网关提供的流量控制插件可以设置API、应用、ClientIP三种资源维度和秒、分钟、小时、天四种时间维度的限流。通过创建基础流控插件并绑定到API,可以有效地保护后端服务不受过多请求的影响,这对于腾讯地图开放平台来说,可以减少因流量突增导致的服务不稳定。
  4. API限流规则创建: 腾讯地图开放平台还提供了创建API限流规则的接口,允许开发者设置API的QPS(每秒查询率)限制,以及开启或禁用限流规则。这为腾讯地图开放平台的使用者提供了更灵活的限流控制,可以根据实际业务需求调整限流策略,确保服务的流畅运行。

源码传送门:

https://github.com/oycyqr/springboot-learning-demo/tree/master/springboot-validated

文章转自微信公众号@传一卓跃

#你可能也喜欢这些API文章!