高并发访问时,缓存、限流、降级往往是系统的利剑,在互联网蓬勃发展的时期,经常会面临因用户暴涨导致的请求不可用的情况,甚至引发连锁反映导致整个系统崩溃。 这个时候常见的解决方案之一就是限流了,当请求达到一定的并发数或速率,就进行等待、排队、降级、拒绝服务等。
package com. programb. example; import org. springframework. boot. SpringApplication;
import org. springframework. boot. autoconfigure. SpringBootApplication; @SpringBootApplication
public class SpringBootLimitApplication { public static void main ( String[ ] args) { SpringApplication. run ( SpringBootLimitApplication. class , args) ; }
}
package com. programb. example. annotation; 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)
public @interface RateLimit { String key ( ) default "" ; int time ( ) ; int count ( ) ;
}
package com. programb. example. config; import org. springframework. context. annotation. Bean;
import org. springframework. core. io. ClassPathResource;
import org. springframework. data. redis. connection. lettuce. LettuceConnectionFactory; import org. springframework. data. redis. core. RedisTemplate;
import org. springframework. data. redis. core. script. DefaultRedisScript;
import org. springframework. data. redis. serializer. GenericJackson2JsonRedisSerializer;
import org. springframework. data. redis. serializer. StringRedisSerializer;
import org. springframework. scripting. support. ResourceScriptSource;
import org. springframework. stereotype. Component; import java. io. Serializable; @Component
public class Commons { @Bean public DefaultRedisScript< Number> redisluaScript ( ) { DefaultRedisScript< Number> redisScript = new DefaultRedisScript < > ( ) ; redisScript. setScriptSource ( new ResourceScriptSource ( new ClassPathResource ( "rateLimit.lua" ) ) ) ; redisScript. setResultType ( Number. class ) ; return redisScript; } @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; } }
package com. programb. example. config; 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. context. annotation. Configuration;
import org. springframework. data. redis. core. RedisTemplate;
import org. springframework. data. redis. core. script. DefaultRedisScript;
import org. springframework. web. context. request. RequestContextHolder;
import org. springframework. web. context. request. ServletRequestAttributes; import com. programb. example. annotation. RateLimit; import javax. servlet. http. HttpServletRequest;
import java. io. Serializable;
import java. lang. reflect. Method;
import java. util. Collections;
import java. util. List; @Aspect
@Configuration
public class LimitAspect { private static final Logger logger = LoggerFactory. getLogger ( LimitAspect. class ) ; @Autowired private RedisTemplate< String, Serializable> limitRedisTemplate; @Autowired private DefaultRedisScript< Number> redisluaScript; @Around ( "execution(* com.souyunku.example.controller ..*(..) )" ) public Object interceptor ( ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = ( MethodSignature) joinPoint. getSignature ( ) ; Method method = signature. getMethod ( ) ; Class< ? > targetClass = method. getDeclaringClass ( ) ; RateLimit rateLimit = method. getAnnotation ( RateLimit. class ) ; if ( rateLimit != 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 ( rateLimit. key ( ) ) ; List< String> keys = Collections. singletonList ( stringBuffer. toString ( ) ) ; Number number = limitRedisTemplate. execute ( redisluaScript, keys, rateLimit. count ( ) , rateLimit. time ( ) ) ; if ( number != null && number. intValue ( ) != 0 && number. intValue ( ) <= rateLimit. count ( ) ) { logger. 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; }
}
package com. programb. example. controller; import org. apache. commons. lang3. time. DateFormatUtils;
import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. data. redis. core. RedisTemplate;
import org. springframework. data. redis. support. atomic. RedisAtomicInteger;
import org. springframework. data. redis. support. atomic. RedisAtomicLong;
import org. springframework. web. bind. annotation. GetMapping;
import org. springframework. web. bind. annotation. RestController; import com. programb. example. annotation. RateLimit; import java. util. Date; @RestController
public class LimiterController { @Autowired private RedisTemplate redisTemplate; @RateLimit ( key = "test" , time = 10 , count = 10 ) @GetMapping ( "/test" ) public String luaLimiter ( ) { RedisAtomicInteger entityIdCounter = new RedisAtomicInteger ( "entityIdCounter" , redisTemplate. getConnectionFactory ( ) ) ; String date = DateFormatUtils. format ( new Date ( ) , "yyyy-MM-dd HH:mm:ss.SSS" ) ; return date + " 累计访问次数:" + entityIdCounter. getAndIncrement ( ) ; }
}
spring. application. name= spring- boot- limit
spring. redis. database= 0
spring. redis. host= 127.0 .0 .1
spring. redis. port= 6379
spring. redis. password=
spring. redis. jedis. pool. max- active= 8
spring. redis. jedis. pool. max- wait= - 1
spring. redis. jedis. pool. max- idle= 8
spring. redis. jedis. pool. min- idle= 0
spring. redis. timeout= 10000
< ? xml version= "1.0" encoding= "UTF-8" ? >
< project xmlns= "http://maven.apache.org/POM/4.0.0" xmlns: xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi: schemaLocation= "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > < modelVersion> 4.0 .0 < / modelVersion> < groupId> com. programb. example< / groupId> < artifactId> spring- boot- limit< / artifactId> < version> 0.0 .1 - SNAPSHOT< / version> < packaging> jar< / packaging> < name> spring- boot- limit< / name> < description> Demo project for Spring Boot< / description> < parent> < groupId> org. springframework. boot< / groupId> < artifactId> spring- boot- starter- parent< / artifactId> < version> 2.0 .4 . RELEASE< / version> < relativePath/ > < / parent> < properties> < project. build. sourceEncoding> UTF- 8 < / project. build. sourceEncoding> < project. reporting. outputEncoding> UTF- 8 < / project. reporting. outputEncoding> < java. version> 1.8 < / java. version> < / properties> < dependencies> < dependency> < groupId> org. springframework. boot< / groupId> < artifactId> spring- boot- starter- web< / artifactId> < / dependency> < dependency> < groupId> org. springframework. boot< / groupId> < artifactId> spring- boot- starter- data- redis< / artifactId> < / dependency> < dependency> < groupId> org. springframework. boot< / groupId> < artifactId> spring- boot- starter- aop< / artifactId> < / dependency> < dependency> < groupId> org. apache. commons< / groupId> < artifactId> commons- lang3< / artifactId> < / dependency> < dependency> < groupId> org. springframework. boot< / groupId> < artifactId> spring- boot- starter- test< / artifactId> < / dependency> < / dependencies> < build> < plugins> < plugin> < groupId> org. springframework. boot< / groupId> < artifactId> spring- boot- maven- plugin< / artifactId> < / plugin> < / plugins> < / build> < / project>