Zuul-RateLimiter source code analysis

Based on spring-cloud-zuul-ratelimit-core-2.4.0.RELEASE.jar.

1 Introduction

As a gateway, current limiting is naturally one of the basic functions.

This article tries to explain how to realize the current limiting requirement based on the zuul1.x version.

2. SpringBoot related

This section mainly introduces the AutoConfig provided for Zuul-RateLimiter under the SpringBoot framework, as well as actuator-related functions.

2.1 Function of AutoConfigRateLimitAutoConfiguration

The automatic configuration function of Zuul-RateLimiter is RateLimitAutoConfigurationcompleted by .

RateLimitAutoConfigurationThere are mainly three types of beans registered in:

  1. Two Filters for implementing current limiting logic - RateLimitPreFilterand RateLimitPostFilter. We will discuss this specifically in the next subsection.
  2. A cache class bean used to store current-limited state data. Zuul-RateLimiter provides Redis, JPA and other methods.
  3. Some other auxiliary beans. For example, the implementation class responsible for caching Key generation RateLimitKeyGenerator, etc.

2.2 actuator function

This function is actually provided by the zuul body spring-cloud-netflix-core-2.2.5.RELEASE.jar . For the completeness of this article, it is added here.

The key type is RoutesEndpoint, and there are three restful interfaces finally provided:

URL illustrate Remark
/routes Get the successfully registered routing rule set in zuul. GET requests.
/routes/details Verbose version of the /routes method above. GET requests.
/routes/reset Can be used to reset registered routing rule sets. ( use with caution ) POST requests . The outside world can RoutesRefreshedEventdynamically update the existing routing rules at runtime through the response.

3. Core logic processing type: RateLimitPreFilter/RateLimitPostFilter

These two classes are the key to the implementation of current limiting logic.

3.1 RateLimitPreFilter(core logic)

This class is responsible for the main logic of current limiting logic judgment.

According to the interpretation of the key points of spring-cloud-netflix-zuul source code analysis , as the ZuulFilter implementation class, RateLimitPreFilterthe four base class methods implemented are:

base class method Remark
filterType() The type is "pre", which conforms to the current limiting logic.
filterOrder() The default value is -1. zuul.ratelimit.preFilterOrderIt can be set by external configuration items .
shouldFilter() defined in the base class AbstractRateLimitFilter.
run() Collect data and determine whether the current limit is effective or not. The main logic implementation point. Special interpretation below.
// --------------------------------- RateLimitPreFilter.run()
@Override
public Object run() {
    
    
   final RequestContext ctx = RequestContext.getCurrentContext();
   final HttpServletResponse response = ctx.getResponse();
   final HttpServletRequest request = ctx.getRequest();
   // 数据项Route : 用户自定义的 route规则, 例如projectB路由到 projectB系统
   // 以下配置文件所对应的是 ZuulRoute
   /*
	   # ZuulProperties
	   zuul:  
		 routes:
		   projectB:
			 path: /projectB/**
			 url: http://127.0.0.1:801/projectB/        
   */
   final Route route = route(request);

   // 数据项RateLimitProperties.Policy  代表用户自定义的限流规则
   // policy(route, request) 筛选出匹配当前请求的 限流策略 ———— 对应 RateLimitProperties.Policy 类
   policy(route, request).forEach(policy -> {
    
    
	   Map<String, String> responseHeaders = Maps.newHashMap();
	
	   // 筛选, 生成出key. 
	   // 该key将作为本次请求的唯一标识,  相关类型: MatchType, RateLimitType(这是个枚举, 列举了限流方式, 例如基于url, 基于http-method, 基于用户的等等)
	   final String key = rateLimitKeyGenerator.key(request, route, policy);
	   // 数据项Rate, 代表针对当前请求所计算出的, 实时限流状况, 其中包含: 当前时间窗口内, 针对该请求还能访问多少次; 当前时间窗口内, 针对该请求允许处理总时长还剩余多少等等, 详见本类注释
	   final Rate rate = rateLimiter.consume(policy, key, null);

	   // 根据配置, 写入响应头
	   final Long limit = policy.getLimit();
	   final Long remaining = rate.getRemaining();
	   if (limit != null) {
    
    
		   responseHeaders.put(HEADER_LIMIT, String.valueOf(limit));
		   responseHeaders.put(HEADER_REMAINING, String.valueOf(Math.max(remaining, 0)));
	   }

	   final Duration quota = policy.getQuota();
	   // Quota: 定额;配额;限额;指标;
	   final Long remainingQuota = rate.getRemainingQuota();
	   if (quota != null) {
    
    
		   // 这个写入值. 将在 RateLimitPostFilter.getRequestStartTime() 方法中被使用, 用于统计最终本次请求的处理总耗时
		   request.setAttribute(REQUEST_START_TIME, System.currentTimeMillis());
		   responseHeaders.put(HEADER_QUOTA, String.valueOf(quota.getSeconds()));
		   responseHeaders.put(HEADER_REMAINING_QUOTA, String.valueOf(MILLISECONDS.toSeconds(Math.max(remainingQuota, 0))));
	   }

	   responseHeaders.put(HEADER_RESET, String.valueOf(rate.getReset()));

	   // 是否将限流信息写入到响应头部;  默认为true, 
	   if (properties.isAddResponseHeaders()) {
    
    
		   final String httpHeaderKey = key.replaceAll("[^A-Za-z0-9-.]", "_").replaceAll("__", "_");
		   for (Map.Entry<String, String> headersEntry : responseHeaders.entrySet()) {
    
    
			   String header = VERBOSE.equals(properties.getResponseHeaders()) ? headersEntry.getKey() + "-" + httpHeaderKey : headersEntry.getKey();
			   response.setHeader(header, headersEntry.getValue());
		   }
	   }

	   // 判断限流是否生效, 生效则直接抛出异常......
	   if ((limit != null && remaining < 0) || (quota != null && remainingQuota < 0)) {
    
    
		// 设置响应信息
		   ctx.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
		   ctx.put(RATE_LIMIT_EXCEEDED, "true");
		   ctx.setSendZuulResponse(false);
		
		// 基于Spring Event实现的事件驱动扩展接口. 外界可以通过响应RateLimitExceededEvent来执行自定义逻辑.
		   eventPublisher.publishEvent(new RateLimitExceededEvent(this, policy, rateLimitUtils.getRemoteAddress(request)));

		   throw new RateLimitExceededException();
	   }
   });

   return null;
}

3.2 RateLimitPostFilter

It is much simpler than RateLimitPreFilterimplementing logic . RateLimitPostFilterThe main responsibility is to calculate the Remaining Quota (remaining time quota) and Remaining Limit (remaining current limiting times) of the route corresponding to this visit based on the preset configuration items before the end of the current request, which will be used as the data basis for the next calculation of current limiting.

base class method Remark
filterType() The type is "post".
filterOrder() The default is 990 ( SendResponseFilterexecute on). zuul.ratelimit.postFilterOrderIt can be set by external configuration items .
shouldFilter() Override the base class AbstractRateLimitFiltermethod.
run() A special introduction is given below.
// ========================= RateLimitPostFilter.run()
@Override
public Object run() {
    
    
	RequestContext ctx = RequestContext.getCurrentContext();
	HttpServletRequest request = ctx.getRequest();
	Route route = route(request);
	
	// 对本次请求对应路由的限流策略的实时执行情况, 基于本次请求结果进行更新. 用作下一次本路由限流策略计算的数据基础.
	policy(route, request).forEach(policy -> {
    
    
		long requestTime = System.currentTimeMillis() - getRequestStartTime();
		String key = rateLimitKeyGenerator.key(request, route, policy);
		// RateLimiter实现. 常用的有RedisRateLimiter, ConsulRateLimiter等
		rateLimiter.consume(policy, key, requestTime > 0 ? requestTime : 1);
	});

	return null;
}

Notice:

  1. ZuulServlet / ZuulServletFilterThe logic disclosed in reveals that the "post" zuulFilter will definitely be executed, which realizes the logical closed loop of current limiting judgment.

4. Data items

Some literal confusing types are defined in zuul-rateLimiter, which are specifically listed here.

type illustrate
ZuulProperties.ZuulRoute When the user-defined Route configuration information in the configuration file is read into the memory by SpringBoot, it is carried by this class instance. Therefore, the field attributes defined inside the class correspond to the configuration item zuul.routes in our configuration file one by one.
Route 1. Used in conjunction with RouteLocator, the ZuulProperties.ZuulRoute memory instance generated by reading the configuration file above must not be used directly. We still need to do some processing, and the information carrying after processing is done by Route.
2. Route SimpleRouteLocator.getRoute(ZuulRoute route, String path)Responsible for converting ZuulRoute to Route.
RateLimitProperties.Policy Host the traffic limiting policy configured by the user in the configuration file.
Rate 1. The comments on the class are rich enough.
2. It is used to express the current limit of a request at a certain point in time:
   a. How many times can it be executed within the time window?
   b. How long is the allowed time for the number of executions remaining in the time window?
   c. How long is the time window?

5. Configuration items

List some common configuration items and their meanings.

zuul:
  routes:
    # 以下读取配置到内存, 数据为: ZuulRoute{id='manager', path='/manager/**', serviceId='project3', url='null', stripPrefix=true, retryable=null, sensitiveHeaders=[ ], customSensitiveHeaders=false }
    # 走的构造函数是: ZuulRoute(String text)
    project3: /manager/**
    # 以下读取配置到内存, 数据为: ZuulRoute{id='project2', path='/projectB/M/**', serviceId='null', url='http://127.0.0.1:801/projectB/M?k=r8O8WtvCRhlzRo48W1sqvw==', stripPrefix=true, retryable=null, sensitiveHeaders=[], customSensitiveHeaders=false, }
    project2:
      path: /projectB/M/**
      url: http://127.0.0.1:801/projectB/M?k=r8O8WtvCRhlzRo48W1sqvw==     
    projectB:
      path: /projectB/**
      url: http://127.0.0.1:801/projectB/
  ratelimit:
    enabled: true
    behindProxy: false
    responseHeaders: VERBOSE
    # 上面的Route 和这里的 Police 之间的匹配关系, 是通过  routeId 来关联的; 相关源码参见: AbstractRateLimitFilter.policy(...)    
    policyList:
      test:
        - 
          refreshInterval: 60
          # Request number limit per refresh interval window. (每个窗口期间, 允许访问的次数; 例如这里是每60秒访问3次)
          limit: 3
          # Request time limit per refresh interval window (in seconds). (每个窗口期间, 所有正常响应请求的处理总时长; 例如这里是60秒内, 5次请求的总耗时不能超过1秒)
          # 上面这两个条件, 有一个不满足, 限流即生效
          quota: 1
          breakOnMatch: true
         # 对应 RateLimitProperties.Policy.MatchType 内部类, 其中: 
          #		1. 其中的字段type 对应下面配置的user, origin, url; 类型为: com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitType
          #		2. 其中的字段matcher 对应下面配置的/callPost3, 所以针对下面的user, origin而言, matcher为null, 而这将影响 MatchType.apply(...)中的执行 - 如果为空, 则代表匹配, 即所有的请求都适应本Police
          type:
            - user
            - origin
            - url     
    # 上面的 policyList 未匹配时候, 将全部匹配这个
    defaultPolicyList:
      - 
        refreshInterval: 60
        limit: 3
        quota: 1000
        breakOnMatch: true
        type:
          - user       # 登录用户名进行区分,也包括匿名用户
          - origin     # 客户端IP地址区分
          - url
    keyPrefix: 
    repository: REDIS   # REDIS, CONSUL, JPA, BUCKET4J_JCACHE
    postFilterOrder: 990
    preFilterOrder: -1

6. Extension points

Summarize some extensible interface types provided by zuul-ratelimiter to facilitate possible future customization requirements.

type application location Remark
RateLimitExceededEvent RateLimitPreFilter.run()trigger in Based on Spring Events.
RateLimitExceededException RateLimitPreFilter.run()trigger in Based on global exception handling.
RateLimiterErrorHandler RateLimiterused in each implementation class Based on a custom implementation class.
RoutesRefreshedEvent RoutesEndpointtrigger in Based on Spring Events.

7. Reference

  1. spring-cloud-netflix-zuul source code analysis
  2. The spring cloud gateway is configured through Zuul RateLimit current limiting
  3. GitHub - spring-cloud-zuul-ratelimit
  4. AutoConfiguration of SpringBoot source code analysis

Guess you like

Origin blog.csdn.net/lqzkcx3/article/details/124456704