- Authors: Albumen, Jun Liu
通过不同语言版本的 Dubbo3 SDK 直接实现 xDS 协议解析,实现 Dubbo 与 Control Plane 的直接通信,进而实现控制面对流量管控、服务治理、可观测性、安全等的统一管控,规避 Sidecar 模式带来的性能损耗与部署架构复杂性。
经典的 Sidecar Mesh 部署架构有很多优势,如平滑升级、多语言、业务侵入小等,但也带来了一些额外的问题,比如:
- Proxy 带来的性能损耗,在复杂拓扑的网络调用中将变得尤其明显
- 流量拦截带来的架构复杂性
- Sidecar 生命周期管理
- 部署环境受限,并不是所有环境都满足 Sidecar 流量拦截条件
- 一定有更灵活的选择
针对 Sidecar Mesh 模型的问题,Dubbo 社区自很早之前就做了 Dubbo 直接对接到控制面的设想与思考,并在国内开源社区率先提出了 Proxyless Mesh 的概念,当然就 Proxyless 概念的说法而言,最开始是谷歌提出来的。
Proxyless 模式使得微服务又回到了 2.x 时代的部署架构,如上图所示,和我们上面看的 Dubbo 经典服务治理模式非常相似,所以说这个模式并不新鲜, Dubbo 从最开始就是这么样的设计模式。但相比于 Mesh 架构,Dubbo2 并没有强调控制面的统一管控,而这点恰好是 Service Mesh 所强调的,强调对流量、可观测性、证书等的标准化管控与治理,也是 Mesh 理念先进的地方。
在 Dubbo3 Proxyless 架构模式下,Dubbo 进程将直接与控制面通信,Dubbo 进程之间也继续保持直连通信模式,我们可以看出 Proxyless 架构的优势:
- 没有额外的 Proxy 中转损耗,因此更适用于性能敏感应用
- 更有利于遗留系统的平滑迁移
- 架构简单,容易运维部署
- 适用于几乎所有的部署环境
启动参数读取 特有的配置 应用级服务发现
- xDS 官方说明
https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol
● Listener Discovery Service (LDS): Returns Listener resources. Used basically as a convenient root for the gRPC client's configuration. Points to the RouteConfiguration. ● Route Discovery Service (RDS): Returns RouteConfiguration resources. Provides data used to populate the gRPC service config. Points to the Cluster. ● Cluster Discovery Service (CDS): Returns Cluster resources. Configures things like load balancing policy and load reporting. Points to the ClusterLoadAssignment. ● Endpoint Discovery Service (EDS): Returns ClusterLoadAssignment resources. Configures the set of endpoints (backend servers) to load balance across and may tell the client to drop requests.
- 协议基础:protobuf + gRPC
实现方案 1:使用 io.grpc 的依赖手动创建原生 gRPC 服务 实现方案 2:使用 Dubbo 3.0 的新协议
- 测试环境控制平面 mock
Envoy 提供的 Java 实例控制平面 https://github.com/envoyproxy/java-control-plane
- Java 版协议支持
Envoy 提供的 Java 版控制平面 API https://search.maven.org/search?q=g:io.envoyproxy.controlplane
协议可利用内容
● LDS: envoy.api.v2.Listener ● RDS: envoy.api.v2.RouteConfiguration ● CDS: envoy.api.v2.Cluster ● EDS: envoy.api.v2.ClusterLoadAssignment
LDS
Listener 发现协议,在 sidecar 部署模式下用户配置本地监听器,由于 Dubbo 去接入 xDS 本身就是一个 proxy-less 的模式,所以 LDS 很多的配置是不需要考虑的。 针对 LDS 协议,需要做的有获取适配的监听器用于获取路由配置。 对于 LDS 协议,有些控制平面并不能保证获取到的监听器是唯一的,所以需要兼容获取到多个监听器时的配置情况。
RDS
Route 发现协议,通过路由配置可以对请求的地址进行匹配,经过一定的策略后获取对应的上游 Cluster 集群。 针对 RDS 协议,可以在基于 Listener 监听器的配置,获取到一组对应的路由协议。然后根据服务自省框架对应的服务名筛选出对应的 Cluster 集群。 对于 RDS 协议,有些控制平面并不能保证获取到的l路由组是唯一的,所以需要兼容获取到多个路由组时的配置情况。 在 RDS 的结果中包括了流控还有重试的配置,这些可以作为路由配置和负载均衡部分的默认配置来源。
CDS
Cluster 发现协议,可以获取到对应集群内的配置(如超时时间、重试限制、元数据信息等),是基于 RDS 获取的。 CDS 的结果可以作为路由配置和负载均衡部分的配置来源。
EDS
Endpoint 发现协议,可以获取到集群中对应节点信息,即是服务间通信的底层节点信息。 通过 EDS 的结果,可以获取到对应的节点地址信息、权重信息、健康信息,进而组成服务自省框架最基础的实例信息。
接入模型设计
采用注册中心的模式对接,配置通过 ServiceInstance 运行时配置传出,通过 Invoker 传递到服务调用时。
服务发现逻辑
xDS 接入以注册中心的模式对接,节点发现同其他注册中心的服务自省模型一样,对于 xDS 的负载均衡和路由配置通过 ServiceInstance 的动态运行时配置传出,在构建 Invoker 的时候将配置参数传入配置地址。
服务调用逻辑
在 RPC 调用链路中,当获取负载均衡策略时根据 Invoker 的参数信息构建负载均衡信息(适配 EDS 的权重信息),对于集群的重试逻辑配置则工具 RDS 和 CDS 的配置进行修改。
协议内容示例
LDS
获取默认本地监听器,根据 Dubbo 支持的 RPC 协议读取过滤链中对应 "routeConfigName" 字段
{
"name": "0.0.0.0_9080",
"address": {
"socketAddress": {
"address": "0.0.0.0",
"portValue": 9080
}
},
"filterChains": [
{
"filterChainMatch": {
"applicationProtocols": [
"http/1.0",
"http/1.1",
"h2c"
]
},
"filters": [
{
"name": "envoy.filters.network.http_connection_manager",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"statPrefix": "outbound_0.0.0.0_9080",
"rds": {
"configSource": {
"ads": {},
"resourceApiVersion": "V3"
},
"routeConfigName": "9080"
},
"httpFilters": ···,
"tracing": {
"clientSampling": {
"value": 100
},
"randomSampling": {
"value": 100
},
"overallSampling": {
"value": 100
}
},
"streamIdleTimeout": "0s",
"accessLog": ···,
"useRemoteAddress": false,
"generateRequestId": true,
"upgradeConfigs": [
{
"upgradeType": "websocket"
}
],
"normalizePath": true
}
}
]
},
{
"filterChainMatch": {},
"filters": ···,
"name": "PassthroughFilterChain"
}
],
"deprecatedV1": {
"bindToPort": false
},
"listenerFilters": [
···
],
"listenerFiltersTimeout": "5s",
"continueOnListenerFiltersTimeout": true,
"trafficDirection": "OUTBOUND"
}
RDS
根据 LDS 的 "routeConfigName" 参数获取 RDS 匹配服务名和 "domains" 字段,读取 "cluster" 字段
{
"name": "9080",
"virtualHosts": [
···,
{
"name": "details.default.svc.cluster.local:9080",
"domains": [
"details.default.svc.cluster.local",
"details.default.svc.cluster.local:9080",
"details",
"details:9080",
"details.default.svc.cluster",
"details.default.svc.cluster:9080",
"details.default.svc",
"details.default.svc:9080",
"details.default",
"details.default:9080",
"172.20.206.174",
"172.20.206.174:9080"
],
"routes": [
{
"name": "default",
"match": {
"prefix": "/"
},
"route": {
"cluster": "outbound|9080||details.default.svc.cluster.local",
"timeout": "0s",
"retryPolicy": {
"retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
"numRetries": 2,
"retryHostPredicate": [
{
"name": "envoy.retry_host_predicates.previous_hosts"
}
],
"hostSelectionRetryMaxAttempts": "5",
"retriableStatusCodes": [
503
]
},
"maxGrpcTimeout": "0s"
},
"decorator": {
"operation": "details.default.svc.cluster.local:9080/*"
}
}
],
"includeRequestAttemptCount": true
},
{
"name": "productpage.default.svc.cluster.local:9080",
"domains": [
"productpage.default.svc.cluster.local",
"productpage.default.svc.cluster.local:9080",
"productpage",
"productpage:9080",
"productpage.default.svc.cluster",
"productpage.default.svc.cluster:9080",
"productpage.default.svc",
"productpage.default.svc:9080",
"productpage.default",
"productpage.default:9080",
"172.20.132.203",
"172.20.132.203:9080"
],
"routes": [
{
"name": "default",
"match": {
"prefix": "/"
},
"route": {
"cluster": "outbound|9080||productpage.default.svc.cluster.local",
"timeout": "0s",
"retryPolicy": {
"retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
"numRetries": 2,
"retryHostPredicate": [
{
"name": "envoy.retry_host_predicates.previous_hosts"
}
],
"hostSelectionRetryMaxAttempts": "5",
"retriableStatusCodes": [
503
]
},
"maxGrpcTimeout": "0s"
},
"decorator": {
"operation": "productpage.default.svc.cluster.local:9080/*"
}
}
],
"includeRequestAttemptCount": true
},
{
"name": "ratings.default.svc.cluster.local:9080",
"domains": [
"ratings.default.svc.cluster.local",
"ratings.default.svc.cluster.local:9080",
"ratings",
"ratings:9080",
"ratings.default.svc.cluster",
"ratings.default.svc.cluster:9080",
"ratings.default.svc",
"ratings.default.svc:9080",
"ratings.default",
"ratings.default:9080",
"172.20.66.58",
"172.20.66.58:9080"
],
"routes": [
{
"name": "default",
"match": {
"prefix": "/"
},
"route": {
"cluster": "outbound|9080||ratings.default.svc.cluster.local",
"timeout": "0s",
"retryPolicy": {
"retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
"numRetries": 2,
"retryHostPredicate": [
{
"name": "envoy.retry_host_predicates.previous_hosts"
}
],
"hostSelectionRetryMaxAttempts": "5",
"retriableStatusCodes": [
503
]
},
"maxGrpcTimeout": "0s"
},
"decorator": {
"operation": "ratings.default.svc.cluster.local:9080/*"
}
}
],
"includeRequestAttemptCount": true
},
{
"name": "reviews.default.svc.cluster.local:9080",
"domains": [
"reviews.default.svc.cluster.local",
"reviews.default.svc.cluster.local:9080",
"reviews",
"reviews:9080",
"reviews.default.svc.cluster",
"reviews.default.svc.cluster:9080",
"reviews.default.svc",
"reviews.default.svc:9080",
"reviews.default",
"reviews.default:9080",
"172.20.193.13",
"172.20.193.13:9080"
],
"routes": [
{
"name": "default",
"match": {
"prefix": "/"
},
"route": {
"cluster": "outbound|9080||reviews.default.svc.cluster.local",
"timeout": "0s",
"retryPolicy": {
"retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
"numRetries": 2,
"retryHostPredicate": [
{
"name": "envoy.retry_host_predicates.previous_hosts"
}
],
"hostSelectionRetryMaxAttempts": "5",
"retriableStatusCodes": [
503
]
},
"maxGrpcTimeout": "0s"
},
"decorator": {
"operation": "reviews.default.svc.cluster.local:9080/*"
}
}
],
"includeRequestAttemptCount": true
}
],
"validateClusters": false
}
CDS
根据 "cluster" 字段获取集群配置信息
{
"transportSocketMatches": ···,
"name": "outbound|9080||details.default.svc.cluster.local",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
},
"serviceName": "outbound|9080||details.default.svc.cluster.local"
},
"connectTimeout": "10s",
"circuitBreakers": {
"thresholds": [
{
"maxConnections": 4294967295,
"maxPendingRequests": 4294967295,
"maxRequests": 4294967295,
"maxRetries": 4294967295
}
]
},
"metadata": {
"filterMetadata": {
"istio": {
"config": "/apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/details"
}
}
},
"filters": ···
}
EDS
根据 "cluster" 字段获取节点信息
{
"name": "outbound|9080||ratings.default.svc.cluster.local",
"addedViaApi": true,
"hostStatuses": [
{
"address": {
"socketAddress": {
"address": "172.21.128.222",
"portValue": 9080
}
},
"stats": ···,
"healthStatus": {
"edsHealthStatus": "HEALTHY"
},
"weight": 1,
"locality": {}
}
]
}