灰度发布不一定依赖服务网格;轻量级场景可用Nacos权重+元数据实现10%流量切分,但仅限首次路由,无法保障全链路一致性;真正可控的灰度需流量染色+标签路由,依赖Istio或Dubbo Admin等支持。

灰度发布必须依赖服务网格吗?
不一定。轻量级灰度完全可以用 Nacos 权重 + 元数据实现,weight=0 即逻辑下线,weight=10 和 weight=90 配合 10 台 v1 实例 + 1 台 v2 实例,就能做到稳定 10% 流量切分。前提是客户端用的是 Spring Cloud LoadBalancer 或 Dubbo 默认负载均衡器——它们原生支持 Nacos 的 weight 字段。
但要注意:权重只影响「首次选择」,不保证全链路一致性。比如用户 A 的第一次请求打到 v2,第二次可能因本地缓存未刷新或负载策略变化落到 v1,这就不是真正意义上的灰度。要解决这个,就得靠流量染色 + 标签路由,而这通常需要 Istio 或 Dubbo Admin 的 TagRouter 支持。
- 纯 Nacos 权重适合内部服务间低风险灰度,如配置类、工具类服务
- 涉及用户态行为(如前端页面、订单流程)必须做全链路染色,否则灰度无意义
- Spring Cloud Alibaba 2022.0.0+ 版本开始支持
nacos.metadata.tag注册时透传标签,配合MetadataAwareRule可在 Ribbon 层做简单标签路由,但已逐步被弃用
Istio 的 VirtualService 怎么写才能支持流量镜像?
VirtualService 的镜像能力藏在 route 的 mirror 字段里,不是 weight 也不是 subset。它会把原始请求**复制一份**发往镜像目标,原始请求仍走主路径,且镜像请求默认不返回响应给客户端——这点必须明确,否则容易误以为镜像影响了线上。
常见错误是把 mirror 和 weight 混用,或者漏掉 mirrorPercentage 导致 100% 镜像,压垮测试服务。正确写法示例如下:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: service-a
spec:
hosts:
- service-a
http:
- route:
- destination:
host: service-a
subset: v1
weight: 100
mirror:
host: service-a
subset: v2
mirrorPercentage:
value: 100-
mirror不参与负载均衡计算,它独立于route存在 -
mirrorPercentage控制镜像流量比例,值为 100 表示每条请求都镜像,50 表示一半请求镜像 - 镜像目标(
v2)必须定义在对应的DestinationRule中,且host必须可解析 - 镜像请求的 header 默认不带
X-Forwarded-For,如需保留原始客户端 IP,得在 EnvoyFilter 中显式透传
Dubbo Admin 的标签路由为什么经常不生效?
根本原因通常是消费者端没启用标签路由插件,或者 provider 实例注册时没带有效 tag 元数据。Dubbo 默认路由策略是 RandomLoadBalance,它完全忽略标签;只有显式配置 TagRouter 才会根据 consumer.tag 匹配 provider.tag。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
排查步骤很直接:
- 检查消费者启动日志是否有
TagRouter loaded,没有说明没加载路由规则 - 调用
curl http://nacos:8848/nacos/v1/ns/instance/list?serviceName=xxx,确认 provider 实例的metadata里真有"tag":"v2" - 确认消费者代码里是否设置了
invoker.getInvocation().setAttachment("tag", "v2"),或者全局配置了dubbo.consumer.tag=v2 - Dubbo 3.x 后
TagRouter已改为基于TagRouterFactory动态加载,如果用了自定义 Router,得确保它实现了TagRouter接口
流量染色从网关往下透传,header 名字能随便起吗?
不能。Istio 默认只透传白名单内的 header,比如 x-request-id、x-envoy-downstream-service-cluster,而 x-tag 或 x-canary 这类自定义字段默认会被 Envoy 丢弃。如果你在 Gateway 里写了 headers.set("x-canary", "true"),下游服务收不到。
解决方案只有两个:要么改 Istio 的 MeshConfig,要么用 EnvoyFilter 显式放行。后者更安全,示例:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: allow-canary-header
spec:
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
patch:
operation: MERGE
value:
typed_config:
x-envoy-translator-shadowing: true
http_filters:
- name: envoy.filters.http.router
- name: envoy.filters.http.header_to_metadata
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.header_to_metadata.v3.Config
request_rules:
- header: x-canary
on_header_present:
metadata_namespace: envoy.lb
key: canary
type: STRING
value: "%REQ(x-canary)%"- Header 名字本身没限制,但必须在所有中间代理(Gateway、Sidecar、Ingress Controller)上统一放行
- Spring Cloud Gateway 默认透传所有 header,但一旦接入 Istio Sidecar,就受 Envoy 约束
- 如果用 Nginx Ingress,得在
configuration-snippet里加proxy_set_header x-canary $http_x_canary;
实际落地时,最常被忽略的是染色 header 的生命周期管理——它不该在任意一层被覆盖或清空。哪怕只漏掉一个中间服务没透传,整条链路就断了。

















