前言
Envoy 是一款面向 Service Mesh 的高性能网络代理服务。它与应用程序并行运行,通过以平台无关的方式提供通用功能来抽象网络。当基础架构中的所有服务流量都通过 Envoy 网格时,通过一致的可观测性,很容易地查看问题区域,调整整体性能。
Envoy也是istio的核心组件之一,以 sidecar 的方式与服务运行在一起,对服务的流量进行拦截转发,具有路由,流量控制等等强大特性。本系列文章,我们将不局限于istio,envoy的官方文档,从源码级别切入,分享Envoy启动、流量劫持、http 请求处理流程的进阶应用实例,深度分析Envoy架构。
本篇将是Envoy请求流程源码解析的第三篇,主要分享Envoy的outbound方向下篇,包含:接收请求、发送请求、接收响应、返回响应。注:本文中所讨论的issue和pr基于21年12月。
outbound方向
接收请求
1、client开始向socket写入请求数据
2、eventloop在触发read event后,transport_socket_.doRead中会循环读取加入read_buffer_,直到返回EAGAIN
3、
4、把buffer传入Envoy::Http::ConnectionManagerImpl::onData进行HTTP请求的处理
5、
6、如果codec_type是AUTO(HTTP1,2,3目前还不支持,在计划中)的情况下,会判断请求是否以PRI * HTTP/2为开始来判断是否http2
7、利用http_parser进行http解析的callback,ConnectionImpl::settings_静态初始化了parse各个阶段的callbacks
8、
envoy社区有讨论会将协议解析器从http_parser换成llhttp
https://github.com/envoyproxy...
https://github.com/envoyproxy... 使用解析器接口,重构http parser
https://github.com/envoyproxy...添加llhttp解析器的实现,暂时还没合并
9、
10、onMessageBeginBase
11、
12、
创建ActiveStream, 保存downstream的信息,和对应的route信息对于https,会把TLS握手的时候保存的SNI写入ActiveStream.requested_server_name_
13、onHeaderField,onHeaderValue 迭代添加header到current_header_map_中
14、解析完最后一个请求头后会执行 onHeadersComplete 把request中的一些字段(method, path, host )加入headers中
15、回调 onHeadersComplete, 依次回调onMessageComplete,onMessageCompleteBase,ServerConnectionImpl::onMessageComplete
这个请求解码是Envoy上下文的,它会执行Envoy的核心代理逻辑 —— 遍历HTTP过滤器链、进行路由选择
此过滤器当中判断请求过载
通过route上的cluster name从ThreadLocalClusterManager中查找cluster, 缓存在cached_cluster_info_中
根据配置构造在route上的filterChain (具体的filter实现是通过registerFactory方法注册进去,在createFilterChain的时候根据名称构造,比如istio-proxy的stats)
如果对应http connection manager上有trace配置
request header中有trace,就创建子span, sampled跟随parent span
如果header中没有trace,就创建root span, 并设置sampled
16、根据http connection manager上配置的filters (envoy.cors,envoy.fault,envoy.router),一个个执行decodeHeaders
这里主要写一下和envoy.router
(1)envoy.router
在构造RouteMatcher的时候会遍历virtual_hosts下的domains,并根据通配符的位置和domain的长度分为4个map<domain_len, std::unordered_map<domain, virtualHost>, std::greater<int64_t>>
default_virtual_host_`domain就是一个通配符(只允许存在一个)
wildcard_virtual_host_suffixes_domain中通配符在开头
wildcard_virtual_host_prefixes_domain中通配符在结尾
virtual_hosts_不包含通配
按照virtual_hosts_=>wildcard_virtual_host_suffixes_=>wildcard_virtual_host_prefixes_=>default_virtual_host_的顺序查找
同时按照map的迭代顺序(domain len降序)查找最先除去通配符后能匹配到的virtualhost,如果没有直接返回 404
在一个virtualhost上查找对应route和cluster
在通过domain匹配到virtualhost,会在那个virtualhost上匹配查找cluster,如果没匹配上,会直接返回404
match可以根据配置分为prefix,regex,path三种route进行匹配
如果存在weighted_clusters,会根据stream_id, 和clusters的weight进行分发,stream_id本身是每个请求独立随机生成,所以weighted_clusters的权重分发可以视为随机分发
(2)
没有route能匹配请求,返回 404no cluster match for URL
有配置directResponseEntry,直接返回
route上的clustername在clustermanager上找不到对应cluster,返回配置的clusterNotFoundResponseCode
当前处于maintenanceMode (和主动健康检查相关)
调用createConnPool获取upstream conn pool
根据 cluster上的features配置和USE_DOWNSTREAM_PROTOCOL来确定使用http1还是http2协议向上游发送请求
在ThreadLocalClusterManager上根据cluster name查询cluster
根据loadbalancer算法挑选节点(此处worker之间的负载均衡根据不同的负载均衡算法有的是独立的,比如round robin,只有同一个Worker上的才是严格的顺序)
根据节点和协议拿到连接池 (连接池由ThreadLocalClusterManager管理,各个Worker不共享)
没有做直接503,中止解析链
根据配置(timeout, perTryTimeout)确定本次请求的timeout
把之前生成的trace写入request header
对request做一些最终的修改,headers_to_removeheaders_to_add
host_rewrite``rewritePathHeader(路由的配置)
构造 retry和shadowing的对象
发送请求
发送请求部分也是在envoy.router中的逻辑
1、查看当前conn pool是否有空闲client
2、
如果存在空闲连接
根据downstream request和tracing等配置构造发往upstream的请求buffer
把buffer一次性移入write_buffer_, 立即触发Write Event
ConnectionImpl::onWriteReady随后会被触发
把write_ buffer_的内容写入socket发送出去
如果不存在空闲连接
根据max_pending_requests和max_connections判断是否可以创建新的连接(此处的指标为worker间共享),但是每个线程会向上游最少建立一条连接,也就是极端策略可能需要和工作线程数相关
根据配置设置新连接的socket options, 使用dispatcher.createClientConnection创建连接上游的连接,并绑定到eventloop
新建PendingRequest并加到pending_requests_头部
当连接成功建立的时候,会触发ConnectionImpl::onFileEvent
在onConnected的回调中停止connect_timer_;复用存在空闲连接时的逻辑,发送请求
3、在onRequestComplete里调用maybeDoShadowing进行流量复制
4、
shadowing流量并不会返回错误
shadowing 流量为asynclient发送,不会阻塞downstream,timeout也为global_timeout_
shadowing 会修改request header里的host 和 authority 添加-shadow后缀
5、根据global_timeout_启动响应超时的定时器
接收响应
1、eventloop 触发ClientConnectionImpl.ConnectionImpl上的onFileEvent的read ready事件
2、经过http_parser execute后触发onHeadersComplete后执行到UpstreamRequest::decodeHeaders
3、upstream_request_->upstream_host_->outlierDelector().putHttpResponseCode写入status code,更新外部检测的状态
4、
5、
6、根据返回结果、配置和retries_remaining_判断是否应该retry
根据internal_redirect_action的配置和response来确定是否需要redirect到新的host
返回响应
1、停止request_timer, 重置idle_timer
2、和向upstream发送请求一样的逻辑,发送响应给downstream
阅读源码总结
1、envoy当中各种继承,模板,组合使用的非常多,子类初始化时需要关注父类的构造函数做了什么
2、可以根据请求日志的信息,通过日志的顺序再到代码走一遍大体过程
3、善用各种调试工具,例如抓包,gdb,放开指标等,个人的经验 百分之90的问题日志+抓包+部分源码的阅读可以解决
ASM试用申请
Envoy是Istio中的Sidecar官方标配,是一个面向Service Mesh的高性能网络代理服务。
当前Service Mesh是Kubernetes上微服务治理的最佳实践,灵雀云微服务治理平台Alauda Service Mesh(简称:ASM)可完整覆盖微服务落地所需要的基础设施,让开发者真正聚焦业务。
如果您想深入体验ASM,扫描下方二维码即可报名!
附录:
关于重复header的rfc规范:
https://www.w3.org/Protocols/...
关于header大小写处理:
https://www.envoyproxy.io/doc...
关于修改header append行为:
https://www.envoyproxy.io/doc...
关于【云原生小课堂】
【云原生小课堂】是由灵雀云、Kube-OVN社区、云原生技术社区联合开设的公益性技术分享类专题,将以丰富详实的精品内容和灵活多样的呈现形式,持续为您分享云原生前沿技术,带您了解更多云原生实践干货。
在数字化转型的背景下,云原生已经成为企业创新发展的核心驱动力。作为国内最早将 Kubernetes 产品化的厂商之一,灵雀云从出生便携带“云原生基因”,致力于通过革命性的技术帮助企业完成数字化转型,我们期待着云原生给这个世界带来更多改变。
关注我们,学习更多云原生知识,一起让改变发生。
上一篇:云原生小课堂 | Envoy请求流程源码解析(一):流量劫持
下一篇:云原生小课堂 | Envoy请求流程源码解析(二):请求解析
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。