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

12次阅读

共计 4773 个字符,预计需要花费 12 分钟才能阅读完成。

2019-06-17 13:24 灵雀云 分类:istio 阅读(22) 评论(0) [编辑]
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

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

  1. 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:

  1. ReadFilter

listener.filter_chains.filters

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

onNewConnection()
onData(data, end_stream)

内置类型:

Envoy::Http::CodecClient 只在向上游的连接用到,且向上游的连接只有这个 filter,用于读取响应
envoy.http_connection_manager
处理 http 请求的主要 filter

envoy.tcp_proxy
envoy.redis_proxy

注册 filter:

  1. 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:

  1. 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:

  1. 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:

  1. 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:

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

  1. 匹配 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_ 不包含通配符

  1. 按照 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 中。

  1. istio 通过 mixer filter 实现了 check 和 report 功能。
正文完
 0