在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]: MetadataValue
,MetadataValue
类型定义为string/Buffer
。 - 使用Python语言通过服务端获取Headers实现基本方法。
context.invocation_metadata()
,返回值类型为2-tuple数组,2-tuple的形式为('k','v')
,使用m.key, m.value
获取键值对。
- 使用Java语言通过服务端获取Headers实现基本方法。
- 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()
。
- 使用Java语言通过服务端获取Headers实现Unary RPC。
- 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()
。
- 使用Java语言通过服务端获取Headers实现Server streaming RPC。
- 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()
。
- 使用Java语言通过服务端获取Headers实现Client streaming RPC。
- 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()
。
- 使用Java语言通过服务端获取Headers实现Bidirectional streaming RPC。
客户端发送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())
。
- 使用Java语言通过客户端发送Headers实现基本方法。
- Unary RPC
- 使用Java语言通过客户端发送Headers实现Unary RPC。
对Headers无感知。
- 使用Go语言通过客户端发送Headers实现Unary RPC。
在方法中直接调用
metadata.AppendToOutgoingContext(ctx,kv)
。 - 使用NodeJS语言通过客户端发送Headers实现Unary RPC。
在方法内直接使用基本方法。
- 使用Python语言通过客户端发送Headers实现Unary RPC。
在方法内直接使用基本方法。
- 使用Java语言通过客户端发送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。
在方法内直接使用基本方法。
- 使用Java语言通过客户端发送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。
在方法内直接使用基本方法。
- 使用Java语言通过客户端发送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。
在方法内直接使用基本方法。
- 使用Java语言通过客户端发送Headers实现Bidirectional streaming RPC。
Propaganda Headers
由于链路追踪需要将上游传递过来的链路元数据透传给下游,以形成同一条请求链路的完整信息,需要将服务端获取的Headers信息中,和链路追踪相关的Headers透传给向下游发起请求的客户端。
除了Java语言的实现,其他语言的通信模型方法都对Headers有感知,因此可以将服务端读取Headers-传递Headers-客户端发送Headers这三个动作有顺序地在4种通信模型方法内部实现。
Java语言读取和写入Headers是通过两个拦截器分别实现的,因此Propaganda Headers无法在一个顺序的流程里实现,且考虑到并发因素,以及只有读取拦截器知道链路追踪的唯一ID,所以无法通过最直觉的缓存方式搭建两个拦截器的桥梁。

在服务器拦截器读取阶段,通过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版本服务。
登录ASM控制台。
在左侧导航栏,选择 。
在网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理。
在网格详情页面左侧导航栏,选择 ,然后在右侧页面,单击使用YAML创建。
选择命名空间,在文本框中输入以下信息,然后单击创建。
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