解析envoy处理http请求(上):filter架构

Envoy是istio的核心组件之一,以sidecar的方式与服务运行在一起,对服务的流量进行拦截转发。 具有路由,流量控制等等强大特性。

Envoy利用libevent实现了基于事件触发的异步架构,所有的网络阻塞操作包括 accept,read, connect, write 都是由eventloop进行callback触发。

本文以istio1.1所对应的Envoy版本进行源码流程分析。

名词解释:

  • 下游: 发送请求给Envoy的服务,client
  • 上游:接收Envoy发送的请求,并返回响应的服务, server

Filter流程图

下面的流程图为istio架构下,访问80端口的http服务的流程。

1. Client向Envoy的15001 port建立连接,被转到80 port的Listener

2.Client发送请求给Envoy,Envoy经过路由后找到上游Server,并发送请求

3.上游Server返回响应给Envoy,Envoy利用event_active立即返回响应给下游的client

4.  Client主动断开下游到Envoy的连接

5. Server主动断开Envoy到上游的连接

Filter分类

1. ListenerFilter

listener.listener_filters

用于接收到下游新连接的时候回调

接口:

  • onAccept(callback)

内置类型:

  • envoy.listener.original_dst (istio中的15001端口常用)

    根据iptables转换之前的dst port,查找到真实的Listener,查找到Listener会根据新的Listener的配置继续处理

  • envoy.listener.tls_inspector

    注册read callback,识别tls和进行tls握手,握手结束后会进行下一步的filterChain的处

注册filter:


2. ReadFilter

listener.filter_chains.filters

  1. 用于接受到下游新连接的时候回调
  2. 上游或者下游连接上有数据可以读取的时候的回调,一般用于协议的解析

接口:

  • onNewConnection()
  • onData(data, end_stream)

内置类型:

  • Envoy::Http::CodecClient  只在向上游的连接用到,且向上游的连接只有这个filter,用于读取响应
  • envoy.http_connection_manager

    处理http请求的主要filter

  • envoy.tcp_proxy
  • envoy.redis_proxy

注册filter:

3. WriteFilter

listener.filter_chains.filters

用于向上游的连接写入数据的时候回调(目前内置的writeFilter没有http相关的)

接口:

  • onWrite(data, end_stream)

内置类型:

  • envoy.filters.network.dubbo_proxy
  • envoy.mongo_proxy
  • envoy.filters.network.mysql_proxy
  • envoy.filters.network.zookeeper_proxy

注册filter:

4. StreamDecodeFilter ( envoy.http_connection_manager下独有的filter)

listener.filter_chains.filters[envoy.http_connection_manager].http_filters

用于解析http请求各个部分的时候回调执行

接口:

  • decodeHeaders(headers, end_stream)
  • decodeData(data, end_stream)
  • decodeTrailers(HeaderMaps& trailers)
  • decodeComplete()

内置类型:

  • envoy.cors
  • envoy.fault
  • envoy.router

注册filter:

5. StreamEncodeFilter 

(envoy.http_connection_manager 下独有的filter)

listener.filter_chains.filters[envoy.http_connection_manager].http_filters

发送响应各个部分给下游client的时候执行

接口:

  • encode100ContinueHeaders(headers)
  • encodeHeaders(headers, end_stream)
  • encodeData(data, end_stream)
  • encodeTrailers(HeaderMap& trailers)
  • encodeMetadata(metadata_map)
  • encodeComplete()

内置类型:

  • envoy.cors
  • envoy.fault
  • envoy.lua

注册filter:

6. PerFilterConfig (并不是filter,只是为4,5中的http_filter提供route级别的配置数据)

route.virtual_hosts.per_filter_config

位于route上的字段,只有当对应Listener上http_connection_manager包含对应httpfilter的时候才有用,结构为 map<string, Struct> 用法由filter自己实现

7.ConnectionCallbacks

listener.filter_chains.filters

接口:

  • onEvent(event)

事件分为 RemoteClose, LocalClose, Connected  会在各个阶段调用

  • onAboveWriteBufferHighWatermark()
  • onBelowWriteBufferLowWatermark()
  • route.virtual_hosts.per_filter_config

类型:

  • Envoy::Http::CodeClient只在向上游的连接用到,且向上游的连接只有这个filter,用于检测上游连接断开
  • envoy.http_connection_manager
  • envoy.tcp_proxy
  • envoy.redis_proxy

注册filter:

8. access_log_handlers

接口:

  • log(request_headers, response_headers, response_trailers, stream_info)

类型:

  • Envoy::Http::Mixer::Filter
    istio为Envoy添加的Filter,在AccessLogHandlers这边主要用于Report
  • Envoy::Extensions::AccessLoggers::File::FileAccessLog
  • Envoy::Extensions::HttpGrpc::File::HttpGrpcAccessLog
  • Envoy::Extensions::HttpFilters::TapFilter::Filter
Filter流程中关键步骤解析

1. findActiveListenerByAddress
根据socket的localaddress和port选择合适的Listener处理

1.利用syscall找到iptables转化之前的dst port (如果有envoy.listener.original_dst)
os_syscalls.getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, &orig_addr, &addr_len)

2.先匹配address和port和socket都一致的Listener,如果没找到再找port一致,address==0.0.0.0的Listener

2. 匹配request选择route和cluster

在构造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_ 不包含通配符

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

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

3.在一个virtualhost上查找对应route和cluster

  • 在通过domain匹配到virtualhost,会在那个virtualhost上匹配查找cluster,如果没匹配上,会直接返回404

  • match可以根据配置分为 prefix, regex, path 三种route进行匹配
  • 如果存在weighted_clusters ,会根据stream_id , 和clusters的weight进行分发,stream_id 本身是每个请求独立随机生成,所以weighted_clusters 的权重分发可以视为随机分发

3.负载均衡策略选择endpoint
1.在上一步查找到了clusterName, 对于clusterEntry,都是从ThreadLocalClusterManagerImpl 中取出,每个worker都一份自己的数据

2.对于ThreadLocalClusterManagerImpl , 维护了多份根据类型和协议区分的map

其中http协议才用的是host_http_conn_pool_map_  这个map,大致的结构为  map<host, map<protocol, connpool>> , 因为http分为 Http10, Http11, Http2 不同协议的connpool都是独立的

对于http请求,会从 host_http_conn_pool_map_ 中查到对应的connpool,每个worker都维护了一份自己独有的threadlocal connpool

Mixer

mixerclient是istio基于Envoy,添加filter进行check和report的模块

注册到Envoy

注册到Envoy主要就是两行


第一行注册了 StreamDecodeFilter 和 StreamEncodeFilter, Http::Mixer::Filter 在decodeHeader 这个hook中实现了Check,发送attributes给mixerserver进行检查

第二行注册了 AccessLogHandler ,这个会在 一个请求结束的时候执行

在Mixer filter的log method中,会进行report操作

 

可以看到Mixer虽然是每个请求结束都会调用log,但实际的上报mixer是批量发送(累计一定大小或者到达一定时间间隔)。

总结

1.可以在Envoy处理请求的各个阶段加入filter来定制化功能,可以自己编写c++的filter,用REGISTER_FACTORY 注册到对应的Factory map中。

2. istio通过mixer filter实现了check和report功能。

K8S中文社区微信公众号

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址