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())
        }
}
相关推荐
GetcharZp2 小时前
玩转 Linux 机器视觉:手把手带你搞定 Ubuntu 下海康工业相机 C++ SDK
后端
星星在线5 小时前
MusicFree:一个「All in One」的个人音乐服务器,让听歌回归简单
前端·后端
IT_陈寒6 小时前
Redis的SETNX并发问题让我加了三天班
前端·人工智能·后端
demo007x6 小时前
Docling 文档转换以及技术架构分析
前端·后端·程序员
袋鱼不重8 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
用户8356290780518 小时前
使用 Python 操作 Word 内容控件
后端·python
像我这样帅的人丶你还8 小时前
啥? 前端也要会干Java?🛵🛵🛵
后端
Hommy888 小时前
【剪映小助手】添加贴纸接口(Add Sticker)
后端·github·剪映小助手·视频剪辑自动化·剪映api
CaffeinePro8 小时前
FastAPI响应处理:返回值、状态码、响应头与异常标准化与案例解析
后端
HuanYu8 小时前
PageHelper分页的原理
后端