前言
在开发分布式高并发系统时有三把利器用来保护系统:缓存、降级、限流。
缓存
降级
降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开
限流
限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理
本文主要讲的是api接口限流相关内容,虽然不是论述高并发概念中的限流, 不过道理都差不多。通过限流可以让系统维持在一个相对稳定的状态,为更多的客户提供服务。
API 接口的限流主要应用场景有:
1、 电商系统(特别是6.18、双11等)中的秒杀活动,使用限流防止使用软件恶意刷单;
2、 各种基础api接口限流:例如天气信息获取,IP对应城市接口,百度、腾讯等对外提供的基础接口,都是通过限流来实现免费与付费直接的转换。
3、 被各种系统广泛调用的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*/@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();// 配置连接工厂template.setConnectionFactory(factory);// 设置key的序列化器template.setKeySerializer(new StringRedisSerializer());// 设置value的序列化器//使用Jackson 2,将对象序列化为JSONJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//json转对象类,不设置默认的会将json转成hashmapObjectMapper 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编写拦截器;
限流的思路
1、 通过路径:ip的作为key,访问次数为value的方式对某一用户的某一请求进行唯一标识
2、 每次访问的时候判断key是否存在,是否count超过了限制的访问次数
3、 若访问超出限制,则应response返回msg:请求过于频繁给前端予以展示
/*** @Description: 访问拦截器* @Author oyc*/@Componentpublic class AccessLimitInterceptor implements HandlerInterceptor {private final Logger logger = LoggerFactory.getLogger(this.getClass());@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic 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();// 存储keyString 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*/@Configurationpublic class IntercepterConfig implements WebMvcConfigurer {@Autowiredprivate AccessLimitInterceptor accessLimitInterceptor;@Overridepublic 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
