zuul限流
限流算法
漏桶: leakey bucket,原理:桶的下方的小孔会以一个相对恒定的速率漏水,而不管入桶的水流量,这样就达到了控制出水口的流量
令牌桶: token bucket,原理:以相对恒定的速率向桶中加入令牌,请求来时于桶中取令牌,取到了就放行,没能取到令牌的请求则丢弃
限流粒度
粗粒度
网关限流
单个服务
细粒度
user: 认证用户或者匿名,针对某个用户粒度进行限流
origin: 客户机的IP,针对请求客户机的IP进行限流
url: 特定url,针对请求的url粒度进行限流
serviceId: 特定服务,针对某个服务的id粒度进行限流
实现
从单个服务的角度实现限流,原理:利用redis键过期的自动删除的特性。以url为key,如果key不存在,创建key,并设置键过期时间,相同请求过来就对这个key进行计数,使用redis.incr原子方法,当请求超过limit时,则不让请求api。
1.定义注解:br/>@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)br/>@Documented
public @interface RateLimit {
//每秒请求
int value() default 10;
}
2.定义切面br/>@Aspect
@Componentbr/>@Slf4j
public class RateLimitAspect {
@Autowired
private RedisTemplate redisTemplate;
@Pointcut(value = "@annotation(com.pinlor.gml.cloudrateservice.annotation.RateLimit)")
public void pointcut(){
}
@Around(value = "pointcut()")
@ResponseBody
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.debug("限速检测");
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
RateLimit rate = method.getAnnotation(RateLimit.class);
int limit = rate.value();
HttpServletRequest request = RequestUtil.getRequest();
String key = request.getRequestURI();
//最好以下部分用redis分布锁加锁一下
Object object = redisTemplate.opsForValue().get(key);
if (object == null){
redisTemplate.opsForValue().increment(key);
redisTemplate.expire(key, 1, TimeUnit.SECONDS);
}else {
System.out.println("当前记数: "+ redisTemplate.opsForValue().get(key));
if (redisTemplate.opsForValue().increment(key) > limit) {
System.out.println("服务器繁忙,请稍后再试!!");
return "服务器繁忙,请稍后再试!!";
}
}
return joinPoint.proceed();
}
}
3.redis配置:br/>@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
// 默认用的是用JdkSerializationRedisSerializer进行序列化的
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 注入数据源
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer(Object.class);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// key-value结构序列化数据结构
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// hash数据结构序列化方式,必须这样否则存hash 就是基于jdk序列化的
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
// 启用默认序列化方式
redisTemplate.setEnableDefaultSerializer(true);
redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
/// redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
4.api服务:br/>@RestController
@RequestMapping(value = "/rc")
public class RateController {
@Value(value = "${server.port}")
private int port;
@RateLimit(value = 10)
@GetMapping("/getRate")
public String getRate(){
System.out.println("rate from: "+ port);
return "rate from: "+ port;
}
}
5.工具类:
public class RequestUtil {
public static HttpServletRequest getRequest(){
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null){
return null;
}
return ((ServletRequestAttributes)requestAttributes).getRequest();
}
}
6.maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
原文地址:https://blog.51cto.com/youling87/2486771