由于API接口无法控制调用方的行为,因此当遇到瞬时请求量激增时,会导致接口占用过多服务器资源,使得其他请求响应速度降低或是超时,更有甚者可能导致服务器宕机。
限流指对应用服务接口的请求调用次数进行限制,对超过限制次数的请求则进行快速失败或丢弃。
限流可以应对:
1、热点业务带来的高并发请求;
2、客户端异常重试导致的并发请求;
3、恶意攻击请求;
限流算法多种多样,比如常见的:固定窗口计数器、滑动窗口计数器、漏桶、令牌桶等。本章通过Redis 的Lua来实现滑动窗口的计数器算法。
1、Redis lua脚本如下:
local ratelimit_info = redis.pcall('HMGET',KEYS[1],'last_time','current_token') local last_time = ratelimit_info[1] local current_token = tonumber(ratelimit_info[2]) local max_token = tonumber(ARGV[1]) local token_rate = tonumber(ARGV[2]) local current_time = tonumber(ARGV[3]) local reverse_time = token_rate*1000/max_token if current_token == nil then current_token = max_token last_time = current_time else local past_time = current_time-last_time local reverse_token = math.floor(past_time/reverse_time) current_token = current_token+reverse_token last_time = reverse_time*reverse_token+last_time if current_token>max_token then current_token = max_token end end local result = '0' if(current_token>0) then result = '1' current_token = current_token-1 end redis.call('HMSET',KEYS[1],'last_time',last_time,'current_token',current_token) redis.call('pexpire',KEYS[1],math.ceil(reverse_time*(max_token-current_token)+(current_time-last_time))) return result
2、项目中引入spring-data-redis和commons-codec,相关配置请自行google。
3、RedisRateLimitScript类
package com.huatech.support.limit; import org.apache.commons.codec.digest.DigestUtils; import org.springframework.data.redis.core.script.RedisScript; public class RedisRateLimitScript implements RedisScript<String> { private static final String SCRIPT = "local ratelimit_info = redis.pcall('HMGET',KEYS[1],'last_time','current_token') local last_time = ratelimit_info[1] local current_token = tonumber(ratelimit_info[2]) local max_token = tonumber(ARGV[1]) local token_rate = tonumber(ARGV[2]) local current_time = tonumber(ARGV[3]) local reverse_time = token_rate*1000/max_token if current_token == nil then current_token = max_token last_time = current_time else local past_time = current_time-last_time local reverse_token = math.floor(past_time/reverse_time) current_token = current_token+reverse_token last_time = reverse_time*reverse_token+last_time if current_token>max_token then current_token = max_token end end local result = '0' if(current_token>0) then result = '1' current_token = current_token-1 end redis.call('HMSET',KEYS[1],'last_time',last_time,'current_token',current_token) redis.call('pexpire',KEYS[1],math.ceil(reverse_time*(max_token-current_token)+(current_time-last_time))) return result"; @Override public String getSha1() { return DigestUtils.sha1Hex(SCRIPT); } @Override public Class<String> getResultType() { return String.class; } @Override public String getScriptAsString() { return SCRIPT; } }
4、添加RateLimit注解
package com.huatech.support.limit; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RateLimit { /** * 接口标识 * @return */ String value() default ""; /** * 周期:多久为一个周期,单位s * @return */ int period() default 1; /** * 周期速率 * @return */ int rate() default 100; /** * 限制类型,默认按接口限制 * @return */ LimitType limitType() default LimitType.GLOBAL; /** * 超限后处理方式,默认拒绝访问 * @return */ LimitedMethod method() default LimitedMethod.ACCESS_DENIED; }
基于Redis的分布式服务限流有两种落地方案:
一种是基于aop的切面实现,另一种是基于interceptor的拦截器实现,下面分别做介绍。
方案一:基于aspject的aop实现方案
1、添加LimitAspect类
package com.huatech.common.aop; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.util.WebUtils; import com.alibaba.fastjson.JSONObject; import com.huatech.common.constant.Constants; import com.huatech.common.util.IpUtil; import com.huatech.support.limit.RateLimit; import com.huatech.support.limit.RedisRateLimitScript; @Aspect @Component public class LimitAspect { private static final Logger LOGGER = LoggerFactory.getLogger(LimitAspect.class); @Autowired private StringRedisTemplate redisTemplate; @Around("execution(* com.huatech.core.controller..*(..) ) && @annotation(com.huatech.support.limit.RateLimit)") public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable{ MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); RateLimit rateLimit = method.getAnnotation(RateLimit.class); if(rateLimit != null) { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); HttpServletResponse response = requestAttributes.getResponse(); Class<?> targetClass = method.getDeclaringClass(); List<String> keyList = new ArrayList<>(1); String key = rateLimit.value(); if(StringUtils.isBlank(key)){ key = targetClass.getName() + "-" + method.getName(); } switch (rateLimit.limitType()) { case IP: String ip = IpUtil.getRemortIP(request); key = ip + "-" + key; break; case USER: String userId = WebUtils.getSessionAttribute(request, Constants.SESSION_USER_ID).toString(); key = userId + "-" + key; default: break; } keyList.add(key); long timer = System.currentTimeMillis(); boolean pass = "1".equals(redisTemplate.execute(new RedisRateLimitScript(), keyList, Integer.toString(rateLimit.rate()), Integer.toString(rateLimit.period()), Long.toString(timer))); if(pass){ return joinPoint.proceed(); }else{ LOGGER.warn("接口key:{}, 周期:{}, 频率:{}", key, rateLimit.period(), rateLimit.rate()); Map<String, Object> result = new HashMap<>(); result.put("code", "400"); result.put("msg", "访问超过次数限制!"); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().print(JSONObject.toJSON(result)); return null; } }else{ return joinPoint.proceed(); } } }
2、在spring-mvc配置文件中开启自定义注解
<aop:aspectj-autoproxy/>
3、开启LimitAspect类的自动扫描操作,或者在spring配置文件中配置bean
<context:component-scan base-package="com.huatech.common.aop,com.huatech.core.controller"/>
方式二:基于interceptor的拦截器实现方案
1、添加RateLimitInterceptor类
public class RateLimitInterceptor extends HandlerInterceptorAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(RateLimitInterceptor.class); @Autowired StringRedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod method = (HandlerMethod) handler; final RateLimit rateLimit = method.getMethodAnnotation(RateLimit.class); if (rateLimit != null) { // 令牌名称 List<String> keyList = new ArrayList<>(1); String key = rateLimit.value(); if(StringUtils.isBlank(key)){ key = method.getClass().getName() + "-" + method.getMethod().getName(); } switch (rateLimit.limitType()) { case IP: String ip = IpUtil.getRemortIP(request); key = ip + "-" + key; break; case USER: String userId = WebUtils.getSessionAttribute(request, Constants.SESSION_USER_ID).toString(); key = "uid:" + userId + "-" + key; default: break; } keyList.add(key); long timer = System.currentTimeMillis(); boolean pass = "1".equals(redisTemplate.execute(new RedisRateLimitScript(), keyList, Integer.toString(rateLimit.rate()), Integer.toString(rateLimit.period()), Long.toString(timer))); if(pass){ return true; }else{ LOGGER.warn("接口key:{}, 周期:{}, 频率:{}", key, rateLimit.period(), rateLimit.rate()); Map<String, Object> result = new HashMap<>(); result.put("code", "400"); result.put("msg", "访问超过次数限制!"); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().print(JSONObject.toJSON(result)); return false; } } } return true; } }
2、在spring-mvc配置文件中配置拦截器
<!-- 拦截器配置 --> <mvc:interceptors> <!-- 其他拦截器配置 --> **** <!-- 限速拦截器配置 --> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.huatech.common.interceptor.RateLimitInterceptor"/> </mvc:interceptor> </mvc:interceptors>
使用@RateLimit
在controller类的方法头上添加RateLimit注解
/** * 服务端ping地址 * @param request * @param response * @throws Exception */ @RequestMapping(value = "/api/app/open/ping.htm") @RateLimit(value="ping", period=5, rate=5) public void ping(HttpServletRequest request, HttpServletResponse response) throws Exception { Map<String, Object> data = new HashMap<String, Object>(); data.put("time", System.currentTimeMillis()); ServletUtils.successData(response,data); }
另外两个枚举类
package com.huatech.support.limit; /** * 超限处理方式 * @author lh@erongdu.com * @since 2019年8月28日 * @version 1.0 * */ public enum LimitedMethod { /** * 拒绝访问(直接拒绝访问,不预警) */ ACCESS_DENIED, /** * 预警短信(发送预警短信,但不拒绝访问) */ WARN_SMS, /** * 拒绝访问并预警 */ DENIED_AND_SMS ; }
package com.huatech.support.limit; /** * 接口限制类型 * @author lh@erongdu.com * @since 2019年8月29日 * @version 1.0 * */ public enum LimitType { /** * 整个接口限制 */ GLOBAL("接口"), /** * ip层面限制 */ IP("ip"), /** * 用户层面限制 */ USER("用户"); public String value; private LimitType(String value) { this.value = value; } }
IpUtil工具类
package com.huatech.common.util; import java.net.InetAddress; import java.net.UnknownHostException; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author lh@erongdu.com * @since 2019年8月29日 * @version 1.0 * */ public class IpUtil { public static final Logger logger = LoggerFactory.getLogger(IpUtil.class); /** * 获取请求IP * @param request * @return */ public static String getRemortIP(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Real-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } //这里主要是获取本机的ip,可有可无 if ("127.0.0.1".equals(ip) || ip.endsWith("0:0:0:0:0:0:1")) { // 根据网卡取本机配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { logger.error(e.getMessage(), e); } if(inet != null){ ip = inet.getHostAddress(); } return ip; } if(ip.length() > 0){ String[] ipArray = ip.split(","); if (ipArray != null && ipArray.length > 1) { return ipArray[0]; } return ip; } return ""; } }
相关推荐
总的来说,基于Redis的分布式限流方案具有以下优点: 1. 高性能:Redis作为内存数据库,读写速度快,能够快速响应限流决策。 2. 可扩展性:在分布式环境中,多个应用实例可以共享同一套限流规则,通过Redis进行协调...
总之,基于Redis的分布式限频模块在Go语言中是一个实用的工具,它提供了一种简单的方法来控制服务的访问速率,从而保护系统免受过高负载的影响。通过合理地设计和使用这样的限频策略,开发者可以有效地提升系统的...
【分布式环境下限流方案的实现】在现代高并发的分布式系统中,限流是一个至关重要的技术,用于防止系统被过量的请求淹没,确保服务的稳定性和可用性。本篇文章主要探讨了在分布式环境中,如何利用Redis、Guava的...
此外,为了提高限流系统的可扩展性和灵活性,可以考虑引入分布式限流方案,例如使用Redis集群,通过分布式锁或分布式计数器来协调不同节点间的限流策略。还可以结合服务发现机制动态调整限流策略,根据服务实例的...
在IT行业中,限流是一种常见的系统保护策略,用于限制在特定时间...通过学习这个项目,我们可以了解如何利用Redis的数据结构和原子操作来构建高效、可扩展的限流解决方案,这对于构建稳定、高可用的后端服务至关重要。
本文将深入探讨基于分布式配置中心配置限流参数的Redis轻量级分布式限流组件——lightweight-rate-limiter。该组件旨在帮助开发者实现高效、灵活的限流策略,确保服务的稳定性和性能。 一、限流概念与重要性 限流...
### 基于RateLimiter和Lua脚本的分布式限流技术详解 #### 一、引言 在高并发场景下,为了保护后端服务不被突发流量冲击导致崩溃,通常会采用限流策略来控制进入系统的请求数量。限流机制能够有效地分配资源,避免...
FAT ,基于springboot , 使用zookeeper,redis , spring async , spring transactionManager的强一致性分布式事务解决方案 ## 框架介绍 纯编码方式,强一致性。 使用redis/zookeeper作为注册中心 ,代理事务的执行...
在这个“基于redis限流系统.zip”压缩包中,我们可以预见到包含了一个使用Redis和Lua脚本实现的限流解决方案,以及可能的设计文档或源代码。 首先,我们来看看限流的基本概念。限流的主要目标是避免系统过载,保护...
综上所述,这个项目提供了一套便捷的Spring Boot集成Redis分布式锁的解决方案,能够帮助开发者快速实现多节点环境下的并发控制,提高系统的稳定性和可靠性。通过学习和实践,你可以深入理解分布式锁的设计与实现,...
2. 限流和计数:利用Redis的原子操作,可以实现如访问频率限制、消息队列等功能。 3. 分布式锁:Redis的`SETNX`和`EXPIRE`命令可以实现简单的分布式锁,解决并发问题。 4. Session共享:在Web应用中,可以将用户...
在分布式系统中,限流是一种重要的策略,用于保护服务免受过高的请求负载,避免系统崩溃。...通过这种方式,我们可以构建一个高效、灵活且易于扩展的分布式限流解决方案,有效保障服务的稳定性和性能。
* 集群限流:在分布式系统中,需要限制某个资源的访问频次,以避免过载和崩溃。 * 分布式事务:在分布式系统中,需要确保多个节点之间的数据一致性。 * 分布式配置:在分布式系统中,需要在多个节点之间同步配置信息...
本文主要介绍了springboot+aop+Lua分布式限流的最佳实践,涵盖了限流的定义、限流的重要性、限流方案等多个方面。 一、什么是限流?为什么要限流? 限流是指限制系统处理请求的速度,以避免系统崩溃和超载。限流是...
Redis 分布式锁是分布式系统中用于解决并发问题的重要工具,尤其在高并发场景下,如秒杀活动、限流控制等。它基于单机的 Redis 数据存储来实现跨节点的锁服务。以下是对Redis分布式锁及其可能出现的问题的详细解释:...
本章重点讨论了流量管制的基本原理和技术实现方式,包括但不限于常见的限流算法(如令牌桶算法、漏桶算法)、接入层和应用层的限流方案。 **2. 基于时间分片的削峰方案** 除了传统的限流技术之外,文章还提出了一...
7. **分布式限流**:在多实例的微服务环境中,限流策略需要考虑分布式场景。可以使用Redis的分布式锁或者Spring Cloud Gateway的RateLimiter组件实现跨服务的限流。 通过以上步骤,我们能够构建一个基于Spring Boot...
微服务架构下的分布式限流方案全解析,是针对现代服务架构中确保系统稳定性和抗压能力的重要技术。在微服务环境中,服务间的依赖性增强,系统复杂性也随之增加,因此,缓存、降级和限流成为保障系统稳定运行的三大...
分布式系统在处理高并发场景时,常常...以上就是基于分布式Redis和消息中间件实现高并发秒杀方案的基本原理和可能的源码结构。实际应用中,还需要结合具体业务需求和系统架构进行调整优化,确保系统的高可用性和性能。
通过上述分析可以看出,基于Redis的共享缓存方案不仅能够有效解决微服务架构下的数据共享问题,还能够提升系统的性能和可靠性。随着微服务架构的不断发展和完善,Redis将在服务间数据共享领域发挥更加重要的作用。...