跳转至

第十四节 Mixer 适配器的应用(MemQuota服务限流/Mixer对象的定义/RedisQuota服务限流)

1、使用MemQuota适配器进行服务限流

网格内部的服务间调用,因为存在业务优先级、资源分配及服务负载能力等要求,所以常常需要对特定的服务调用进行限制,防止服务过载。

Istio中,我们可以利用MixerMemQuotaRedisQuota适配器,在预检段对流量进行配额判断,根据为特定流量设定的额度规则来判断流量请求是否已经超过指定的额度(简称超额),如果发生超额,就进行限流处理

Istio的限流功能实现中,配置工作分为客户端Mixer端两部分:

  • 客户端使用QuotaSpec定义一个限额,用QuotaSpecBinding对象将QuotaSpec绑定到特定的服务上;
  • Mixer:端需要处理限额的实现逻辑,用一个quota实例定义流量处理规则,用MemQuota/RedisQuotaHandler来实际处理流量,最后使用Rule对象将二者进行绑定:

本节依然会使用sleep服务作为客户端,并使用httpbin作为服务端。

1-1 Mixer对象的定义

**在限流过程中用到的服务端对象并无特别,和所有的Mixer适配器应用一样, Handler, Instance结合Rule,这二种对象一起定义这一行为。 **

  • 首先定义一个MemQuota类型的Handler,通过它来定义MemQuota适配器的行为

这里首先定义了默认的限流规定,每秒最多调用20次,然后通过overrides的方式为httpbin服务定义一个限流,每5秒只能调用一次:

memquota-handler.yaml

apiVersion: "config.istio.io/v1alpha2" 
kind: memquota 
metadata: 
  name: handler 
spec: 
  quotas: 
  - name: dent-quota.quota.default 
    maxAmount: 20 
    validDuration: 10s 
    overrides: 
    - dimensions: 
        destination: httpbin 
      maxAmount: 1 
      validDuration : 5s 
$ kubectl apply -f memquota-handler.yaml 
memquota.config.istio.io/handler created

把上述文件命名为memquota-handler.yaml,并提交到Kubernetes集群。 这里定义了一个每10秒调用20次的默认调用配额,又为httpbin服务定义了一个特例: 每5秒只允许调用一次。

  • 接下来需要定义的是基于Quota模板的Instance对象:

quota-instance.yaml

apiVersion: "config.istio.io/v1alpha2" 
kind: quota 
metadata: 
  name: dest-quota 
spec: 
  dimensions: 
    destination: destination.labels["app"] | destination.service | "unknown" 
$ kubectl apply -f quota-instance.yaml 
quota.config.istio.io/dest-quota created

把代码保存为quota-instance.yaml,并提交到Kubernetes集群。

这里为quota模板定义了一个输入项demensions,其中包含对服务目标的定义。

destination字段的定义是一个属性表达式,其取值逻辑是:

  • 首先判断流量目标是否具有app标签,
  • 如果没有,则获取目标服务的名称,否则取值为“unknown"

  • 接下来使用一个Rule对象将两者关联起来

quota-rule.yaml

apiVersion: config.istio.io/v1alpha2 
kind: rule 
metadata: 
  name: quota 
spec: 
  actions: 
  - handler: handler.memquota 
    instances: 
    - dest-quota.quota 
$ kubectl apply -f quota-rule.yaml 
rule.config.istio.io/quota created

同样,将代码保存为:quota-rule.yaml,并使用kubectl apply命令提交到Kubernetes集群。

这样就完成了对Mixer端三个对象的定义,三个对象联合起来完成任务:

  • Rule 对象负责识别流量,对符合条件并进入该Rule对象处理流程的流量
  • 使用dest-quota进行处理,将处理结果输出给MemQuota适配器的Handler对象进行判断

1-2 客户端对象定义

Mixer端已经定义了配额方式处理方式,而客户端的配置需要定义两个内容:

受限的应用和配额的扣减方式,需要用QuotaSpecQuotaSpecBinding对象来完成

创建一个QuotaSpec对象:

quotaspec.yaml

apiVersion: config.istio.io/v1alpha2 
kind: QuotaSpec 
metadata: 
  name: request-count 
spec: 
  rules: 
  - quotas: 
    - charge: 5 
      quota: dest-quota 
$ kubectl apply -f quotaspec.yaml 
quotaspec.config.istio.io/request-count created

把上述代码保存为quotaspec.yaml,使用kubectl apply提交到Kubernetes集群。

这里定义了一个quota扣减方式,其中的quota对应的是创建的dest-quota

然后定义QuotaSpecBindng对象,把QuotaSpec绑定到服务上:

quotaspec-binding.yaml

apiVersion: config.istio.io/v1alpha2 
kind: QuotaSpecBinding 
metadata: 
  name: spec-sleep 
spec: 
  quotaSpecs: 
  - name: request-count 
    namespace: default 
  services: 
  - name: httpbin 
    namespace: default 
$ kubectl apply -f quotaspec-binding.yaml
quotaspecbinding.config.istio.io/spec-sleep created

1-3 测试限流功能

在相关的5个对象都创建成功后,就可以进行测试了:

$ kubectl exec sleep-v1-548d87cc5c-92lqk -it bash -c sleep

bash-4.4# for i in 'seq 10';do http --body http://httpbin:8000/ip; done
{
    "origin": "127.0.0.1"
}

RESOURCE EXHAUSTED:Quota is exhausted for: dest-quota 

可以看到,这里连续调用了httpbin服务,并返回了超额错误。

下面尝试调用flaskapp服务:

bash-4.4# for i in 'seq 10'; do  http --body http://flaskapp/env/version; done
v2

发现flaskapp并未受到影响

1-4 注意事项

通过上面的尝试肯定有不少读者会想:这太复杂了而且对象之间的引用太混乱

Alt Image Text

同时、对服务身份的绑定关系也散乱在三个不同的对象中。

甚至到目前为止QuotaSpecQcotaSpecBinding还没有提供Reference笔者推测这部分发生变化的 可能性非常大, 强烈建议不要采用

2、 使用RedisQuota适配器进行服务限流

MemQuota适配器限流,是一种实验性质的做法,它仅支持固定窗口的限流算法, 并且在Mixer是多副本的运行状态下内存计数会完全失效, 必须提供一个独立于Mixer进程之外的后端服务来提供数据支持, 目前官方建议在生产环境中使用RedisQuota对象进行限流.

其整体环节和MemQuota流的流程是一致, 的只不过其中的MemQuota Handler要更换为Redis版本还需要一个用于提供数据后端的Redis应用。

2-1 启动Redis服务

首先我们做一下准备工作启用一个Redis服务器来支持限流操作,这里为了方便起见,先在Kubernetes集群中启动一个单实例服务来完成功能展示。

这里选择使用Redis的官方镜像, 创建一个单实例的Deployment和对应的Service:

apiVersion: v1
kind: ReplicationController
metadata:
  name: redis
  labels:
    name: redis
spec:
  replicas: 1
  selector:
    name: redis
  template:
    metadata:
      labels:
        name: redis
    spec:
      containers:
      - name: redis
        image: k8s.gcr.io/redis:v1
        ports:
        - containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
    name: redis
    labels:
      name: redis
spec:
  ports:
    - port: 6379
      targetPort: 6379
  selector:
    name: redis

将上述文件保存为redis.yaml,并使用kubectl apply命令提交到Kubernetes集群RC在启动之后,会在6379端口开放一个Redis服务。

2-2 定义限流相关对象

两种限流方式的对象定义格式基本是一致的,区别仅在于RedisQuota的定义要比MemQuota的定义细致一些。

这里首先定义RedisQuota:

redisquota.yaml

apiVersion: "config.istio.io/v1alpha2" 
kind: redisquota 
metadata: 
  name: handler 
spec: 
  redisServerUrl: "10.245.90.154:6379" 
  quotas:
  - name: dest-quota.quota.default 
  maxAmount: 20 
  bucketDuration: 1s 
  validDuration: 10s 
  rateLimitAlgorithm: ROLLING_WINDOW 
  overrides: 
  - dimensions: 
      destination: httpbin 
    maxAmount: 1 
$ kubectl apply -f redisquota.yaml 
redisquota.config.istio.io/handler created

将上述代码保存为redisquota.yaml,并使用kubectl apply命令提交到集群。

  • redisServerUrl:用于指定Redis服务的地址。
  • quota.bucketDuration:必须设置为一个大于零的秒数。
  • quota.rateLimitAlgorithm:可以为每个Quota指定各自的限流算法,在MemQuota中采用的是FIXED_ WINDOW算法,且不可更改。
  • overrides.validDuration:该字段无法继续使用。

redis-rule.yaml

apiVersion: "config.istio.io/v1alpha2" 
kind: rule
metadata: 
  name: quota
spec: 
  actions:
  - handler: handler.redisquota
    instances:
      - dest-quota.quota
$ kubectl apply -f redis-rule.yaml 
rule.config.istio.io/quota unchanged
$ kubectl apply -f quotaspec.yaml 
quotaspec.config.istio.io/request-count created

$ kubectl apply -f quotaspec-binding.yaml 
quotaspecbinding.config.istio.io/spec-sleep created

2-3 测试限流功能

$ kubectl exec sleep-v1-548d87cc5c-92lqk -it bash -c sleep
bash-4.4# for i in 'seq 10'; do  http --body http://httpbin:8000/ip; done

RESOURCE_EXHAUSTED:Quota is exhausted for. dest-quota 

{
    "origin": "127.0.0.1"
}
RESOURCE_EXHAUSTED:Quota is exhausted for. dest-quota 

可以看到,和MemQuota一样,限流已经生效,调用失败。