lua脚本
local key = "rate.limit:" .. KEYS[ 1 ]
local limit = tonumber ( ARGV[ 1 ] )
local current = tonumber ( redis. call ( 'get' , key) or "0" )
if current + 1 > limit then
return 0
else
redis. call ( "INCRBY" , key, "1" )
redis. call ( "expire" , key, "2" )
return current + 1
end
限流注解
@Target ( { ElementType . TYPE, ElementType . METHOD} )
@Retention ( RetentionPolicy . RUNTIME)
public @interface RateLimiter {
String key ( ) default "limit" ;
int time ( ) default 5 ;
int count ( ) default 5 ;
}
处理器
@Aspect
@Configuration
@Slf4j
public class RateLimiterAspect {
@Autowired
private RedisTemplate < String , Serializable > limitRedisTemplate;
@Autowired
private DefaultRedisScript < Long > redisScript;
@Around ( "execution(* com.larch.middleware.redis.controller ..*(..) )" )
public Object interceptor ( ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = ( MethodSignature ) joinPoint. getSignature ( ) ;
Method method = signature. getMethod ( ) ;
Class < ? > targetClass = method. getDeclaringClass ( ) ;
RateLimiter rateLimiter = method. getAnnotation ( RateLimiter . class ) ;
if ( rateLimiter != null ) {
HttpServletRequest request = ( ( ServletRequestAttributes ) RequestContextHolder . getRequestAttributes ( ) ) . getRequest ( ) ;
String ipAddress = getIpAddr ( request) ;
StringBuffer stringBuffer = new StringBuffer ( ) ;
stringBuffer. append ( ipAddress) . append ( "-" )
. append ( targetClass. getName ( ) ) . append ( "-" )
. append ( method. getName ( ) ) . append ( "-" )
. append ( rateLimiter. key ( ) ) ;
List < String > keys = Collections . singletonList ( stringBuffer. toString ( ) ) ;
int count = rateLimiter. count ( ) ;
int time = rateLimiter. time ( ) ;
Long number = limitRedisTemplate. execute ( redisScript, keys, time, count) ;
if ( null != number && number. intValue ( ) != 0 && number. intValue ( ) <= rateLimiter. count ( ) ) {
log. info ( "限流时间段内访问第:{} 次" , number. toString ( ) ) ;
return joinPoint. proceed ( ) ;
}
} else {
return joinPoint. proceed ( ) ;
}
throw new RuntimeException ( "访问过于频繁!" ) ;
}
public static String getIpAddr ( HttpServletRequest request) {
String ipAddress = null ;
try {
ipAddress = request. getHeader ( "x-forwarded-for" ) ;
if ( ipAddress == null || ipAddress. length ( ) == 0 || "unknown" . equalsIgnoreCase ( ipAddress) ) {
ipAddress = request. getHeader ( "Proxy-Client-IP" ) ;
}
if ( ipAddress == null || ipAddress. length ( ) == 0 || "unknown" . equalsIgnoreCase ( ipAddress) ) {
ipAddress = request. getHeader ( "WL-Proxy-Client-IP" ) ;
}
if ( ipAddress == null || ipAddress. length ( ) == 0 || "unknown" . equalsIgnoreCase ( ipAddress) ) {
ipAddress = request. getRemoteAddr ( ) ;
}
if ( ipAddress != null && ipAddress. length ( ) > 15 ) {
if ( ipAddress. indexOf ( "," ) > 0 ) {
ipAddress = ipAddress. substring ( 0 , ipAddress. indexOf ( "," ) ) ;
}
}
} catch ( Exception e) {
ipAddress = "" ;
}
return ipAddress;
}
}
@Component
public class LuaConfiguration {
@Bean
public DefaultRedisScript < Long > redisScript ( ) {
DefaultRedisScript < Long > redisScript = new DefaultRedisScript < > ( ) ;
redisScript. setScriptSource ( new ResourceScriptSource ( new ClassPathResource ( "limit.lua" ) ) ) ;
redisScript. setResultType ( Long . class ) ;
System . out. println ( "限流脚本加载完成" ) ;
return redisScript;
}
}
redis配置
@EnableCaching
@Configuration
@AutoConfigureBefore ( RedisAutoConfiguration . class )
public class RedisConfig {
@Bean
public RedisTemplate < String , Serializable > limitRedisTemplate ( LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate < String , Serializable > template = new RedisTemplate < String , Serializable > ( ) ;
template. setKeySerializer ( new StringRedisSerializer ( ) ) ;
template. setValueSerializer ( new GenericJackson2JsonRedisSerializer ( ) ) ;
template. setConnectionFactory ( redisConnectionFactory) ;
return template;
}
@Bean
@Primary
public RedisTemplate < String , Object > redisTemplate ( RedisConnectionFactory redisConnectionFactory) {
RedisTemplate < String , Object > redisTemplate = new RedisTemplate < > ( ) ;
redisTemplate. setKeySerializer ( new StringRedisSerializer ( ) ) ;
redisTemplate. setHashKeySerializer ( new StringRedisSerializer ( ) ) ;
redisTemplate. setValueSerializer ( new StringRedisSerializer ( ) ) ;
redisTemplate. setHashValueSerializer ( new StringRedisSerializer ( ) ) ;
redisTemplate. setConnectionFactory ( redisConnectionFactory) ;
return redisTemplate;
}
}
spring. redis. host= 127.0 .0 .1
spring. redis. port= 6379
spring. redis. password=
spring. redis. database= 0
# 连接池最大连接数(使用负值表示没有限制)
spring. redis. jedis. pool. max- active= 20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring. redis. jedis. pool. max- wait= - 1
# 连接池中的最大空闲连接
spring. redis. jedis. pool. max- idle= 10
# 连接池中的最小空闲连接
spring. redis. jedis. pool. min- idle= 0
# 连接超时时间(毫秒)
spring. redis. timeout= 2000
测试controller
@RestController
@Slf4j
@RequestMapping ( "limit" )
public class RateLimiterController {
@Resource
private RedisTemplate redisTemplate;
@GetMapping ( value = "/test" )
@RateLimiter ( key = "test" , time = 10 , count = 8 )
public ResponseEntity < Object > test ( ) {
SimpleDateFormat format = new SimpleDateFormat ( "yyyy-MM-dd HH:mm:ss.SSS" ) ;
String date = format. format ( new Date ( ) ) ;
RedisAtomicInteger limitCounter = new RedisAtomicInteger ( "limitCounter" , redisTemplate. getConnectionFactory ( ) ) ;
String str = date + " 累计访问次数:" + limitCounter. getAndIncrement ( ) ;
return ResponseEntity . ok ( str) ;
}
}