云原生小课堂|Envoy请求流程源码解析(三):请求解析
2022-03-24

云原生小课堂 envoy3 副本 (1).png

前言

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、

1-接收请求-序号3后-合 (1).png

4、把buffer传入Envoy::Http::ConnectionManagerImpl::onData进行HTTP请求的处理

5、

2-接收请求-序号5后-合 (1).png

6、如果codec_type是AUTO(HTTP1,2,3目前还不支持,在计划中)的情况下,会判断请求是否以PRI * HTTP/2为开始来判断是否http2 

3-接收请求-序号6后-合 (1).png

7、利用http_parser进行http解析的callback,ConnectionImpl::settings_静态初始化了parse各个阶段的callbacks

8、

4-接收请求-序号8后-合 (1).png

envoy社区有讨论会将协议解析器从http_parser换成llhttp

5-envoy社区后 (1).png

https://github.com/envoyproxy/envoy/issues/5155

https://github.com/envoyproxy/envoy/pull/15263/files 使用解析器接口,重构http parser

https://github.com/envoyproxy/envoy/pull/15814添加llhttp解析器的实现,暂时还没合并

9、

6-序号9之后-合 (1).png

10、onMessageBeginBase

11、

7-序号11之后-合 (1).png    

12、
创建ActiveStream, 保存downstream的信息,和对应的route信息对于https,会把TLS握手的时候保存的SNI写入ActiveStream.requested_server_name_  

8-序号12之后 (1).png

13、onHeaderField,onHeaderValue 迭代添加header到current_header_map_中

14、解析完最后一个请求头后会执行 onHeadersComplete 把request中的一些字段(method, path, host )加入headers中

9-序号14之后 (1).png

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配置

10-序号15-trace配置之后 (1).png

request header中有trace,就创建子span, sampled跟随parent span

如果header中没有trace,就创建root span, 并设置sampled

11-序号15-设置sampled之后-合 (1).png

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_不包含通配

12-不包含通配之后-合 (1).png

按照virtual_hosts_=>wildcard_virtual_host_suffixes_=>wildcard_virtual_host_prefixes_=>default_virtual_host_的顺序查找

同时按照map的迭代顺序(domain len降序)查找最先除去通配符后能匹配到的virtualhost,如果没有直接返回 404

13-返回404-合 (1).png

在一个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 (和主动健康检查相关)

14-主动健康检查-合 (1).png

调用createConnPool获取upstream conn pool

15-conn pool (1).png

根据 cluster上的features配置和USE_DOWNSTREAM_PROTOCOL来确定使用http1还是http2协议向上游发送请求

16-上游发送请求-合 (1).png

在ThreadLocalClusterManager上根据cluster name查询cluster

17-cluster (1).png

根据loadbalancer算法挑选节点(此处worker之间的负载均衡根据不同的负载均衡算法有的是独立的,比如round robin,只有同一个Worker上的才是严格的顺序)

18-严格的顺序-合 (1).png

根据节点和协议拿到连接池 (连接池由ThreadLocalClusterManager管理,各个Worker不共享)

没有做直接503,中止解析链

19-中止解析链 (1).png

根据配置(timeout, perTryTimeout)确定本次请求的timeout

20-timeout后-合 (1).png

把之前生成的trace写入request header

对request做一些最终的修改,headers_to_remove``headers_to_add``host_rewrite``rewritePathHeader(路由的配置)

21-路由的配置后 (1).png         

构造 retry和shadowing的对象

22-shadowing的对象后 (1).png


发送请求

发送请求部分也是在envoy.router中的逻辑

1、查看当前conn pool是否有空闲client

2、

23-发送请求-序号2后 (1).png

如果存在空闲连接

根据downstream request和tracing等配置构造发往upstream的请求buffer

把buffer一次性移入write_buffer_, 立即触发Write Event

ConnectionImpl::onWriteReady随后会被触发

把write_ buffer_的内容写入socket发送出去

如果不存在空闲连接

24-不存在空闲连接后-合 (1).png

根据max_pending_requests和max_connections判断是否可以创建新的连接(此处的指标为worker间共享),但是每个线程会向上游最少建立一条连接,也就是极端策略可能需要和工作线程数相关
根据配置设置新连接的socket options, 使用dispatcher.createClientConnection创建连接上游的连接,并绑定到eventloop
新建PendingRequest并加到pending_requests_头部
当连接成功建立的时候,会触发ConnectionImpl::onFileEvent

在onConnected的回调中停止connect_timer_;复用存在空闲连接时的逻辑,发送请求

3、在onRequestComplete里调用maybeDoShadowing进行流量复制

4、

25-序号4后 (1).png

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、

26-接收响应-序号4后-合 (1).png

5、

27-接收响应-序号5后 (1).png

6、根据返回结果、配置和retries_remaining_判断是否应该retry

根据internal_redirect_action的配置和response来确定是否需要redirect到新的host

28-序号6-新的host后-合 (1).png


返回响应

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,扫描下方二维码即可报名!

试用申请二维码.png


附录:

关于重复header的rfc规范:

https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2

关于header大小写处理:

https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/header_casing

关于修改header append行为:

https://www.envoyproxy.io/docs/envoy/latest/version_history/v1.15.1


关于【云原生小课堂】

小课堂 (1).png

【云原生小课堂】是由灵雀云、Kube-OVN社区、云原生技术社区联合开设的公益性技术分享类专题,将以丰富详实的精品内容和灵活多样的呈现形式,持续为您分享云原生前沿技术,带您了解更多云原生实践干货。

在数字化转型的背景下,云原生已经成为企业创新发展的核心驱动力。作为国内最早将 Kubernetes 产品化的厂商之一,灵雀云从出生便携带“云原生基因”,致力于通过革命性的技术帮助企业完成数字化转型,我们期待着云原生给这个世界带来更多改变。

关注灵雀云,学习更多云原生知识,一起让改变发生。


上一篇:云原生小课堂 | Envoy请求流程源码解析(一):流量劫持

下一篇:云原生小课堂 | Envoy请求流程源码解析(二):请求解析

为您数字化转型提供更为完善的解决方案和更加优质的全栈服务。

申请试用
© 2024 All Rights Reserved. 灵雀云 版权所有 备 案号:京ICP备15011102号-2      隐私条款
电话咨询 在线客服 微信咨询 公众号