Kitex xDS 功能增强:熔断、限流和重试

Kitex 的 xDS 模块已经支持了以 Proxyless 模式对接 Istio,支持服务发现、服务路由和超时配置等功能,过往也有文章对此进行了介绍:Kitex 新增功能特性:支持 xDS 对接 Istio。Kitex 除了上述功能外,还支持熔断、限流、重试等治理策略,在 xDS 模块最新的 0.4.1 版本也对这些特性做了针对 Istio 的适配。

GitHub 地址: github.com/kitex-contr...

官网文档地址:www.cloudwego.io/zh/docs/kit...

xDS 对接

首先来简单回顾一下接入 xDS 的步骤:

  1. xDS 模块初始化,调用 xds.Init() 便可开启对 xDS 模块的初始化,其中包括 xdsResourceManager 负责 xDS 资源的管理。xdsClient 负责与控制面进行交互。
  2. Kite Client/Server 的 Option 配置
  • 熔断和重试是针对 Kitex Client 的配置,需要在构造 Kitex Client 添加上配置所需要的 Middleware。
less 复制代码
greetservice.NewClient(
    "kitex-server:6789", // 服务名称
    client.WithSuite(xdssuite.NewClientSuite()), // 包含了 xDS client 端所有的配置
)
  • 限流针对 Kitex Server 生效,在构造 Kitex Server 时添加限流 Middleware。
less 复制代码
greetservice.NewServer(
    &GreetServiceImpl{},
    xdssuite.NewLimiter(), //  限流配置
)

BootStrap

xdsClient 负责与控制面(例如 Istio)交互,以获得所需的 xDS 资源。在初始化时,需要读取环境变量用于构建 node 标识。所以,需要在 K8S 的容器配置文件 spec.containers.env 部分加入以下几个环境变量。

  • POD_NAMESPACE:当前 pod 所在的 namespace。
  • POD_NAME:pod 名。
  • INSTANCE_IP: pod 的 ip。
  • KITEX_XDS_METAS: 和 istiod 建立链接的元信息

在需要使用 xDS 功能的容器配置中加入以下定义即可:

yaml 复制代码
- name: POD_NAME
  valueFrom:
    fieldRef:
      apiVersion: v1
      fieldPath: metadata.name
- name: POD_NAMESPACE
  valueFrom:
    fieldRef:
      apiVersion: v1
      fieldPath: metadata.namespace
- name: INSTANCE_IP
  valueFrom:
    fieldRef:
      apiVersion: v1
      fieldPath: status.podIP
- name: KITEX_XDS_METAS
  value: '{"CLUSTER_ID":"Kubernetes","DNS_AUTO_ALLOCATE":"true","DNS_CAPTURE":"true","INSTANCE_IPS":"$(INSTANCE_IP)","ISTIO_VERSION":"1.13.5","NAMESPACE":"$(POD_NAMESPACE)"}'

治理策略配置

熔断

熔断器的主要作用是在下游服务大量请求出错的情况下,熔断上游服务对下游服务的请求以防止故障蔓延,保护系统的整体稳定性。

现在 Kitex 仅支持错误率熔断,Istio 的 DestinationRule 只支持根据连续错误数量进行熔断,所以需要使用 Envoyfilter 的配置下发,xDS 的配置参考 www.envoyproxy.io/docs/envoy/... 配置参考 www.cloudwego.io/zh/docs/kit... 支持服务级别和实例级别的熔断,xDS 模块默认使用实例级别熔断,如果要切换到服务级别使用 xdssuite.WithServiceCircuitBreak(true)方法进行切换。

下面的例子表示对 default 命名空间下所有访问 hello 的 client 生效的熔断配置,现在只支持针对服务、客户端的配置,不支持以方法的维度进行熔断,参数介绍:

  • spec.configPatches[0].match.cluster.service:表示访问的服务,需要遵循 Kuberntes 的 FQDN 格式。
  • failure_percentage_threshold:触发熔断阈值,当错误率达到该值时进行熔断。
  • failure_percentage_request_volume:触发熔断的最小请求量,当总请求量小于该值时不会触发熔断。
yaml 复制代码
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: circuitbreak-client
  namespace: default
spec:
  configPatches:
  - applyTo: CLUSTER
    match:
      context: SIDECAR_OUTBOUND
      cluster:
        service: hello.default.svc.cluster.local
    patch:
      operation: MERGE
      value:
        outlier_detection:
          failure_percentage_threshold: 10
          failure_percentage_request_volume: 100
  workloadSelector:
    labels:
      # the label of the client pod.
      app.kubernetes.io/name: kitex-client

重试

Kitex 重试的规则比较复杂,参考:www.cloudwego.io/zh/docs/kit...

  • max_retry_times: 重试次数
  • max_duration_ms: 最大超时时间,如果请求耗时超过这个时间不会进行重试,以免整体耗时过大
  • error_rate:错误率,如果错误率超过该值,不再进行重试,在错误率过大的情况下进行重试没有实际意义,而且还会扩大 QPS,对服务器造成更大的压力。取值范围为(0, 0.3],如果不在该有效范围内使用默认值 0.1。
  • backoff_policy: 重试间隔策略,支持类型为 fixedrandomnone,cfg_items 根据实际类型配置 fix_msmin_msmax_ms等内容。
json 复制代码
{
    "enable": true,
    "failure_policy": {
        "retry_same_node": false,
        "stop_policy": {
            "max_retry_times": 2,
            "max_duration_ms": 300,
            "cb_policy": {
                "error_rate": 0.2
            }
        },
        "backoff_policy": {
            "backoff_type": "fixed",
            "cfg_items": {
                "fix_ms": 100
            }
        }
    }
}

Istio 的 VirtualService 支持配置重试规则,但是该规则配置相对比较简单,只支持重试次数以及重试超时时间,不建议生产使用,内容如下:

yaml 复制代码
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: retry-client
  namespace: default
spec:
  hosts:
  - hello.prod.svc.cluster.local:21001
  http:
  - route:
    - destination:
        host: hello.prod.svc.cluster.local:21001
    retries:
      attempts: 1
      perTryTimeout: 2s

Envoy 自身 xDS 配置相对比较丰富,可以和 Kitex 的配置较好的搭配,参考:www.envoyproxy.io/docs/envoy/... Envoyfilter 配置重试规则,这里说明下两者之间的映射关系:

  • numRetries: 对应 max_retry_times。
  • perTryTimeout:单个请求超时,乘以重试次数为 max_duration_ms。
  • retryBackOff: 对应 backoff_policy,会根据 baseInterval 和 maxInterval 两者的大小关系自动设置 backoff_policy 的类型。
  • retriableHeaders:由于两者配置存在一定的差异,这里使用 kitexRetryErrorRate 映射 Kitex 的 error_rate;kitexRetryMethods 匹配具体需要执行重试策略的方法,多个方法之间使用逗号隔开,建议只针对幂等方法配置重试策略。
  • workloadSelector:针对生效的 pod 客户端,如果不填会对该命名空间下的客户端生效。
  • routeConfiguration:name 对应需要生效的服务名称,需要遵循 FQDN 规则,如果不填则对所有的服务生效。
yaml 复制代码
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: retry-policy
  namespace: default
spec:
  configPatches:
  - applyTo: HTTP_ROUTE
    match:
      context: SIDECAR_OUTBOUND
      routeConfiguration:
        name: hello.default.svc.cluster.local:21001
        vhost: 
          name: hello.default.svc.cluster.local:21001
    patch:
      operation: MERGE
      value:
        route:
          retryPolicy:
            numRetries: 3
            perTryTimeout: 100ms
            retryBackOff:
              baseInterval: 100ms
              maxInterval: 100ms
            retriableHeaders:
              - name: "kitexRetryErrorRate"
                stringMatch:
                  exact: "0.29"
              - name: "kitexRetryMethods"
                stringMatch:
                  exact: "Echo,Greet"
  workloadSelector:
    labels:
      # the label of the service pod.
      app.kubernetes.io/name: kitex-client

限流

限流需要使用 Envoyfilter 来配置,xDS 配置参考: www.envoyproxy.io/docs/envoy/... 限流器参考 www.cloudwego.io/zh/docs/kit... tokens_per_fill 字段表示每秒最大的请求数量,超出的请求将会被拒绝。Kitex 的 QPS 限流算法采用了令牌桶算法,每隔 100ms 往令牌桶添加 tokens_per_fill/10 的数量,所以建议该值的配置为 10 的整数。

yaml 复制代码
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: ratelimit-client
  namespace: default
spec:
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: SIDECAR_INBOUND
        listener:
          filterChain:
            filter:
              name: "envoy.filters.network.http_connection_manager"
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.filters.http.local_ratelimit
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
            stat_prefix: http_local_rate_limiter
            token_bucket:
              # the qps limit
              tokens_per_fill: 100
  workloadSelector:
    labels:
      # the label of the server pod.
      app.kubernetes.io/name: kitex-server

完整用例

Client 代码

go 复制代码
package main

import (
        "context"
        "os"

        "github.com/cloudwego/kitex/client"
        "github.com/cloudwego/kitex/pkg/klog"
        xdsmanager "github.com/kitex-contrib/xds"
        "github.com/kitex-contrib/xds/xdssuite"

        hello "github.com/whalecold/kitex-demo/helloworld/kitex_gen/hello"
        "github.com/whalecold/kitex-demo/helloworld/kitex_gen/hello/echoservice"
)

func main() {
        if err := xdsmanager.Init(); err != nil {
                klog.Fatal(err)
        }
        dest := os.Getenv("DEST_SERVICE")

        cli, err := echoservice.NewClient(dest,
                client.WithSuite(xdssuite.NewClientSuite(xdssuite.WithMatchRetryMethod(true))),
        )
        if err != nil {
                panic(err)
        }

        resp, err := cli.Greet(context.Background(), &hello.GreetRequest{
                Req: "thrift client",
        })
        if err != nil {
                klog.Error(err)
        } else {
                klog.Infof("resp: %s", resp)
        }
}

Server 代码

go 复制代码
package main

import (
        "log"
        "net"

        "github.com/cloudwego/kitex/pkg/klog"
        "github.com/cloudwego/kitex/server"
        xdsmanager "github.com/kitex-contrib/xds"
        "github.com/kitex-contrib/xds/xdssuite"
        echo "github.com/whalecold/kitex-demo/helloworld/kitex_gen/hello/echoservice"
)

func main() {
        if err := xdsmanager.Init(); err != nil {
                klog.Fatal(err)
        }
        addr, _ := net.ResolveTCPAddr("tcp", ":6789")
        svr := echo.NewServer(new(GreetServiceImplProto),
                server.WithServiceAddr(addr),
                xdssuite.NewLimiter(),
        )

        err := svr.Run()
        if err != nil {
                log.Println(err.Error())
        }
}
相关推荐
颜淡慕潇几秒前
Spring Boot 3.3.x、3.4.x、3.5.x 深度对比与演进分析
java·后端·架构
布列瑟农的星空1 分钟前
WebAssembly入门(一)——Emscripten
前端·后端
小突突突1 小时前
Spring框架中的单例bean是线程安全的吗?
java·后端·spring
iso少年2 小时前
Go 语言并发编程核心与用法
开发语言·后端·golang
掘金码甲哥2 小时前
云原生算力平台的架构解读
后端
码事漫谈2 小时前
智谱AI从清华实验室到“全球大模型第一股”的六年征程
后端
码事漫谈2 小时前
现代软件开发中常用架构的系统梳理与实践指南
后端
Mr.Entropy2 小时前
JdbcTemplate 性能好,但 Hibernate 生产力高。 如何选择?
java·后端·hibernate
YDS8292 小时前
SpringCloud —— MQ的可靠性保障和延迟消息
后端·spring·spring cloud·rabbitmq
无限大63 小时前
为什么"区块链"不只是比特币?——从加密货币到分布式应用
后端