Envoy Http请求处理流程 -事件模型和连接管理

Envoy是istio的核心组件之一,以sidecar的方式与服务运行在一起,对服务的流量进行拦截转发。 具有路由,流量控制等等强大特性。 本文以istio1.1所对应的Envoy版本进行源码流程分析。

Envoy并发架构

摘自

  • Envoy进程由一个Main Thread和多个Worker Thread 组成
  • 每个Main和Worker包含一个eventloop,所有的处理都是由eventloop触发开始
  • Main负责xDS等功能,Worker负责处理连接和请求
  • 当一个client向Envoy建立连接的时候,因为所有Worker的EventLoop都注册了listening fd,会由内核决定分配给哪个Worker
  • 当一个下游client连接到了Envoy,在保持连接不断的情况下,会和同一个Worker进行通讯

libevent函数

  • evconnlistener_new 把一个 已经bind port 的listening fd和callback注册到eventloop,当accept到新连接的时候会触发callback。Envoy里面采用这个函数把同一个listening fd注册到所有的Worker的eventloop中,当新连接来的时候,由内核选择应该分发给哪个Worker
  • event_assign 把一个fd和一个callback注册到eventloop,当read write closed事件触发的时候触发callback
  • event_active 立即触发一个eventloop中的event,执行callbac

事件触发各阶段

  1. client向Envoy建立连接

  1. client发送请求到Envoy,Envoy挑选节点向上游Server建立连接(如果连接池有空闲连接直接发送请求)

  1. Envoy向上游建连接成功,发送请求

  1. 上游server返回响应给Envoy

  1. Envoy返回请求给下游的client

Cluster管理 (HTTP1)

层次结构图

  • 上面的实线表示下方的头部是上方的属性之一
  • 虚线表示两者相关
  • ClusterManagerImpl是Envoy内的单例,用于管理多个Worker上ThreadLocalClusterManagerImpl
  • ThreadLocalCusterManagerImpl 每个Worker都拥有一个,用于管理上游的连接和负载均衡上下文

连接结构简介

  1. 负载均衡器只挑选host,然后会从conn_pool_map取出对应host的连接池

  2. 每个worker都都包含自己独立的连接池和负载均衡上下文

(此处有发现设置RoundRobin负载均衡策略的时候,只有client保持长连接(不换worker)的情况下,才是严格的轮询)

  1. 同一个上游节点的不同协议(http10, http11, http2, tcp)的连接池都是分开的

连接管理
对于同一个Worker,同一个Host,同一个协议,Envoy会维护一个连接池,连接池中http1有关属性如下(一下情况没有对Limit做说明,实际各个阶段会有stats和config limit来进行限制):

  • busy_clients_ 当前发送了请求,还没处理响应的connection
    变多:

    1. 有新的请求需要发送的时候,如果ready_clients非空, 就会把ready_clients_.front()移到busy_clients.front(), 并发送请求;
    2. 向上游的连接connected时,如果 (!pending_requests_.empty() && !ready_clients_.empty()) 就会把ready_clients_.front() 移到busy_clients_;
    3. 当有连接close的时候,如果pending_requests_.size() > (ready_clients_.size() + busy_clients_.size()),就会创建新的connection到busy_clients_并发送请求;

    变少:

    1. connpool被删除,busy_clients_会被清空;
    2. 连接关闭时,如果是正在包含stream_wrapper (正在发送请求)的client,就会从busy_clients移除;
  • ready_clients_ 空闲连接
    变多:

    1. ((连接connected的时候 || 有响应完成的时候) && pending_requests_ 为空) ,
      就会加入ready_clients

    变少:

    1. 在有新的请求需要发送的时候,如果ready_clients非空, 就会把ready_clients_.front()移到busy_clients.front()

    2. 向上游的连接connected时,如果
      (!pending_requests_.empty() && !ready_clients_.empty())
      就会把 ready_clients_.front() 移到busy_clients_

    3. connpool被删除,ready_clients_会被清空

    4. 空闲的连接被关闭的时候,会从ready_clients_ 中移除

  • pending_requests_ 代发送的请求

    变多:

    1. ready_clients 为空的时候,有新的请求,就会加入到pending_requests

    变少:

    1. 当上游连接connected时,发送请求到连接,并从pending_requests 中移除
    2. 请求被终止的时候(超时,或者收到上游的响应结束 ),就会从pending_requests 中移除

总结

1.每个Worker的连接池和负载均衡上下文都是独立的。

2.连接池内的连接都是同一worker,同一upstream host,同一协议。