Spring Boot + Redis 实现 API 接口防刷限流!
推荐关注
扫码关注“后端架构师”,选择“星标”公众号
重磅干货,第一时间送达!
责编:架构君 | 来源:blog.csdn.net/u014553029/article/details/109232225
责编:架构君 | 来源:blog.csdn.net/u014553029/article/details/109232225
上一篇好文:Java + lua = 王炸!!!
大家好,我是后端架构师。
前言
在开发分布式高并发系统时有三把利器用来保护系统:缓存、降级、限流。
缓存
缓存的目的是提升系统访问速度和增大系统处理容量
降级
降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开
限流
限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理
本文主要讲的是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 */@Configuration@EnableCachingpublic 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,如下所示:
@Autowiredprivate RedisTemplate<String, Object> redisTemplate;
二、实现限流
2.1 添加自定义AccessLimit注解
使用注解方式实现接口的限流操作,方便而优雅。
/** * @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 编写拦截器
限流的思路
通过路径:ip的作为key,访问次数为value的方式对某一用户的某一请求进行唯一标识 另外,搜索公众号Linux就该这样学后台回复“猴子”,获取一份惊喜礼包。
每次访问的时候判断key是否存在,是否count超过了限制的访问次数 若访问超出限制,则应response返回msg:请求过于频繁给前端予以展示
/** * @Description: 访问拦截器 * @Author oyc */@Componentpublic 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 下午 */@Configurationpublic 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 */@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 测试
源码传送门:
https://github.com/oycyqr/springboot-learning-demo/tree/master/springboot-validated
上一篇好文:Java + lua = 王炸!!!
大家好,我是后端架构师。
前言
在开发分布式高并发系统时有三把利器用来保护系统:缓存、降级、限流。
缓存
缓存的目的是提升系统访问速度和增大系统处理容量
降级
降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开
限流
限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理
本文主要讲的是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 */@Configuration@EnableCachingpublic 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,如下所示:
@Autowiredprivate RedisTemplate<String, Object> redisTemplate;
二、实现限流
2.1 添加自定义AccessLimit注解
使用注解方式实现接口的限流操作,方便而优雅。
/** * @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 编写拦截器
限流的思路
通过路径:ip的作为key,访问次数为value的方式对某一用户的某一请求进行唯一标识 另外,搜索公众号Linux就该这样学后台回复“猴子”,获取一份惊喜礼包。 每次访问的时候判断key是否存在,是否count超过了限制的访问次数 若访问超出限制,则应response返回msg:请求过于频繁给前端予以展示
/** * @Description: 访问拦截器 * @Author oyc */@Componentpublic 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 下午 */@Configurationpublic 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 */@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 测试
源码传送门:
https://github.com/oycyqr/springboot-learning-demo/tree/master/springboot-validated
欢迎有需要的同学试试,如果本文对您有帮助,也请帮忙点个 赞 + 在看 啦!❤️
在 GitHub猿 还有更多优质项目系统学习资源,欢迎分享给其他同学吧!
PS:如果觉得我的分享不错,欢迎大家随手点赞、转发、在看。
最后给读者整理了一份BAT大厂面试真题,需要的可扫码加微信备注:“面试”获取。
欢迎有需要的同学试试,如果本文对您有帮助,也请帮忙点个 赞 + 在看 啦!❤️
在 GitHub猿 还有更多优质项目系统学习资源,欢迎分享给其他同学吧!
PS:如果觉得我的分享不错,欢迎大家随手点赞、转发、在看。
最后给读者整理了一份BAT大厂面试真题,需要的可扫码加微信备注:“面试”获取。
版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!
版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!
END
最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。在这里,我为大家准备了一份2021年最新最全BAT等大厂Java面试经验总结。
别找了,想获取史上最全的Java大厂面试题学习资料
扫下方二维码回复「面试」就好了
历史好文:
基于SpringBoot 的CMS系统,拿去开发企业官网真香
扫码关注“后端架构师”,选择“星标”公众号
别找了,想获取史上最全的Java大厂面试题学习资料
扫下方二维码回复「面试」就好了
历史好文:
基于SpringBoot 的CMS系统,拿去开发企业官网真香
扫码关注“后端架构师”,选择“星标”公众号