写点什么

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

作者:York
  • 2022 年 3 月 24 日
  • 本文字数:3635 字

    阅读完需:约 12 分钟

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

前言

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、


1648102484819310.png


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

5、


1648102526571691.png


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


1648102581616187.png


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

8、


1648102624458057.png


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


1648102702525736.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、


1648102745825699.png


10、onMessageBeginBase

11、


1648102780781343.png

    

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


1648102865470985.png


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

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


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


1648102996119332.png


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

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


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


1648103129216484.png


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

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


1648103165704248.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 (和主动健康检查相关)


1648103359787088.png


调用 createConnPool 获取 upstream conn pool


1648103386135899.png


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


1648103415469910.png


在 ThreadLocalClusterManager 上根据 cluster name 查询 cluster


1648103453546419.png


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


1648103490864813.png


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

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


1648103532637487.png


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


1648103573896289.png


把之前生成的 trace 写入 request header

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


1648103596654574.png

         

构造 retry 和 shadowing 的对象


1648103617422356.png


发送请求

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

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

2、


1648103670375067.png


如果存在空闲连接

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

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

ConnectionImpl::onWriteReady 随后会被触发

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

如果不存在空闲连接


1648103720936663.png


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

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

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

4、


1648103840371309.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、


1648103936461120.png


5、


1648103953669651.png


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

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


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


1648104098885247.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


关于【云原生小课堂】


1648104153225969.png


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

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

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


发布于: 刚刚阅读数: 2
用户头像

York

关注

云原生的美男子YORK 2021.01.07 加入

云原生技术社区为云原生技术实践联盟(CNBPA)旗下技术社区,专注泛云原生全栈云前沿技术和落地实践的布道。分享容器、Kubernetes、DevOps、Service Mesh、Serverless、数据库、中间件等技术干货。

评论

发布
暂无评论
云原生小课堂|Envoy请求流程源码解析(三):请求解析_云原生_York_InfoQ写作平台