Springboot+Redis实现API接口防刷限流的项目实践

 更新时间:2024年07月12日 09:49:25   作者:忧伤夏天的风  
本文主要介绍了Springboot+Redis实现API接口防刷限流的项目实践,通过限流可以让系统维持在一个相对稳定的状态,为更多的客户提供服务,具有一定的参考价值,感兴趣的可以了解一下

前言

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

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

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

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

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

api接口的限流主要应用场景有:

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

api限流实战

一、SpringBoot中集成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
 * @Date 2020/4/22 11:50 下午
 */
@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);
        //json转对象类,不设置默认的会将json转成hashmap
        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(具体可参考另一篇博客:https://blog.csdn.net/u014553029/article/details/106087846),如下所示:

@Autowired
private RedisTemplate<String, Object> redisTemplate;

二、实现限流

2.1 添加自定义AccessLimit注解

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

/**
 * @Description:
 * @Author oyc
 * @Date 2020/10/22 11:16 下午
 */
@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 编写拦截器

限流的思路

  • 通过路径:ip的作为key,访问次数为value的方式对某一用户的某一请求进行唯一标识
  • 每次访问的时候判断key是否存在,是否count超过了限制的访问次数
  • 若访问超出限制,则应response返回msg:请求过于频繁给前端予以展示
/**
 * @Description: 访问拦截器
 * @Author oyc
 * @Date 2020/10/22 11:20 下午
 */
@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) throws Exception {
        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.getServletPath();

                // 已经访问的次数
                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 IntercepterConfig  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
 * @Date 2020/10/22 11:36 下午
 */
@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.4 测试

源码传送门:https://github.com/oycyqr/springboot-learning-demo/tree/master/springboot-validated

到此这篇关于Springboot+Redis实现API接口防刷限流的项目实践的文章就介绍到这了,更多相关Springboot Redis 接口防刷限流内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring Boot整合持久层之JdbcTemplate多数据源

    Spring Boot整合持久层之JdbcTemplate多数据源

    持久层是JavaEE中访问数据库的核心操作,SpringBoot中对常见的持久层框架都提供了自动化配置,例如JdbcTemplate、JPA 等,MyBatis 的自动化配置则是MyBatis官方提供的。接下来分别向读者介绍Spring Boot整合这持久层技术中的整合JdbcTemplate
    2022-08-08
  • Java  HttpURLConnection超时和IO异常处理

    Java HttpURLConnection超时和IO异常处理

    这篇文章主要介绍了Java HttpURLConnection超时和IO异常处理的相关资料,需要的朋友可以参考下
    2016-09-09
  • 浅谈Java并发编程之Lock锁和条件变量

    浅谈Java并发编程之Lock锁和条件变量

    这篇文章主要介绍了浅谈Java并发编程之Lock锁和条件变量,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • Fluent Mybatis实现环境隔离和租户隔离

    Fluent Mybatis实现环境隔离和租户隔离

    我们在实际的业务开发中,经常会碰到环境逻辑隔离和租户数据逻辑隔离的问题。本文就详细的来介绍一下,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • springboot2 生产部署注意事项及示例代码

    springboot2 生产部署注意事项及示例代码

    这篇文章主要介绍了springboot2 生产部署注意事项及示例代码,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-04-04
  • Spring中的Bean对象初始化详解

    Spring中的Bean对象初始化详解

    这篇文章主要介绍了Spring中的Bean对象初始化详解,我们也可以根据Java语言的特性猜测到其很有可能是通过反射机制来完成Bean的初始化操作,接下来我们一步一步的剖析Spring对Bean的初始化操作,需要的朋友可以参考下
    2023-12-12
  • 邻接表无向图的Java语言实现完整源码

    邻接表无向图的Java语言实现完整源码

    这篇文章主要介绍了邻接表无向图的Java语言实现完整源码,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • Spring Boot2中如何优雅地个性化定制Jackson实现示例

    Spring Boot2中如何优雅地个性化定制Jackson实现示例

    这篇文章主要为大家介绍了Spring Boot2中如何优雅地个性化定制Jackson实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • IDEA正则表达式全局搜索图文教程

    IDEA正则表达式全局搜索图文教程

    当您要搜索和替换特定的文本模式时,请使用正则表达式,下面这篇文章主要给大家介绍了关于IDEA正则表达式全局搜索的相关资料,文中通过代码以及图文介绍的非常详细,需要的朋友可以参考下
    2024-08-08
  • 详解SpringBoot接收参数的五种形式

    详解SpringBoot接收参数的五种形式

    在Spring Boot中,接收参数可以通过多种方式实现,本文给大家介绍了SpringBoot接收参数的五种形式,并通过代码和图文给大家介绍的非常详细,需要的朋友可以参考下
    2024-03-03

最新评论