正文
descriptors:
- entries:
- key: client_appid
value: client-a
token_bucket:
max_tokens: 10
tokens_per_fill: 10
fill_interval: 60s
- entries:
- key: client_appid
value: client-b
- key: path
value: /foo
token_bucket:
max_tokens: 100
tokens_per_fill: 100
fill_interval: 60s
这里的
client_appid
和 path 的值,通过如下在 route 的 ratelimit 配置中添加 actions 来设置。
route:
cluster: service_protected_by_rate_limit
rate_limits:
- actions:
- request_headers:
header_name: x-envoy-client-appid
descriptor_key: client_appid
- request_headers:
header_name: ":path"
descriptor_key: path
以上配置本质上就是通过从请求中提取一些特征,例如读取一些特定的 Header,然后再针对不同的请求分配不同的限流值。
对于更加复杂的场景,例如需要根据多个 Header 做逻辑判断时,我们通过 Envoy Filter 实现相关逻辑并设置到 metadata 中。然后在 ratelimit 的 actions 中从 metadata 中提取特征。例如,我们可以 patch 如下的配置到 envoy 中,生成限流使用的 metadata 数据。
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
subFilter:
name: istio.metadata_exchange
patch:
operation: INSERT_FIRST
value:
name: service.metadata.ratelimit
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |-
function envoy_on_request(request_handle)
custom_key_1 = build_custom_key_1()
custom_key_2 = build_custom_key_2()
request_handle:streamInfo():dynamicMetadata():set("metadata.custom.ratelimit", "request.info", {
customKey1 = custom_key_1,
customKey2 = custom_key_2
})
end
然后通过
metadata_key
的方式,拿到我们自己设置的限流相关的 metadata。
route:
cluster: inbound|8080|http|svc-a
rate_limits:
- actions:
- metadata:
descriptor_key: customKey1
metadata_key:
key: metadata.custom.ratelimit
path:
- key: request.info
- key: customKey1
- metadata:
descriptor_key: customKey2
metadata_key:
key: metadata.custom.ratelimit
path:
- key: request.info
- key: customKey2
其他功能
除了一些服务治理常见的功能,携程内部也有不少定制化的功能需要在 Service Mesh 中实现。
这部分需求部分是通过 Lua Filter 实现,部分是扩展 Envoy 编写了 C++ Filter 来实现的。
因为这 C++ Filter 这部分需求比较稳定,所以静态编译在 Sidecar 中也并不是个大问题。
如果可以通过 WebAssembly 实现当然是可以做得更灵活。但在我们项目启动的时候,Istio 的 WebAssembly 还未正式发布,后期我们会考虑引入 WebAssembly。
3.4 SDK 兼容
接入 Service Mesh 后的一大优势就是可以为 SOA SDK 做轻量化,仅保留基本的功能即可。
而 Service Mesh 的接入是一个长期的过程,应用是一批批接入的,同一个应用在不同的机房也有可能存在接入和不接入两种状态。让业务方写两个版本的代码肯定是不合适的。
因此我们在现有的 SOA SDK 中实现了无缝接入功能,原理也非常简单。
凡是接入 Service Mesh 的应用在发布时就会被注入一个环境变量,当 SOA SDK 探测到这个环境变量后,便会启动轻量化模式。
其中轻量化模式中被移出的功能包括:
这样不仅有利于业务方快速回滚,也可以方便业务方对两种 SOA 架构进行性能对比。
四、数据平面
4.1 HTTP 协议
携程当前主流的 SOA 传输协议还是 HTTP,这块的确慢了半拍,但这也更利于我们接入 Service Mesh。
Istio 本身对 HTTP 协议有着很好的支持,因此这部分并不需要我们做什么调整。
4.2 Dubbo 协议
携程在 2018 年的时候引入了 dubbo 协议作为 HTTP 协议的补充,在两年时间的发展中也积累了较多的用户群体。
我们的 dubbo 的调用方式依赖 Dubbo 框架中的 dubbo 协议。
部分应用扩展使用了压缩率更高的 protobuf 来做序列化并进一步使用 gzip 压缩提升性能。
dubbo 协议本身是四层的私有协议,在 Istio 中的支持力度远不如 HTTP。另外 Dubbo 3.0 中也将 gRPC 协议作为了新的传输协议。
如何让 dubbo 协议升级到 gRPC 成为 Service Mesh 落地必须解决的问题。
现状
当前携程内部通过 Dubbo 框架调开发服务端应用有近千个,对应的调用方就更多了。
并且考虑到携程内部使用 Node.js、Python 等语言的应用越来越多,新版升级必须满足如下的要求:
1)使用 gRPC 协议以支持 Service Mesh
2)使用 Dubbo 框架的业务方尽量不改或者少改代码
3)新协议注册的服务端符合 gRPC 的规范并使得其他语言客户端可以很方便地调用
4)不强制依赖 protobuf,能让用户保持 Code First 的编码习惯
技术选型
我们调研了当时主流的做法并通尝试得出三条可行的升级道路。
这里最大的难点是当前使用 dubbo 的旧服务不是基于 protobuf 编写契约的,所以不能直接通过依赖 protobuf 结构的 gRPC 发起调用。
【方案一】
依然用原来的序列化器将数据处理成二进制,在 gRPC 调用时 wrap 一个 protobuf 对象,用一个字段传递原来数据的二进制数据流,再用另外一些字段描述它的序列化方式。这也是 Dubbo 3.0 中透明升级 gRPC 协议时所使用的方案。
-
• 优点:
-
• 缺点:
-
• 这种方式对于标准的 gRPC 客户端不友好
-
• 两次序列化和反序列化影响性能
【方案二】
gRPC 标准中,没有规定 gRPC 强依赖 protobuf 做序列化器,gRPC 官方的 FAQ 中这样写道:
gRPC is designed to be extensible to support multiple content types. The initial release contains support for Protobuf and with external support for other content types such as FlatBuffers and Thrift, at varying levels of maturity.
那具体如何使用其他序列化器呢?
从协议角度,只需要改变
content-type
即可。例如想表达用 json 作为序列化格式,那具体内容为:
application/grpc+json
。