限流的目的,是在系统流量过大的时候,对系统进行保护,避免因为流量过大,导致系统不稳定,甚至出现故障。

云原生环境下的限流方案比较多。 轻量级的方案可以使用 Bucket4j + Hazelcast/ignite/infinispan 的内存数据结合的方案。 完整的方案可以使用 Sentinel 集群。

1.Sentinel

使用 sentinel 进行限流,可以单机限流,也可以多机集群限流。

GitHub - alibaba/Sentinel: A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件)

优点:可以灵活进行控制。 有集群的方案。 

缺点:需要写代码,侵入性较高。

Sentinel 的 规则在 sentinel 集群中配置。 集群配置参考:

集群流控 · alibaba/Sentinel Wiki · GitHub

限流需要结合每一个服务的实例数来做限流, 因此可以监听 k8s 的 pod 状态变化来更新限流策略。  监听 k8s 集群:

java/WatchExample.java at master · kubernetes-client/java · GitHub

Using Watch with the Kubernetes API | Baeldung

How to watch events on a kubernetes service using its go client - Stack Overflowj

监听 k8s 的实例代码:

import com.google.gson.reflect.TypeToken;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1Namespace;
import io.kubernetes.client.util.Config;
import io.kubernetes.client.util.Watch;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;

/** A simple example of how to use Watch API to watch changes in Namespace list. */
public class WatchExample {
  public static void main(String[] args) throws IOException, ApiException {
    ApiClient client = Config.defaultClient();
    // infinite timeout
    OkHttpClient httpClient =
        client.getHttpClient().newBuilder().readTimeout(0, TimeUnit.SECONDS).build();
    client.setHttpClient(httpClient);
    Configuration.setDefaultApiClient(client);

    CoreV1Api api = new CoreV1Api();

    Watch<V1Namespace> watch =
        Watch.createWatch(
            client,
            api.listNamespaceCall(
                null, null, null, null, null, 5, null, null, null, Boolean.TRUE, null),
            new TypeToken<Watch.Response<V1Namespace>>() {}.getType());

    try {
      for (Watch.Response<V1Namespace> item : watch) {
        System.out.printf("%s : %s%n", item.type, item.object.getMetadata().getName());
      }
    } finally {
      watch.close();
    }
  }
}

2.Bucket4j 对 API 进行限流

基本使用

Rate Limiting a Spring API Using Bucket4j | Baeldung

How to set rate limit for each user in Spring Boot?

https://youssefelyamani.medium.com/secure-your-spring-boot-api-with-rate-limit-20d868148fd

<dependency>
    <groupId>com.github.vladimir-bukhtoyarov</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>4.10.0</version>
</dependency>

示例

@RestController
class AreaCalculationController {

    private final Bucket bucket;

    public AreaCalculationController() {
        Bandwidth limit = Bandwidth.classic(20, Refill.greedy(20, Duration.ofMinutes(1)));
        this.bucket = Bucket4j.builder()
            .addLimit(limit)
            .build();
    }
    //..
}

@PostMapping(value = "/api/v1/area/rectangle")
public ResponseEntity<AreaV1> rectangle(@RequestBody RectangleDimensionsV1 dimensions) {
    if (bucket.tryConsume(1)) {
        return ResponseEntity.ok(new AreaV1("rectangle", dimensions.getLength() * dimensions.getWidth()));
    }

    return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build();
}

测试验证限流的效果

$ curl -X POST http://localhost:9001/api/v1/area/rectangle \
    -H "Content-Type: application/json" \
    -d '{ "length": 10, "width": 12 }'

{ "shape":"rectangle","area":120.0 }

优点:

提供了 RateLimiter, 以及集群限流的方案。

缺点:需要 Hazelcast/ignite/infinispan 的内存数据结合来用做集群限流,增加了比较重的外部依赖。

集群限流方案

Bucket4j 7.0.0 Reference

可以使用 Hazelcast, ignite, infinispan 做为分布式内存数据库。

Hazelcast, ignite 各有优劣。Ignite 的性能更好,开放性更好一些。

3.Resilience4j ratelimiter

Resilience4j 使用示例 & 动态调整 limit:

Rate-Limiting with Spring Boot and Resilience4j

RateLimiter

Implementing Rate Limiting with Resilience4j

优点:提供了 RateLimiter, CircuitBreaker, Retry, BulkHead 等不同的稳定性保障方法。

缺点:没有提供集群的方案。

4.使用 Traefik 来控制流量

Rate limiting on Kubernetes applications | Traefik Blog

Kubernetes Ingress - Traefik

RateLimit - Traefik

How to use Traefik reverse proxy

traefik-helm-chart/README.md at master · traefik/traefik-helm-chart · GitHub

优点:能够实时调整限制的阈值。 可以对整个集群限流,且能使用 qps 和 同时并发数进行限流。

缺点:一个入口API 对应到后面多个算法(根据 不同请求条件路由到不同算法服务),路由逻辑不能使用 Traefik 的 Route 规则来表达。

示例,如下设置 /prod 路径的请求平均每秒最大 100 qps, 最大并发量 50。两个参数一起可以精确控制 qps。

定义一个 rate limit 的 middleware。 然后在 IngressRoute 里面引用这个 middleware。

在 集群发生扩缩容的时候, 更新 middleware 的配置即可改变限流的策略。

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: prod-rate-limit
spec:
  rateLimit:
    average: 100
    burst: 50
    
--
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: prod-route
spec:
  entryPoints:
    - web
  routes:
  - match: PathPrefix(`/prod`)
    kind: Rule
    services:
    - name: prod-service
      port: 80
    middlewares:
      - name: prod-rate-limit 

Traefik 除了 提供限流,同时提供了 API的统计和监控 Dashboard,方便观测集群的健康度,可以根据需要开启。

5.使用 istio 限流


Istio / Traffic Management
Istio / Circuit Breaking
Istio / Destination Rule
Istio / Ingress Gateways
Istio / Istio as a Proxy for External Services
可以控制并发连接数,不能准确控制 qps。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
  - name: v1
    labels:
      version: v1
    trafficPolicy:
      connectionPool:
        tcp:
          maxConnections: 100

另外可以使用 Spring Cloud Zuul RateLimit 来做限流, 也可以在 nginx ingress 中也可以控制 qps 流量,灵活性比较小。  参考:

managed - Rate Limiting based on URL and Path in Kubernetes - Stack Overflow

https://medium.com/titansoft-engineering/rate-limiting-for-your-kubernetes-applications-with-nginx-ingress-2e32721f7f57

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐