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())
        }
}
相关推荐
码农派大星。15 分钟前
Spring Boot 配置文件
java·spring boot·后端
杜杜的man1 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*1 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu1 小时前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s1 小时前
Golang--协程和管道
开发语言·后端·golang
为什么这亚子1 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
想进大厂的小王1 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
customer082 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
2402_857589362 小时前
SpringBoot框架:作业管理技术新解
java·spring boot·后端
一只爱打拳的程序猿2 小时前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring