在ASM中定义Headers键值的匹配条件,可以根据请求动态地进行流量转移。本文介绍如何通过Headers在ASM实现应用流量转移。

GRPC协议Headers编程实践

服务端获取Headers

  • 基本方法
    • 使用Java语言通过服务端获取Headers实现基本方法。

      实现拦截器ServerInterceptor接口的interceptCall(ServerCall<ReqT, RespT> call,final Metadata m,ServerCallHandler<ReqT, RespT> h)方法,通过String v = m.get(k)获取header信息,get方法入参类型为Metadata.Key<String>

    • 使用Go语言通过服务端获取Headers实现基本方法。

      metadata.FromIncomingContext(ctx)(md MD, ok bool),MD是一个map[string][]string

    • 使用NodeJS语言通过服务端获取Headers实现基本方法。

      call.metadata.getMap(),返回值类型是[key: string]: MetadataValueMetadataValue类型定义为string/Buffer

    • 使用Python语言通过服务端获取Headers实现基本方法。

      context.invocation_metadata(),返回值类型为2-tuple数组,2-tuple的形式为('k','v'),使用m.key, m.value获取键值对。

  • Unary RPC
    • 使用Java语言通过服务端获取Headers实现Unary RPC。

      对Headers无感知。

    • 使用Go语言通过服务端获取Headers实现Unary RPC。

      在方法中直接调用metadata.FromIncomingContext(ctx),上下文参数ctx来自Talk的入参。

    • 使用NodeJS语言通过服务端获取Headers实现Unary RPC。

      在方法内直接调用call.metadata.getMap()

    • 使用Python语言通过服务端获取Headers实现Unary RPC。

      在方法内直接调用context.invocation_metadata()

  • Server streaming RPC
    • 使用Java语言通过服务端获取Headers实现Server streaming RPC。

      对Headers无感知。

    • 使用Go语言通过服务端获取Headers实现Server streaming RPC。

      在方法中直接调用metadata.FromIncomingContext(ctx),上下文参数ctx从TalkOneAnswerMore的入参stream中获取stream.Context()

    • 使用NodeJS语言通过服务端获取Headers实现Server streaming RPC。

      在方法内直接调用call.metadata.getMap()

    • 使用Python语言通过服务端获取Headers实现Server streaming RPC。

      在方法内直接调用context.invocation_metadata()

  • Client streaming RPC
    • 使用Java语言通过服务端获取Headers实现Client streaming RPC。

      对Headers无感知。

    • 使用Go语言通过服务端获取Headers实现Client streaming RPC。

      在方法中直接调用metadata.FromIncomingContext(ctx),上下文参数ctx从TalkMoreAnswerOne的入参stream中获取stream.Context()

    • 使用NodeJS语言通过服务端获取Headers实现Client streaming RPC。

      在方法内直接调用call.metadata.getMap()

    • 使用Python语言通过服务端获取Headers实现Client streaming RPC。

      在方法内直接调用context.invocation_metadata()

  • Bidirectional streaming RPC
    • 使用Java语言通过服务端获取Headers实现Bidirectional streaming RPC。

      对Headers无感知。

    • 使用Go语言通过服务端获取Headers实现Bidirectional streaming RPC。

      在方法中直接调用metadata.FromIncomingContext(ctx),上下文参数ctx从TalkBidirectional的入参stream中获取stream.Context()

    • 使用NodeJS语言通过服务端获取Headers实现Bidirectional streaming RPC。

      在方法内直接调用call.metadata.getMap()

    • 使用Python语言通过服务端获取Headers实现Bidirectional streaming RPC。

      在方法内直接调用context.invocation_metadata()

客户端发送Headers

  • 基本方法
    • 使用Java语言通过客户端发送Headers实现基本方法。

      实现拦截器ClientInterceptor接口的interceptCall(MethodDescriptor<ReqT, RespT> m, CallOptions o, Channel c)方法,实现返回值类型ClientCall<ReqT, RespT>的start((Listener<RespT> l, Metadata h))方法,通过h.put(k, v)填充header信息,put方法入参k的类型为Metadata.Key<String>v的类型为String

    • 使用Go语言通过客户端发送Headers实现基本方法。

      metadata.AppendToOutgoingContext(ctx,kv ...) context.Context

    • 使用NodeJS语言通过客户端发送Headers实现基本方法。

      metadata=call.metadata.getMap()metadata.add(key, headers[key])

    • 使用Python语言通过客户端发送Headers实现基本方法。

      metadata_dict = {}变量填充metadata_dict[c.key] = c.value,最终转为list tuple类型list(metadata_dict.items())

  • Unary RPC
    • 使用Java语言通过客户端发送Headers实现Unary RPC。

      对Headers无感知。

    • 使用Go语言通过客户端发送Headers实现Unary RPC。

      在方法中直接调用metadata.AppendToOutgoingContext(ctx,kv)

    • 使用NodeJS语言通过客户端发送Headers实现Unary RPC。

      在方法内直接使用基本方法。

    • 使用Python语言通过客户端发送Headers实现Unary RPC。

      在方法内直接使用基本方法。

  • Server streaming RPC
    • 使用Java语言通过客户端发送Headers实现Server streaming RPC。

      对Headers无感知。

    • 使用Go语言通过客户端发送Headers实现Server streaming RPC。

      在方法中直接调用metadata.AppendToOutgoingContext(ctx,kv)

    • 使用NodeJS语言通过客户端发送Headers实现Server streaming RPC。

      在方法内直接使用基本方法。

    • 使用Python语言通过客户端发送Headers实现Server streaming RPC。

      在方法内直接使用基本方法。

  • Client streaming RPC
    • 使用Java语言通过客户端发送Headers实现Client streaming RPC。

      对Headers无感知。

    • 使用Go语言通过客户端发送Headers实现Client streaming RPC。

      在方法中直接调用metadata.AppendToOutgoingContext(ctx,kv)

    • 使用NodeJS语言通过客户端发送Headers实现Client streaming RPC。

      在方法内直接使用基本方法。

    • 使用Python语言通过客户端发送Headers实现Client streaming RPC。

      在方法内直接使用基本方法。

  • Bidirectional streaming RPC
    • 使用Java语言通过客户端发送Headers实现Bidirectional streaming RPC。

      对Headers无感知。

    • 使用Go语言通过客户端发送Headers实现Bidirectional streaming RPC。

      在方法中直接调用metadata.AppendToOutgoingContext(ctx,kv)

    • 使用NodeJS语言通过客户端发送Headers实现Bidirectional streaming RPC。

      在方法内直接使用基本方法。

    • 使用Python语言通过客户端发送Headers实现Bidirectional streaming RPC。

      在方法内直接使用基本方法。

Propaganda Headers

由于链路追踪需要将上游传递过来的链路元数据透传给下游,以形成同一条请求链路的完整信息,需要将服务端获取的Headers信息中,和链路追踪相关的Headers透传给向下游发起请求的客户端。

除了Java语言的实现,其他语言的通信模型方法都对Headers有感知,因此可以将服务端读取Headers-传递Headers-客户端发送Headers这三个动作有顺序地在4种通信模型方法内部实现。

Java语言读取和写入Headers是通过两个拦截器分别实现的,因此Propaganda Headers无法在一个顺序的流程里实现,且考虑到并发因素,以及只有读取拦截器知道链路追踪的唯一ID,所以无法通过最直觉的缓存方式搭建两个拦截器的桥梁。

Java语言的实现提供了一种Metadata-Context Propagation的机制。机制

在服务器拦截器读取阶段,通过ctx.withValue(key, metadata)Metadata/Header存入Context,其中Key是Context.Key<String>类型。然后在客户端拦截器中,通过key.get()Metadata从Context读出,get方法默认使用Context.current()上下文,这就保证了一次请求的Headers读取和写入使用的是同一个上下文。

有了Propaganda Headers的实现,基于GRPC的链路追踪就有了机制上的保证。

部署和验证网格拓扑

实现流量转移之前,您需要部署和验证网格拓扑,确保网格拓扑是可以通信的。

进入示例工程的tracing目录,该目录下包含4种编程语言的部署脚本。以下以Go版本为例,部署和验证网格拓扑。
cd go
# 部署
sh apply.sh
# 验证
sh test.sh

如果没有出现异常信息,则说明网格拓扑是可以通信的。

部署后的服务网格拓扑如下图所示。网络拓扑

流量转移

在VirtualService中通过定义Headers键值的匹配条件,可以实现根据请求动态地进行流量转移。如果再结合按API和按版本进行流量管理的实践,就可以完成应用级的精细化流量管理。流量管理的详细介绍请参见管理gRPC协议示例流量。以下VirtualService定义了Headers中server-version=go的请求100%流量路由到Go版本服务。

  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 控制平面区域,单击VirtualService页签,然后单击新建
  5. 新建页面,选择命名空间,在文本框中输入以下信息,然后单击确定
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      namespace: grpc-best
      name: grpc-server-vs
    spec:
      hosts:
        - "*"
      gateways:
        - grpc-gateway
      http:
        - match:
          - headers:
              server-version:
                exact: go
          route:
            - destination:
                host: grpc-server-svc
                subset: v2
              weight: 100