| --- |
| title: traffic-split |
| keywords: |
| - APISIX |
| - API 网关 |
| - Traffic Split |
| - 灰度发布 |
| - 蓝绿发布 |
| description: 本文介绍了 Apache APISIX traffic-split 插件的相关操作,你可以使用此插件动态地将部分流量引导至各种上游服务。 |
| --- |
| |
| <!-- |
| # |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| --> |
| |
| ## 描述 |
| |
| `traffic-split` 插件可以通过配置 `match` 和 `weighted_upstreams` 属性,从而动态地将部分流量引导至各种上游服务。该插件可应用于灰度发布和蓝绿发布的场景。 |
| |
| `match` 属性是用于引导流量的自定义规则,`weighted_upstreams` 属性则用于引导流量的上游服务。当一个请求被 `match` 属性匹配时,它将根据配置的 `weights` 属性被引导至上游服务。你也可以不使用 `match` 属性,只根据 `weighted_upstreams` 属性来引导所有流量。 |
| |
| :::note 注意 |
| |
| 由于该插件使用了加权循环算法(特别是在重置 `wrr` 状态时),因此在使用该插件时,可能会存在上游服务之间的流量比例不精准现象。 |
| |
| ::: |
| |
| ## 属性 |
| |
| | 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 | |
| | ---------------------- | --------------| ------ | ------ | ------ |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |
| | rules.match | array[object] | 否 | | | 匹配规则列表,默认为空且规则将被无条件执行。 | |
| | rules.match.vars | array[array] | 否 | | | 由一个或多个 `{var, operator, val}` 元素组成的列表,例如:`{"arg_name", "==", "json"}`,表示当前请求参数 `name` 是 `json`。这里的 `var` 与 NGINX 内部自身变量命名是保持一致,所以也可以使用 `request_uri`、`host` 等;对于已支持的运算符,具体用法请参考 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 的 `operator-list` 部分。 | |
| | rules.weighted_upstreams | array[object] | 否 | | | 上游配置规则列表。 | |
| | weighted_upstreams.upstream_id | string/integer | 否 | | | 通过上游 `id` 绑定对应上游。 | |
| | weighted_upstreams.upstream | object | 否 | | | 上游配置信息。 | |
| | upstream.type | enum | 否 | roundrobin | [roundrobin, chash] | 流量引导机制的类型;`roundrobin` 表示支持权重的负载,`chash` 表示使用一致性哈希。 | |
| | upstream.hash_on | enum | 否 | vars | | 该属性仅当 `upstream.type` 是 `chash` 时有效。支持的类型有 `vars`(NGINX 内置变量),`header`(自定义 header),`cookie`,`consumer`,`vars_combinations`。更多信息请参考 [Upstream](../admin-api.md#upstream) 用法。 | |
| | upstream.key | string | 否 | | | 该属性仅当 `upstream.type` 是 `chash` 时有效。根据 `hash_on` 和 `key` 来查找对应的 Node `id`。更多信息请参考 [Upstream](../admin-api.md#upstream) 用法。 | |
| | upstream.nodes | object | 否 | | | 哈希表,键是上游节点的 IP 地址与可选端口的组合,值是节点的权重。将 `weight` 设置为 `0` 表示一个请求永远不会被转发到该节点。 | |
| | upstream.timeout | object | 否 | 15 | | 发送和接收消息的超时时间(单位为秒)。 | |
| | upstream.pass_host | enum | 否 | "pass" | ["pass", "node", "rewrite"] | 当请求被转发到上游时配置 `host`。`pass` 代表将客户端的 `host` 透明传输给上游;`node` 代表使用 `upstream` Node 中配置的 `host`; `rewrite` 代表使用配置项 `upstream_host` 的值。 | |
| | upstream.name | string | 否 | | | 标识上游服务名称、使用场景等。 | |
| | upstream.upstream_host | string | 否 | | | 上游服务请求的 `host`,仅当 `pass_host` 属性配置为 `rewrite` 时生效。 | |
| | weighted_upstreams.weight | integer | 否 | weight = 1 | | 根据 `weight` 值做流量划分,多个 `weight` 之间使用 `roundrobin` 算法划分。 | |
| |
| :::note 注意 |
| |
| 目前 `weighted_upstreams.upstream` 的配置不支持 `service_name`、`discovery_type`、`checks`、`retries`、`retry_timeout`、`desc`、`scheme`、`labels`、`create_time` 和 `update_time` 等字段。如果你需要使用这些字段,可以在创建上游对象时指定这些字段,然后在该插件中配置 `weighted_upstreams.upstream_id` 属性即可。 |
| |
| ::: |
| |
| :::info 重要 |
| |
| 在 `match` 属性中,变量中的表达式以 AND 方式关联,多个变量以 OR 方式关联。 |
| |
| 如果你仅配置了 `weight` 属性,那么它将会使用该 Route 或 Service 中的上游服务的权重。 |
| |
| ::: |
| |
| ## 启用插件 |
| |
| 以下示例展示了如何在指定路由上启用 `traffic-split` 插件,并通过插件中的 `upstream` 属性配置上游信息: |
| |
| ```shell |
| curl http://127.0.0.1:9180/apisix/admin/routes/1 \ |
| -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' |
| { |
| "uri": "/index.html", |
| "plugins": { |
| "traffic-split": { |
| "rules": [ |
| { |
| "weighted_upstreams": [ |
| { |
| "upstream": { |
| "name": "upstream_A", |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1981":10 |
| }, |
| "timeout": { |
| "connect": 15, |
| "send": 15, |
| "read": 15 |
| } |
| }, |
| "weight": 1 |
| }, |
| { |
| "weight": 1 |
| } |
| ] |
| } |
| ] |
| } |
| }, |
| "upstream": { |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1980": 1 |
| } |
| } |
| }' |
| ``` |
| |
| 如果你已经配置了一个上游对象,你可以通过插件中的 `upstream_id` 属性来绑定上游服务: |
| |
| ```shell |
| curl http://127.0.0.1:9180/apisix/admin/routes/1 \ |
| -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' |
| { |
| "uri": "/index.html", |
| "plugins": { |
| "traffic-split": { |
| "rules": [ |
| { |
| "weighted_upstreams": [ |
| { |
| "upstream_id": 1, |
| "weight": 1 |
| }, |
| { |
| "weight": 1 |
| } |
| ] |
| } |
| ] |
| } |
| }, |
| "upstream": { |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1980": 1 |
| } |
| } |
| }' |
| ``` |
| |
| :::tip 提示 |
| |
| 通过 `upstream_id` 方式来绑定已定义的上游,可以复用上游已存在的健康检查、重试等功能。 |
| |
| ::: |
| |
| :::note 注意 |
| |
| `weighted_upstreams` 属性支持同时使用 `upstream` 和 `upstream_id` 两种配置方式。 |
| |
| ::: |
| |
| ## 测试插件 |
| |
| ### 灰度发布 |
| |
| 灰度发布(又名金丝雀发布)是指在已经上线与未上线服务之间,能够平滑过渡的一种发布方式。在其上可以进行 A/B 测试,即让一部分用户继续用产品特性 A,一部分用户开始用产品特性 B。如果用户对特性 B 没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到特性 B 上面来。 |
| |
| 以下示例展示了如何通过配置 `weighted_upstreams` 的 `weight` 属性来实现流量分流。按 3:2 的权重流量比例进行划分,其中 60% 的流量到达运行在 `1981` 端口上的上游服务,40% 的流量到达运行在 `1980` 端口上的上游服务: |
| |
| ```shell |
| curl http://127.0.0.1:9180/apisix/admin/routes/1 \ |
| -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' |
| { |
| "uri": "/index.html", |
| "plugins": { |
| "traffic-split": { |
| "rules": [ |
| { |
| "weighted_upstreams": [ |
| { |
| "upstream": { |
| "name": "upstream_A", |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1981":10 |
| }, |
| "timeout": { |
| "connect": 15, |
| "send": 15, |
| "read": 15 |
| } |
| }, |
| "weight": 3 |
| }, |
| { |
| "weight": 2 |
| } |
| ] |
| } |
| ] |
| } |
| }, |
| "upstream": { |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1980": 1 |
| } |
| } |
| }' |
| ``` |
| |
| **测试** |
| |
| 在请求 5 次后,其中会有 3 次命中运行在 `1981` 端口的插件上游服务,2 次命中运行在 `1980` 端口的路由上游服务: |
| |
| ```shell |
| curl http://127.0.0.1:9080/index.html -i |
| ``` |
| |
| ```shell |
| HTTP/1.1 200 OK |
| Content-Type: text/html; charset=utf-8 |
| ... |
| hello 1980 |
| ``` |
| |
| ```shell |
| curl http://127.0.0.1:9080/index.html -i |
| ``` |
| |
| ```shell |
| HTTP/1.1 200 OK |
| Content-Type: text/html; charset=utf-8 |
| ... |
| world 1981 |
| ``` |
| |
| ### 蓝绿发布 |
| |
| 在蓝绿发布场景中,你需要维护两个环境,一旦新的变化在蓝色环境(staging)中被测试和接受,用户流量就会从绿色环境(production)转移到蓝色环境。 |
| |
| 以下示例展示了如何基于请求头来配置 `match` 规则: |
| |
| ```shell |
| curl http://127.0.0.1:9180/apisix/admin/routes/1 \ |
| -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' |
| { |
| "uri": "/index.html", |
| "plugins": { |
| "traffic-split": { |
| "rules": [ |
| { |
| "match": [ |
| { |
| "vars": [ |
| ["http_release","==","new_release"] |
| ] |
| } |
| ], |
| "weighted_upstreams": [ |
| { |
| "upstream": { |
| "name": "upstream_A", |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1981":10 |
| } |
| } |
| } |
| ] |
| } |
| ] |
| } |
| }, |
| "upstream": { |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1980": 1 |
| } |
| } |
| }' |
| ``` |
| |
| **测试** |
| |
| 1. 通过 `curl` 命令发出请求,如果请求带有一个值为 `new_release` 的 release header,它就会被引导至在插件上配置的新的上游服务: |
| |
| ```shell |
| curl http://127.0.0.1:9080/index.html -H 'release: new_release' -i |
| ``` |
| |
| ```shell |
| HTTP/1.1 200 OK |
| Content-Type: text/html; charset=utf-8 |
| ... |
| world 1981 |
| ``` |
| |
| 2. 否则请求会被引导至在路由上配置的另一个上游服务: |
| |
| ```shell |
| curl http://127.0.0.1:9080/index.html -H 'release: old_release' -i |
| ``` |
| |
| ```shell |
| HTTP/1.1 200 OK |
| Content-Type: text/html; charset=utf-8 |
| ... |
| hello 1980 |
| ``` |
| |
| ### 自定义发布 |
| |
| 你也可以通过配置规则和权重来实现自定义发布。 |
| |
| **示例 1** |
| |
| 下面的示例只配置了一个 `vars` 规则,流量按 3:2 的权重比例进行划分,不匹配 `vars` 的流量将被重定向到在路由上配置的上游服务: |
| |
| ```shell |
| curl http://127.0.0.1:9180/apisix/admin/routes/1 \ |
| -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' |
| { |
| "uri": "/index.html", |
| "plugins": { |
| "traffic-split": { |
| "rules": [ |
| { |
| "match": [ |
| { |
| "vars": [ |
| ["arg_name","==","jack"], |
| ["http_user-id",">","23"], |
| ["http_apisix-key","~~","[a-z]+"] |
| ] |
| } |
| ], |
| "weighted_upstreams": [ |
| { |
| "upstream": { |
| "name": "upstream_A", |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1981":10 |
| } |
| }, |
| "weight": 3 |
| }, |
| { |
| "weight": 2 |
| } |
| ] |
| } |
| ] |
| } |
| }, |
| "upstream": { |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1980": 1 |
| } |
| } |
| }' |
| ``` |
| |
| **测试** |
| |
| 1. 通过 `curl` 命令发出请求,在 `match` 规则校验通过后,将会有 60% 的请求被引导至插件 `1981` 端口的上游服务,40% 的请求被引导至路由 `1980` 端口的上游服务: |
| |
| ```shell |
| curl 'http://127.0.0.1:9080/index.html?name=jack' \ |
| -H 'user-id:30' -H 'apisix-key: hello' -i |
| ``` |
| |
| 在请求 5 次后,其中会有 3 次命中 `1981` 端口的服务,2 次命中 `1980` 端口的服务: |
| |
| ```shell |
| HTTP/1.1 200 OK |
| Content-Type: text/html; charset=utf-8 |
| ... |
| world 1981 |
| ``` |
| |
| ```shell |
| HTTP/1.1 200 OK |
| Content-Type: text/html; charset=utf-8 |
| ... |
| hello 1980 |
| ``` |
| |
| 2. 如果 `match` 规则校验失败(如缺少请求头 `apisix-key`), 那么请求将被引导至路由的 `1980` 端口的上游服务: |
| |
| ```shell |
| curl 'http://127.0.0.1:9080/index.html?name=jack' \ |
| -H 'user-id:30' -i |
| ``` |
| |
| ```shell |
| HTTP/1.1 200 OK |
| Content-Type: text/html; charset=utf-8 |
| ... |
| hello 1980 |
| ``` |
| |
| **示例 2** |
| |
| 下面的示例配置了多个 `vars` 规则,流量按 3:2 的权重比例进行划分,不匹配 `vars` 的流量将被重定向到在路由上配置的上游服务: |
| |
| ```shell |
| curl http://127.0.0.1:9180/apisix/admin/routes/1 \ |
| -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' |
| { |
| "uri": "/index.html", |
| "plugins": { |
| "traffic-split": { |
| "rules": [ |
| { |
| "match": [ |
| { |
| "vars": [ |
| ["arg_name","==","jack"], |
| ["http_user-id",">","23"], |
| ["http_apisix-key","~~","[a-z]+"] |
| ] |
| }, |
| { |
| "vars": [ |
| ["arg_name2","==","rose"], |
| ["http_user-id2","!",">","33"], |
| ["http_apisix-key2","~~","[a-z]+"] |
| ] |
| } |
| ], |
| "weighted_upstreams": [ |
| { |
| "upstream": { |
| "name": "upstream_A", |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1981":10 |
| } |
| }, |
| "weight": 3 |
| }, |
| { |
| "weight": 2 |
| } |
| ] |
| } |
| ] |
| } |
| }, |
| "upstream": { |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1980": 1 |
| } |
| } |
| }' |
| ``` |
| |
| **测试** |
| |
| 1. 通过 `curl` 命令发出请求,如果两个 `vars` 表达式均匹配成功,`match` 规则校验通过后,将会有 60% 的请求被引导至插件 `1981` 端口的上游服务,40% 的请求命中到路由的 `1980` 端口的上游服务: |
| |
| ```shell |
| curl 'http://127.0.0.1:9080/index.html?name=jack&name2=rose' \ |
| -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i |
| ``` |
| |
| 在请求 5 次后,其中会有 3 次命中 `1981` 端口的服务,2 次命中 `1980` 端口的服务: |
| |
| ```shell |
| HTTP/1.1 200 OK |
| Content-Type: text/html; charset=utf-8 |
| ... |
| world 1981 |
| ``` |
| |
| ```shell |
| HTTP/1.1 200 OK |
| Content-Type: text/html; charset=utf-8 |
| ... |
| hello 1980 |
| ``` |
| |
| 2. 如果第二个 `vars` 的表达式匹配失败(例如缺少 `name2` 请求参数),`match` 规则校验通过后,效果将会与上一种相同。即有 60% 的请求被引导至插件 `1981` 端口的上游服务,40% 的请求命中到路由的 `1980` 端口的上游服务: |
| |
| ```shell |
| curl 'http://127.0.0.1:9080/index.html?name=jack' \ |
| -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i |
| ``` |
| |
| 在请求 5 次后,其中会有 3 次命中 `1981` 端口的服务,2 次命中 `1980` 端口的服务: |
| |
| ```shell |
| HTTP/1.1 200 OK |
| Content-Type: text/html; charset=utf-8 |
| ... |
| world 1981 |
| ``` |
| |
| ```shell |
| HTTP/1.1 200 OK |
| Content-Type: text/html; charset=utf-8 |
| ... |
| hello 1980 |
| ``` |
| |
| 3. 如果两个 `vars` 的表达式均匹配失败(如缺少 `name` 和 `name2` 请求参数),`match` 规则会校验失败,请求将被引导至路由的 `1980` 端口的上游服务: |
| |
| ```shell |
| curl 'http://127.0.0.1:9080/index.html?name=jack' -i |
| ``` |
| |
| ```shell |
| HTTP/1.1 200 OK |
| Content-Type: text/html; charset=utf-8 |
| ... |
| hello 1980 |
| ``` |
| |
| ### 匹配规则与上游对应 |
| |
| 以下示例展示了如何配置多个 `rules` 属性,实现不同的匹配规则与上游一一对应。当请求头 `x-api-id` 为 `1` 时,请求会被引导至 `1981` 端口的上游服务;当 `x-api-id` 为 `2` 时,请求会被引导至 `1982` 端口的上游服务;否则请求会被引导至 `1980` 端口的上游服务: |
| |
| ```shell |
| curl http://127.0.0.1:9180/apisix/admin/routes/1 \ |
| -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' |
| { |
| "uri": "/hello", |
| "plugins": { |
| "traffic-split": { |
| "rules": [ |
| { |
| "match": [ |
| { |
| "vars": [ |
| ["http_x-api-id","==","1"] |
| ] |
| } |
| ], |
| "weighted_upstreams": [ |
| { |
| "upstream": { |
| "name": "upstream-A", |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1981":1 |
| } |
| }, |
| "weight": 3 |
| } |
| ] |
| }, |
| { |
| "match": [ |
| { |
| "vars": [ |
| ["http_x-api-id","==","2"] |
| ] |
| } |
| ], |
| "weighted_upstreams": [ |
| { |
| "upstream": { |
| "name": "upstream-B", |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1982":1 |
| } |
| }, |
| "weight": 3 |
| } |
| ] |
| } |
| ] |
| } |
| }, |
| "upstream": { |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1980": 1 |
| } |
| } |
| }' |
| ``` |
| |
| **测试** |
| |
| 1. 通过 `curl` 命令发出请求,请求头 `x-api-id` 为 `1`,则会命中 `1980` 端口的服务: |
| |
| ```shell |
| curl http://127.0.0.1:9080/hello -H 'x-api-id: 1' |
| ``` |
| |
| ```shell |
| 1981 |
| ``` |
| |
| 2. 如果请求头 `x-api-id` 为 `2`,则会命中 `1982` 端口的服务: |
| |
| ```shell |
| curl http://127.0.0.1:9080/hello -H 'x-api-id: 2' |
| ``` |
| |
| ```shell |
| 1982 |
| ``` |
| |
| 3. 如果请求头 `x-api-id` 为 `3`,规则不匹配,则会命中带 `1980` 端口的服务: |
| |
| ```shell |
| curl http://127.0.0.1:9080/hello -H 'x-api-id: 3' |
| ``` |
| |
| ```shell |
| 1980 |
| ``` |
| |
| ## 删除插件 |
| |
| 当你需要删除该插件时,可以通过以下命令删除相应的 JSON 配置,APISIX 将会自动重新加载相关配置,无需重启服务: |
| |
| ```shell |
| curl http://127.0.0.1:9180/apisix/admin/routes/1 \ |
| -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' |
| { |
| "uri": "/index.html", |
| "plugins": {}, |
| "upstream": { |
| "type": "roundrobin", |
| "nodes": { |
| "127.0.0.1:1980": 1 |
| } |
| } |
| }' |
| ``` |