└── envoy ├── 20190603220615.png ├── envoy-ads.md ├── http-request-notes.md ├── 解析envoy处理http请求(一)| filter架构.md └── 解析envoy处理http请求(二)| 事件模型与连接管理.md /envoy/20190603220615.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzdx/notes/e6cb964048ed401d2f47b7ead89993b77e50c16a/envoy/20190603220615.png -------------------------------------------------------------------------------- /envoy/envoy-ads.md: -------------------------------------------------------------------------------- 1 | # Envoy 核心模块 2 | 3 | ## libevent 4 | 5 | 1. `event_base_new()` 6 | 7 | 创建event_base对象 8 | 9 | 2. `event_base_loop(base, flag)` 10 | 11 | 创建event loop, 接受event的注册和处理callback 12 | 13 | base: even_base对象 14 | 15 | flag: 16 | 17 | 默认(0x00)是当前event_base没有事件注册的时候返回 18 | 19 | ​ Worker 都是使用了默认flag 20 | 21 | `EVLOOP_ONCE: 0x01` 等待event变active, 运行后退出 22 | 23 | `EVLOOP_NONBLOCK: 0x02` 不会等待,如果有active event,会立即执行对应callback,执行完毕会立刻退出 24 | 25 | `EVLOOP_NO_EXIT_ON_EMPTY: 0x04 ` 即使没有事件注册也不会退出,直到调用了 `event_base_loopbreak`, `event_base_loopexit` 或者出现错误 26 | 27 | ​ GuardDog使用了`EVLOOP_NO_EXIT_ON_EMPTY` flag 28 | 29 | 参考: 30 | 31 | 32 | 3. `event_base_loopexit(base, timeval)` 33 | 34 | 使一个event_base退出event loop 35 | 36 | timevar: 多长时间后event_base会被终止, NULL就表示运行完所有当前active events就会退出 37 | 38 | 4. `event_assign(ev, base, fd, events, callback, arg)` 39 | 40 | 设置一个event关联callback 41 | 42 | ev: event struct, 标识符 43 | 44 | fd: 监听的文件描述符 45 | 46 | events: 监听的事件, 类似 EV_READ, EV_WRITE 47 | 48 | callback: event发生的时候触发的回调函数 49 | 50 | args: 传递给callback的参数 51 | 52 | 5. `evtimer_assign(ev, base, callback, arg)` 53 | 54 | ​ `event_assign(ev, base, -1, 0, callback, arg)` 55 | 56 | 6. `evsignal_assign(ev, base, signal, callback, arg)` 57 | 58 | ​ `event_assign(ev, base, signal, EV_SIGNAL|EV_PERSIST, cb, arg)` 59 | 60 | 7. `evconnlistener_new(base, callback, ptr, flags, backlog, fd)` 61 | 62 | 会对一个已经bind的fd利用event loop 进行listen操作, 并设置backlog 63 | 64 | ​ ptr: 传递给callback的参数 65 | 66 | ​ flags: 67 | 68 | ​ backlog: 已经tcp握手完成,但还没有被accept的fd队列大小 69 | 70 | 8. `event_active(event, res, ncalls)` 71 | 72 | 立即active一个event 73 | 74 |  event: active的event对象 75 | 76 | res: 事件的类型,会传递给callback (EV_TIMEOUT, EV_READ,EV_WRITE …) 77 | 78 | ncalls: 废弃字段 79 | 80 | 9. `event_add(event, timeout)` 81 | 82 | 把event加入pending队列, 并设置timeout 83 | 84 | timeout: event会被执行当timeout过期或者特定条件触发的时候,NULL 永不过期,只有当event_assign或者event_new时设置的条件触发的时候才会执行 85 | 86 | 10. `event_del(event)` 87 | 88 | 把一个event从监听的events中删除 89 | 90 | ​ 91 | 92 | 11. `evbuffer_new()` 93 | 94 | 返回一个空的evbuffer对象 95 | 96 | 12. `evbuffer_add(buf, data, size)` 97 | 98 | 把data加到buf的尾部 99 | 100 | buf: evbuffer对象 101 | 102 | 13. `evbuffer_add_reference(outbuf, data, size, cleanupfn, cleanupfn_arg)` 103 | 104 | 无拷贝的引用一段内存到evbuffer 105 | 106 | cleanupfn: 当内存不再被evbuffer应用的时候会调用 107 | 108 | 14. `evbuffer_prepend(buf, data, size)` 109 | 110 | 把data加到buf头部 111 | 112 | 15. `evbuffer_reserve_space(buf, size, vec, n_vec)` 113 | 114 | 在evbuffer的链种保留一段大小的空间,在 `evbuffer_commit_space` 之前都无法读取 115 | 116 | vec: 保留内存的数据,`evbuffer_iovc` 结构 117 | 118 | n_vec: vec的个数, 最少1 119 | 120 | 16. `evbuffer_commit_space(buf, vec, n_vecs)` 121 | 122 | commit之前reserve的space 123 | 124 | ​ 125 | 126 | 17. `evbuffer_peek(buf, len, start_at, vec_out, n_vec)` 127 | 128 | peek 到evbuffer中指定位置 129 | 130 | 18. `evbuffer_drain(buf, len)` 131 | 132 | 移除evbuffer中头部指定长度的数据 133 | 134 | 135 | 136 | evbuffer在envoy中的用法主要是read和write 137 | 138 | read: 139 | 140 | 利用 `evbuffer_reserve_space` 和 `evbuffer_commit_space` 在evbuffer种申请内存, 用syscall readv, 读取指定fd到evbuffer中 141 | 142 | write: 143 | 144 | 利用`evbuffer_peek`和syscall writev 向fd写入evbuffer中的首部数据,并用`evbuffer_drain` 删除已写入的数据 145 | 146 | 147 | 148 | ## dispatcher 149 | 150 | 封装了libevent ,方便envoy worker使用。 151 | 152 | envoy强依赖libevent进行事件处理, master和worker的功能分隔如下 153 | 154 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1g2cli29m25j310n0u0gq7.jpg) 155 | 156 | dispatcher为每个worker (包括master) 独立拥有, 即每一个worker一个eventloop。 157 | 158 | Master: 159 | 160 | Worker: 161 | 162 | 163 | 164 | 165 | 166 | 1. `createTimer(callback)` 167 | 168 | 调用 `evtimer_assign` 注册一个callback到event_base, 并返回一个TimerImpl 包含了event对象,并没有真正执行callback 169 | 170 | 2. `TimerImpl::enableTimer(milliseconds)` 171 | 172 | 通过 `event_active` (立即active) 和 `event_add` (延迟active)来调用TimerImpl上event对应的callback 173 | 174 | 3. `post(callback)` 175 | 176 | 把一个callback加入post_callbacks_ 列表,当前post_callbacks_ 有任务, 就通过active post_timer来调用 `runPostCallbacks` 执行所有的callback 177 | 178 | 4. `createFileEvent(fd, callback, trigger, events)` 179 | 180 | 当fd 发生events的事件(可读或可写)的时候会触发 181 | 182 | fd: 监控的fd, file, socket都可以 183 | 184 | callback:触发时调用的函数 185 | 186 | trigger: 187 | 188 | ​ Level: 水平触发 189 | 190 | ​ Edge: 边沿触发:用于连接上事件的触发 191 | 192 | events: 193 | 194 | ​ Read or Write or Closed 195 | 196 | 5. `createClientConnection(address, source_address, transport_socket, options)` 197 | 198 | 创建指向upstream的连接,会使用 `createFileEvent` 对连接的事件(Read|Write)进行注册,触发 `onFileEvent` 199 | 200 | 6. `createServerConnection(socket, transport_socket)` 201 | 202 | 根据传入的downstream的socket,创建ConnectionImpl, 同时使用 `createFileEvent` 注册 203 | 204 | 7. `createListener(socket, callback, bind_to_port, hand_off_restored_destination_connections)` 205 | 206 | 创建用于管理地址信息的 `ListenerImpl` 对象,并根据bind_to_port (istio架构下的15001), 使用`evconnListener_new` 注册了listenCallback, 用于downstream请求的handle 207 | 208 | 8. `listenForSignal(signal_num, callback)` 209 | 210 | 利用`evsignal_assign`和 `evsignal_add` 注册signal handler到eventloop 211 | 212 | 9. `run(type)` 213 | 214 | 调用 `event_base_loop` , 根据type的参数,会阻塞于这个函数 215 | 216 | 217 | 218 | ## TLS(Thread Local Storage) 219 | 220 | tls为master和worker共享一个 `ThreadLocal::Instance` 对象,利用c++的thread_local keyword进行隔离 221 | 222 | 1. `registerThread(dispatcher, main_thread)` 223 | 224 | 只能在master调用,可以把worker的dispatcher加入 `registered_threads_` 进行管理 225 | 226 | 2. `runOnAllThreads(callback)` 227 | 228 | 只能在master调用,把指定callback在 `registered_threads_` 内的dispatcher (worker dispatchers) 上执行, 常用于比如rds更新 229 | 230 | 3. `allocateSlot()` 231 | 232 | 申请一个用于存放信息的 **位置**,所有的thread共享, slot有很多种,比如: 233 | 234 | - `AsyncClientManagerImpl::google_tls_slot_` 235 | - `RdsRouteConfigProviderImpl::tls` 236 | - `Config::upstream_drain_manager_slot_` 237 | 238 | ![img](https://cdn-images-1.medium.com/max/1600/1*fyx9IJBwbGDVtK_LhwQB6A.png) 239 | 240 | 4. `SlotImpl::set(callback)` 241 | 242 | 传入一个callback, callback参数为Dispatcher, 在每个thread上都执行一遍,并把返回值设置给thread local的`thread_local_data_` 上slot的index指示处 243 | 244 | 5. `SlotImpl::get()` 245 | 246 | 返回`thread_local_data_` 中存储的slot对应对象 247 | 248 | 249 | 250 | # 启动流程 251 | 252 | 1. 初始化 thread local storage 253 | 254 | 255 | 256 | ``` 257 | tls_ = std::make_unique(); 258 | ``` 259 | 260 | 2. 初始化master的dispatcher 261 | 262 | 263 | 264 | ``` 265 | dispatcher_(api_->allocateDispatcher()), 266 | ``` 267 | 268 | 3. Load bootstrap config 269 | 270 | 271 | 272 | ``` 273 | InstanceUtil::loadBootstrapConfig(bootstrap_, options); 274 | bootstrap_config_update_time_ = time_system_.systemTime(); 275 | ``` 276 | 277 | 4. 初始化Workers 278 | 279 | *根据机器的硬件线程数目创建worker* 280 | 281 | 282 | 283 | ``` 284 | for (uint32_t i = 0; i < server.options().concurrency(); i++) { 285 | workers_.emplace_back(worker_factory.createWorker(server.overloadManager())); 286 | } 287 | ``` 288 | 289 | 5. 根据bootstrap config中的静态配置初始化 secrets, cluster, listener等配置 290 | 291 | 292 | 293 | ``` 294 | onst auto& secrets = bootstrap.static_resources().secrets(); 295 | ENVOY_LOG(info, "loading {} static secret(s)", secrets.size()); 296 | for (ssize_t i = 0; i < secrets.size(); i++) { 297 | ENVOY_LOG(debug, "static secret #{}: {}", i, secrets[i].name()); 298 | server.secretManager().addStaticSecret(secrets[i]); 299 | } 300 | 301 | ENVOY_LOG(info, "loading {} cluster(s)", bootstrap.static_resources().clusters().size()); 302 | cluster_manager_ = cluster_manager_factory.clusterManagerFromProto(bootstrap); 303 | 304 | const auto& listeners = bootstrap.static_resources().listeners(); 305 | ENVOY_LOG(info, "loading {} listener(s)", listeners.size()); 306 | for (ssize_t i = 0; i < listeners.size(); i++) { 307 | ENVOY_LOG(debug, "listener #{}:", i); 308 | server.listenerManager().addOrUpdateListener(listeners[i], "", false); 309 | } 310 | ``` 311 | 312 | 313 | 314 | 6. 初始化ClusterManager 315 | 316 | 1. load static cluster (exclude eds) 317 | 2. 初始化ads grcp client 318 | 3. load static cluster (eds) (在v2版本的配置中,eds cluster会依赖于non-eds的cluster) 319 | 4. 初始化 `ThreadLocalClusterManager` ,提供从中央cluster manager更新的cached cluster data, 维护load balanacer状态和连接池 320 | 5. 通过 `onClusterInit` 把static cluster 更新到 ThreadLocalClusterManager中 321 | 6. 启动ads grpc双向stream, 并发送ads请求 322 | 323 | 324 | 325 | 326 | 327 | 7. 当第一次rds synced的时候, 会启动所有的worker 328 | 329 | 1. 把active_listeners_ 加入所有的worker 330 | 2. 用thread启动每一个worker 331 | 332 | 333 | 334 | ``` 335 | init_watcher_("RunHelper", [&instance, workers_start_cb]() { 336 | if (!instance.isShutdown()) { 337 | workers_start_cb(); 338 | } 339 | }) 340 | ``` 341 | 342 | 343 | 344 | # ADS 345 | 346 | ###依赖触发流程 347 | 348 | 1. 根据bootstrap config 初始化grcp client 的 `remote_cluster_name_` 等配置 349 | 350 | 351 | 352 | ``` 353 | ads_mux_ = std::make_unique( 354 | local_info, 355 | Config::Utility::factoryForGrpcApiConfigSource( 356 | *async_client_manager_, bootstrap.dynamic_resources().ads_config(), stats) 357 | ->create(), 358 | main_thread_dispatcher, 359 | *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( 360 | "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), 361 | random_, stats_, 362 | Envoy::Config::Utility::parseRateLimitSettings(bootstrap.dynamic_resources().ads_config())); 363 | ``` 364 | 365 | 366 | 367 | 2. 建立grpc连接的时候,根据1中设置的 `remote_cluster_name_` 在`ThreadLocalClusterManagerImpl` 中取出对应http client, 并发出grpc请求 368 | 369 | 370 | ``` 371 | ThreadLocalClusterManagerImpl& cluster_manager = tls_>getTyped(); 372 | auto entry = cluster_manager.thread_local_clusters_.find(cluster); 373 | if (entry != cluster_manager.thread_local_clusters_.end()) { 374 | return entry->second->http_async_client_; 375 | } else { 376 | throw EnvoyException(fmt::format("unknown cluster '{}'", cluster)); 377 | } 378 | ``` 379 | 380 | 3. 首先发送 `type.googleapis.com/envoy.api.v2.Cluster` (cds) 381 | 382 | Pilot 那边会把该envoy所对应的所有Cluster一次性返回 383 | 384 | 385 | 386 | ``` 387 | rawClusters, err := s.generateRawClusters(con.modelNode, push) 388 | if err != nil { 389 | return err 390 | } 391 | if s.DebugConfigs { 392 | con.CDSClusters = rawClusters 393 | } 394 | response := con.clusters(rawClusters) 395 | err = con.send(response) 396 | ``` 397 | 398 | 4. 与当前ClusterManager中的Cluster进行对比,得到 to_add和to_remove 399 | 400 | 401 | 5. 此时暂停发送eds请求 ,并在cds更新完成后统一发送 `ClusterLoadAssignment` (eds) 402 | 403 | ``` 404 | cm_.adsMux().pause(Config::TypeUrl::get().ClusterLoadAssignment); 405 | Cleanup eds_resume([this] { cm_.adsMux().resume(Config::TypeUrl::get().ClusterLoadAssignment); }); 406 | 407 | --- 408 | // RAII cleanup via functor. 409 | class Cleanup { 410 | public: 411 | Cleanup(std::function f) : f_(std::move(f)) {} 412 | ~Cleanup() { f_(); } 413 | 414 | private: 415 | std::function f_; 416 | }; 417 | ``` 418 | 419 | 6. 在更新cluster的时候,会更新到每个worker的`ThreadLocalClusterManagerImpl` 中 (启动的时候只有add, update的逻辑较复杂) 420 | 421 | 422 | ``` 423 | if (use_active_map) { 424 | ENVOY_LOG(info, "add/update cluster {} during init", cluster_name); 425 | auto& cluster_entry = active_clusters_.at(cluster_name); 426 | createOrUpdateThreadLocalCluster(*cluster_entry); 427 | init_helper_.addCluster(*cluster_entry->cluster_); 428 | ``` 429 | 430 | 431 | 432 | 7. 当所有的cluster初始化完毕的时候,会挨个触发`ClusterLoadAssignment` 请求 433 | 434 | ``` 435 | Envoy::Config::GrpcMuxImpl::subscribe grpc_mux_impl.cc:80 436 | Envoy::Config::GrpcMuxSubscriptionImpl::start grpc_mux_subscription_impl.h:41 437 | Envoy::Upstream::EdsClusterImpl::startPreInit eds.cc:34 438 | Envoy::Upstream::ClusterImplBase::initialize(std::function) upstream_impl.cc:711 439 | Envoy::Upstream::ClusterManagerInitHelper::maybeFinishInitialize cluster_manager_impl.cc:122 440 | Envoy::Upstream::ClusterManagerInitHelper::removeCluster cluster_manager_impl.cc:93 441 | Envoy::Upstream::ClusterManagerInitHelper::onClusterInit cluster_manager_impl.cc:70 442 | ``` 443 | 444 | 8. eds的更新也会post到每个worker去执行, 更新到`ThreadLocalClusterManagerImpl` 上 445 | 446 | 447 | ``` 448 | for (auto& host_set : cluster.prioritySet().hostSetsPerPriority()) { 449 | if (host_set->hosts().empty()) { 450 | continue; 451 | } 452 | postThreadLocalClusterUpdate(cluster, host_set->priority(), host_set->hosts(), HostVector{}); 453 | } 454 | ``` 455 | 456 | 457 | 458 | 9. 当 `ClusterManagerImpl` 初始化完成(cds和eds同步完成)的时候会暂时暂停rds更新, 并发送lds的请求 459 | 460 | 461 | ``` 462 | cm.setInitializedCb([&instance, &init_manager, &cm, this]() { 463 | if (instance.isShutdown()) { 464 | return; 465 | } 466 | 467 | // Pause RDS to ensure that we don't send any requests until we've 468 | // subscribed to all the RDS resources. The subscriptions happen in the init callbacks, 469 | // so we pause RDS until we've completed all the callbacks. 470 | cm.adsMux().pause(Config::TypeUrl::get().RouteConfiguration); 471 | 472 | ENVOY_LOG(info, "all clusters initialized. initializing init manager"); 473 | init_manager.initialize(init_watcher_); 474 | 475 | // Now that we're execute all the init callbacks we can resume RDS 476 | // as we've subscribed to all the statically defined RDS resources. 477 | cm.adsMux().resume(Config::TypeUrl::get().RouteConfiguration); 478 | }); 479 | ``` 480 | 481 | 482 | 483 | 10. lds的初始化callback是作为init manager一个handle加入,并在init done的时候回调,发送 `type.googleapis.com/envoy.api.v2.Listener` (lds) 请求 484 | 485 | ``` 486 | Envoy::Init::ManagerImpl::add manager_impl.cc:21 487 | Envoy::Server::LdsApiImpl::LdsApiImpl lds_api.cc:31 488 | std::make_unique unique_ptr.h:831 489 | Envoy::Server::ProdListenerComponentFactory::createLdsApi listener_manager_impl.h:49 490 | Envoy::Server::ListenerManagerImpl::createLdsApi listener_manager_impl.h:116 491 | Envoy::Server::InstanceImpl::initialize server.cc:339 492 | ``` 493 | 494 | 495 | 496 | 11. pilot会把对应的Listener全部push给envoy 497 | 498 | 499 | 500 | 501 | 502 | 12. lds的更新期间会暂停rds请求的发送 503 | 504 | 505 | ``` 506 | void LdsApiImpl::onConfigUpdate(const Protobuf::RepeatedPtrField& resources, 507 | const std::string& version_info) { 508 | cm_.adsMux().pause(Config::TypeUrl::get().RouteConfiguration); 509 | Cleanup rds_resume([this] { cm_.adsMux().resume(Config::TypeUrl::get().RouteConfiguration); }); 510 | 511 | ``` 512 | 513 | 514 | 515 | 13. 接收到`bindToPort` 的Listener后,会在master进行端口bind 516 | 517 | ``` 518 | Envoy::Network::ListenSocketImpl::doBind listen_socket_impl.cc:20 519 | Envoy::Network::ListenSocketImpl::setupSocket listen_socket_impl.cc:46 520 | Envoy::Network::NetworkListenSocket >::NetworkListenSocket listen_socket_impl.h:90 521 | __gnu_cxx::new_allocator > >::construct >, std::shared_ptr&, std::shared_ptr, std::allocator > > > const&, bool&> new_allocator.h:136 522 | std::allocator_traits > > >::construct >, std::shared_ptr&, std::shared_ptr, std::allocator > > > const&, bool&> alloc_traits.h:475 523 | std::_Sp_counted_ptr_inplace >, std::allocator > >, (__gnu_cxx::_Lock_policy)2>::_Sp_counted_ptr_inplace&, std::shared_ptr, std::allocator > > > const&, bool&> shared_ptr_base.h:545 524 | std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count >, std::allocator > >, std::shared_ptr&, std::shared_ptr, std::allocator > > > const&, bool&> shared_ptr_base.h:677 525 | std::__shared_ptr >, (__gnu_cxx::_Lock_policy)2>::__shared_ptr > >, std::shared_ptr&, std::shared_ptr, std::allocator > > > const&, bool&> shared_ptr_base.h:1342 526 | std::shared_ptr > >::shared_ptr > >, std::shared_ptr&, std::shared_ptr, std::allocator > > > const&, bool&> shared_ptr.h:359 527 | std::allocate_shared >, std::allocator > >, std::shared_ptr&, std::shared_ptr, std::allocator > > > const&, bool&> shared_ptr.h:706 528 | std::make_shared >, std::shared_ptr&, std::shared_ptr, std::allocator > > > const&, bool&> shared_ptr.h:722 529 | Envoy::Server::ProdListenerComponentFactory::createListenSocket listener_manager_impl.cc:144 530 | Envoy::Server::ListenerManagerImpl::addOrUpdateListener listener_manager_impl.cc:819 531 | Envoy::Server::LdsApiImpl::onConfigUpdate lds_api.cc:73 532 | ``` 533 | 534 | 14. 在 master接受到LDS,初始化 `ListenerImpl`的时候,会把listener加到所有worker中,同时会把`bind_to_port==true` 的 listener的fd加入到所有worker的event loop中,绑定 `ListenCallback` 535 | 536 | 537 | 538 | 539 | ``` 540 | void ListenerManagerImpl::onListenerWarmed(ListenerImpl& listener) { 541 | // The warmed listener should be added first so that the worker will accept new connections 542 | // when it stops listening on the old listener. 543 | for (const auto& worker : workers_) { 544 | addListenerToWorker(*worker, listener); 545 | } 546 | ``` 547 | 548 | 549 | 550 | 551 | 552 | ``` 553 | listener_.reset( 554 | evconnlistener_new(&dispatcher.base(), listenCallback, this, 0, -1, socket.ioHandle().fd())); 555 | ``` 556 | 557 | 15. 在`ListenerImpl` 初始化的时候,对应的Rds的handle会调用对应handle, 发送 `type.googleapis.com/envoy.api.v2.RouteConfiguration` (rds)请求 558 | 559 | ``` 560 | Envoy::Config::GrpcMuxImpl::subscribe grpc_mux_impl.cc:80 561 | Envoy::Config::GrpcMuxSubscriptionImpl::start grpc_mux_subscription_impl.h:41 562 | Envoy::Router::RdsRouteConfigSubscription::::operator()(void) const rds_impl.cc:64 563 | std::_Function_handler >::_M_invoke(const std::_Any_data &) std_function.h:297 564 | std::function::operator()() const std_function.h:687 565 | Envoy::Init::TargetImpl::::operator()(Envoy::Init::WatcherHandlePtr) const target_impl.cc:29 566 | std::_Function_handler >), Envoy::Init::TargetImpl::TargetImpl(absl::string_view, Envoy::Init::InitializeFn):: >::_M_invoke(const std::_Any_data &, std::unique_ptr > &&) std_function.h:297 567 | std::function >)>::operator()(std::unique_ptr >) const std_function.h:687 568 | Envoy::Init::TargetHandleImpl::initialize target_impl.cc:16 569 | Envoy::Init::ManagerImpl::add manager_impl.cc:28 570 | Envoy::Router::RouteConfigProviderManagerImpl::createRdsRouteConfigProvider rds_impl.cc:202 571 | Envoy::Router::RouteConfigProviderUtil::create rds_impl.cc:35 572 | Envoy::Extensions::NetworkFilters::HttpConnectionManager::HttpConnectionManagerConfig::HttpConnectionManagerConfig config.cc:167 573 | Envoy::Extensions::NetworkFilters::HttpConnectionManager::HttpConnectionManagerFilterConfigFactory::createFilterFactoryFromProtoTyped config.cc:92 574 | Envoy::Extensions::NetworkFilters::Common::FactoryBase::createFilterFactoryFromProto factory_base.h:29 575 | Envoy::Server::ProdListenerComponentFactory::createNetworkFilterFactoryList_ listener_manager_impl.cc:70 576 | Envoy::Server::ProdListenerComponentFactory::createNetworkFilterFactoryList listener_manager_impl.h:57 577 | Envoy::Server::ListenerImpl::ListenerImpl listener_manager_impl.cc:283 578 | Envoy::Server::ListenerManagerImpl::addOrUpdateListener listener_manager_impl.cc:751 579 | Envoy::Server::LdsApiImpl::onConfigUpdate lds_api.cc:73 580 | ``` 581 | 582 | 16. RDS的更新很简单,master分发给所有worker,设置 `ThreadLocalConfig.config_` 的变量 583 | 584 | 585 | ``` 586 | void RdsRouteConfigProviderImpl::onConfigUpdate() { 587 | ConfigConstSharedPtr new_config( 588 | new ConfigImpl(subscription_->route_config_proto_, factory_context_, false)); 589 | tls_->runOnAllThreads( 590 | [this, new_config]() -> void { tls_->getTyped().config_ = new_config; }); 591 | } 592 | ``` 593 | 594 | ### 总结 595 | 596 | envoy的ADS更新,完全由master负责,使用grpc建立stream拉取配置,更新的顺序为: 597 | 598 | CDS -> EDS -> LDS -> RDS 599 | 600 | 这些配置经过master的处理后,会用每个worker的dispatcher分发给所有的worker上的TLS 601 | 602 | -------------------------------------------------------------------------------- /envoy/http-request-notes.md: -------------------------------------------------------------------------------- 1 | ### 1. 建立连接 2 | 3 | 1. client向envoy发起连接,envoy的worker接收eventloop的callback, 触发 `listenCallback` (port: 15001) 4 | 2. 15001的 `useOriginalDst": true` , `accept_filters_` 中会带有 `OriginalDstFilter` 5 | 3. 在 `OriginalDstFilter.OnAccept`中用 `os_syscalls.getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, &orig_addr, &addr_len)` 获取在iptables修改之前dst ip 6 | 4. 通过`ConnectionHandlerImpl::findActiveListenerByAddress` 查到addr对应的Listener 7 | 1. 先查找 `Listener.IP==addr.ip && Listener.Port==addr.port`的Listener 8 | 2. 再查找`Listener.IP==0.0.0.0 && Listener.Port==addr.port`的Listener 9 | 5. `dispatcher.createServerConnection` 传入accept到的fd 创建Server连接对象 `ConnectionImpl` , 并把`onFileEvent` 注册到eventloop,等待读写事件的到来,因为socket是由一个non-blocking listening socket创建而来,所以也是non-blocking 10 | 6. http的listener里filters为 `envoy.http_connection_manager` , `buildFilterChain` 里会把`HTTP::ConnectionManagerImpl` 加入到 `upstream_filters_ `(list\)中,这样在请求数据到达的时候,就可以使用http_connection_manager的`on_read` 方法 11 | 7. 目前envoy的ServerConnection会被设置 `TCP NO_DELAY`, 同时会把连接加入对应Listener的`connections_`中 12 | 8. 当连接刚刚加入eventloop的时候, Write Event会被立即触发,但因为`write_buffer_` 没有数据,所以不会写入任何数据 13 | 14 | ### 2. 接收请求 15 | 16 | 1. client开始向socket写入请求数据 17 | 18 | 2. eventloop在触发read event后,`transport_socket_.doRead` 中会循环读取加入`read_buffer_`,直到返回EAGAIN 19 | 20 | 3. 把buffer传入`Envoy::Http::ConnectionManagerImpl::onData` 进行HTTP请求的处理 21 | 22 | 4. 如果`codec_type` 是AUTO(HTTP1 or HTTP2 or AUTO)的情况下,会从请求中搜索 `PRI * HTTP/2` 来判断是否http2 23 | 24 | 5. 利用`http_parser` 进行http解析的callback, `ConnectionImpl::settings_` 静态初始化了parse各个阶段的callbacks 25 | 26 | 6. `onMessageBeginBase` 27 | 28 | 1. 创建`ActiveStream` , 保存downstream的信息,和对应的route信息 29 | 2. 对于https,会把TLS握手的时候保存的SNI写入`ActiveStream.requested_server_name_` 30 | 31 | 7. `onHeaderField`, `onHeaderValue` 32 | 33 | 迭代添加header到`current_header_map_` 中 34 | 35 | 8. `onHeadersComplete` 36 | 37 | 把request中的一些字段(method, path, host )加入headers中 38 | 39 | 9. `onMessageComplete` 40 | 41 | 1. 默认不支持http/1.0和http/0.9协议,快速返回426,可以用过配置`accept_http_10`来开启 42 | 43 | 2. 调用`refreshCachedRoute` 在route中查询cluster 44 | 45 | 3. route查找逻辑,缓存在`cached_route_` 中 46 | 47 | 1. 根据host查找对应virtualhost 48 | 49 | 1. 如果只有一个domain为`*`的virtualhost,快速返回 50 | 2. 在整个route中,跨越多个virtualhost,对于domains用http host进行equal匹配,返回匹配到的domain所属virtualhost 51 | 3. domains中匹配通配符放在开始(*.foo.bar)的最长的domain 52 | 4. domains中匹配通配符放在最后(foo.bar.*)的最长的domain 53 | 5. 以上逻辑都没有匹配后,就返回`*` 的virtualhost(需要自己定义) 54 | 55 | 2. 通过配置的header和query params在virtualhost的routes中匹配 56 | 57 | 1. Method等属性也是存放在header内(`:method`) 58 | 59 | 2. header match 60 | 61 | `Value` 等于,`Regex` 正则,`Range` 在指定范围内,value需要是int,`Present` 存在就可以,`Prefix`前缀,`Suffix` 后缀,`Invert` 上面的match取反。除了`Invert`,上面只有一个能生效 62 | 63 | 3. query param match, 只有regex和value 64 | 65 | 4. Path match, 只有 `path` (exact), `prefix`, `regex` 66 | 67 | 5. 可以在request中指定cluster name,返回指定cluster 68 | 69 | 6. 会根据stream_id在weighted_clusters中进行取余按照概率返回一个cluster (stream_id为随机数) 70 | 71 | 4. 通过route上的cluster name从ThreadLocalClusterManager中查找cluster, 缓存在 `cached_cluster_info_` 中 72 | 73 | 5. 根据配置构造在route上的filterChain (具体的filter实现是通过 `registerFactory`方法注册进去,在`createFilterChain` 的时候根据名称构造,比如istio-proxy的mixer) 74 | 75 | 6. 如果对应http connection manager上有trace配置 76 | 77 | 1. request header中有trace,就创建子span, sampled跟随parent span 78 | 2. 如果header中没有trace,就创建root span, 并设置sampled 79 | 80 | 7. 根据http connection manager上配置的filters (`mixer`, `envoy.cors`, `envoy.fault`, `envoy.router`),一个个执行`decodeHeaders` 81 | 82 | 这里主要写一下`envoy.fault`和`envoy.router` 83 | 84 | 1. `envoy.fault` 85 | 86 | 1. 如果设置了delay, 会创建一个指定timeout的定时器,并中断decodeHeaders, 当定时器超时的时候会触发继续decode。`decodeHeaders`会从参数filter的下一个开始执行,超时callback会把fault filter传入,这样就可以继续恢复中断之前迭代到的地方 87 | 2. 如果设置了abort,会直接返回响应,并中断decode 88 | 3. 没有情况发生就继续一下个filter 89 | 90 | 2. `envoy.router` 91 | 92 | 1. 没有route能匹配请求 93 | 94 | 返回 404 `no cluster match for URL` 95 | 96 | 2. 有配置`directResponseEntry` 97 | 98 | 直接返回 99 | 100 | 3. route上的clustername在clustermanager上找不到对应cluster 101 | 102 | 返回配置的 `clusterNotFoundResponseCode` 103 | 104 | 4. 当前处于`maintenanceMode` 105 | 106 | 503 `maintenance mode` 107 | 108 | 5. 调用`getConnPool` 获取upstream conn pool 109 | 110 | 1. 根据 cluster上的`features`配置和 `USE_DOWNSTREAM_PROTOCOL` 来确定使用http1还是http2协议向上游发送请求 111 | 2. 在 **ThreadLocalClusterManager** 上根据cluster name查询cluster 112 | 3. 根据loadbalancer算法挑选节点(此处worker之间的负载均衡数据是独立的,比如round robin,只有同一个Worker上的才是严格的顺序) 113 | 4. 根据节点和协议拿到连接池 (连接池由ThreadLocalClusterManager管理,各个Worker不共享) 114 | 115 | 6. 如果没有找到对应conn pool 116 | 117 | 503 no healthy upstream 118 | 119 | 7. 根据配置(timeout, perTryTimeout)确定本次请求的timeout 120 | 121 | 8. 检查有无 `x-envoy-upstream-rq-timeout-alt-response` 这个header,如果有timeout的时候用204代替504 122 | 123 | 9. 把之前生成的trace写入request header 124 | 125 | 10. 对request做一些最终的修改,`headers_to_remove` `headers_to_add` `host_rewrite` `rewritePathHeader` 126 | 127 | 11. 构造 retry和shadowing的对象 128 | 129 | ### 3. 发送请求 130 | 131 | 发送请求部分也是在`envoy.router`中的逻辑 132 | 133 | 1. 查看当前conn pool是否有空闲client 134 | - 如果存在空闲连接 135 | 1. 根据downstream request和tracing等配置构造发往upstream的请求buffer 136 | 2. 把buffer一次性移入`write_buffer_`, 立即触发Write Event 137 | 3. `ConnectionImpl::onWriteReady` 随后会被触发 138 | 4. 把`write_ buffer_`的内容写入socket发送出去 139 | - 如果不存在空闲连接 140 | 1. 根据`max_pending_requests`和`max_connections` 判断是否可以创建新的连接(此处的指标为worker间共享) 141 | 2. 根据配置设置新连接的socket options, 使用`dispatcher.createClientConnection` 创建连接上游的连接,并绑定到eventloop 142 | 3. 新建`PendingRequest` 并加到`pending_requests_` 头部 143 | 4. 当连接成功建立的时候,会触发`ConnectionImpl::onFileEvent` 144 | 5. 在`onConnected`的回调中 145 | 1. 停止`connect_timer_` 146 | 2. 复用存在空闲连接时的逻辑,发送请求 147 | 2. 在 `onRequestComplete`里调用`maybeDoShadowing` 进行流量复制 148 | 1. shadowing流量并不会返回错误 149 | 2. shadowing 流量为asynclient发送,不会阻塞downstream,timeout也为`global_timeout_` 150 | 3. shadowing 会修改request header里的host 和 authority 添加 `-shadow` 后缀 151 | 3. 根据 `global_timeout_` 启动响应超时的定时器 152 | 153 | 154 | 155 | ### 4. 接收响应 156 | 157 | 1. eventloop 触发 `ClientConnectionImpl.ConnectionImpl`上的 `onFileEvent` 的read ready事件 158 | 2. 经过http_parser execute后触发 `onHeadersComplete` 后执行到`UpstreamRequest::decodeHeaders` 159 | 3. `upstream_request_->upstream_host_->outlierDelector().putHttpResponseCode` 写入status code,更新外部检测的状态 160 | 4. 根据返回结果、配置和 `retries_remaining_`判断是否应该retry,retry的间隔时间在一定范围内成指数级变大 161 | 5. 根据 `internal_redirect_action` 的配置和response来确定是否需要redirect到新的host 162 | 163 | ### 5. 返回响应 164 | 165 | 1. 停止 `request_timer` , 重置 `idle_timer` 166 | 2. 和向upstream发送请求一样的逻辑,发送响应给downstream -------------------------------------------------------------------------------- /envoy/解析envoy处理http请求(一)| filter架构.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | Envoy是istio的核心组件之一,以sidecar的方式与服务运行在一起,对服务的流量进行拦截转发。 具有路由,流量控制等等强大特性。 4 | 5 | filter是envoy的核心功能之一,采用插件的形式提供功能,请求各个阶段都有filter的hook,用户可以自由添加自定义类型的filter添加新的功能 6 | 7 | 本文以istio1.1所对应的Envoy版本进行源码流程分析 8 | 9 | # 名词解释 10 | 11 | - 下游: 发送请求给Envoy的服务,client 12 | - 上游:接收Envoy发送的请求,并返回响应的服务, server 13 | 14 | 15 | 16 | # Filter流程图 17 | 18 | 下面的流程图为istio架构下,访问80端口的http服务的流程 19 | 20 | ## 21 | 22 | 1. Client向Envoy的15001 port建立连接,被转到80 port的Listener 23 | 24 | ![](https://picgo-1259280442.cos.ap-shanghai.myqcloud.com/20190603220534.png) 25 | 26 | 2. Client发送请求给Envoy,Envoy经过路由后找到上游Server,并发送请求 27 | 28 | ![](./20190603220615.png) 29 | 30 | 3. 上游Server返回响应给Envoy,Envoy利用event_active立即返回响应给下游的client 31 | 32 | ![](https://picgo-1259280442.cos.ap-shanghai.myqcloud.com/20190604102854.png) 33 | 34 | 4. Client主动断开下游到Envoy的连接 35 | 36 | ![](https://picgo-1259280442.cos.ap-shanghai.myqcloud.com/20190603220705.png) 37 | 38 | 5. Server主动断开Envoy到上游的连接 39 | 40 | ![](https://picgo-1259280442.cos.ap-shanghai.myqcloud.com/20190603220718.png) 41 | 42 | # Filter分类 43 | 44 | 1. **ListenerFilter** 45 | 46 | [listener.listener_filters]() 47 | 48 | 用于接收到下游新连接的时候回调 49 | 50 | 接口: 51 | 52 | - `onAccept(callback)` 53 | 54 | 内置类型: 55 | 56 | - `envoy.listener.original_dst` (istio中的15001端口常用) 57 | 58 | 根据iptables转换之前的dst port,查找到真实的Listener,查找到Listener会根据新的Listener的配置继续处理 59 | 60 | - `envoy.listener.tls_inspector` 61 | 62 | 注册read callback,识别tls和进行tls握手,握手结束后会进行下一步的filterChain的处 63 | 64 | 注册filter: 65 | 66 | ``` 67 | REGISTER_FACTORY(OriginalDstConfigFactory, 68 | Server::Configuration::NamedListenerFilterConfigFactory); 69 | ``` 70 | 71 | 2. **ReadFilter** 72 | 73 | [listener.filter_chains.filters]() 74 | 75 | 1. 用于接受到下游新连接的时候回调 76 | 2. 上游或者下游连接上有数据可以读取的时候的回调,一般用于协议的解析 77 | 78 | 接口: 79 | 80 | - `onNewConnection()` 81 | 82 | - `onData(data, end_stream)` 83 | 84 | ... 85 | 86 | 内置类型: 87 | 88 | - `Envoy::Http::CodecClient` 只在向上游的连接用到,且向上游的连接只有这个filter,用于读取响应 89 | 90 | - `envoy.http_connection_manager` 91 | 92 | 处理http请求的主要filter 93 | 94 | - `envoy.tcp_proxy` 95 | 96 | - `envoy.redis_proxy` 97 | 98 | ... 99 | 100 | 注册filter: 101 | 102 | ``` 103 | REGISTER_FACTORY(HttpConnectionManagerFilterConfigFactory, 104 | Server::Configuration::NamedNetworkFilterConfigFactory) 105 | ``` 106 | 107 | 3. **WriteFilter** 108 | 109 | [listener.filter_chains.filters]() 110 | 111 | 用于向上游的连接写入数据的时候回调(目前内置的writeFilter没有http相关的) 112 | 113 | 接口: 114 | 115 | - `onWrite(data, end_stream)` 116 | 117 | 内置类型: 118 | 119 | - `envoy.filters.network.dubbo_proxy` 120 | - `envoy.mongo_proxy` 121 | - `envoy.filters.network.mysql_proxy` 122 | - `envoy.filters.network.zookeeper_proxy` 123 | 124 | ​ 注册filter: 125 | 126 | ``` 127 | REGISTER_FACTORY(HttpConnectionManagerFilterConfigFactory, 128 | Server::Configuration::NamedNetworkFilterConfigFactory) 129 | ``` 130 | 131 | 4. **StreamDecodeFilter** ( `envoy.http_connection_manager`下独有的filter) 132 | 133 | [listener.filter_chains.filters[envoy.http_connection_manager].http_filters]() 134 | 135 | 用于解析http请求各个部分的时候回调执行 136 | 137 | 接口: 138 | 139 | - `decodeHeaders(headers, end_stream)` 140 | 141 | - `decodeData(data, end_stream)` 142 | 143 | - `decodeTrailers(HeaderMaps& trailers)` 144 | 145 | - `decodeComplete()` 146 | 147 | ... 148 | 149 | 内置类型: 150 | 151 | - `envoy.cors` 152 | 153 | - `envoy.fault` 154 | 155 | - `envoy.router` 156 | 157 | ... 158 | 159 | 注册filter: 160 | 161 | ``` 162 | Http::FilterFactoryCb 163 | DynamoFilterConfig::createFilter(const std::string& stat_prefix, 164 | Server::Configuration::FactoryContext& context) { 165 | return [&context, stat_prefix](Http::FilterChainFactoryCallbacks& callbacks) -> void { 166 | callbacks.addStreamFilter(Http::StreamFilterSharedPtr{new Dynamo::DynamoFilter( 167 | context.runtime(), stat_prefix, context.scope(), context.dispatcher().timeSource())}); 168 | }; 169 | } 170 | 171 | REGISTER_FACTORY(DynamoFilterConfig, Server::Configuration::NamedHttpFilterConfigFactory); 172 | ``` 173 | 174 | 175 | 176 | 5. **StreamEncodeFilter** (`envoy.http_connection_manager` 下独有的filter) 177 | 178 | [listener.filter_chains.filters[envoy.http_connection_manager].http_filters]() 179 | 180 | 发送响应各个部分给下游client的时候执行 181 | 182 | 接口: 183 | 184 | - `encode100ContinueHeaders(headers)` 185 | 186 | - `encodeHeaders(headers, end_stream)` 187 | 188 | - `encodeData(data, end_stream)` 189 | 190 | - `encodeTrailers(HeaderMap& trailers)` 191 | 192 | - `encodeMetadata(metadata_map)` 193 | 194 | - `encodeComplete()` 195 | 196 | ... 197 | 198 | 内置类型: 199 | 200 | - `envoy.cors` 201 | 202 | - `envoy.fault` 203 | 204 | - `envoy.lua` 205 | 206 | ... 207 | 208 | 注册filter: 209 | 210 | ``` 211 | Http::FilterFactoryCb 212 | DynamoFilterConfig::createFilter(const std::string& stat_prefix, 213 | Server::Configuration::FactoryContext& context) { 214 | return [&context, stat_prefix](Http::FilterChainFactoryCallbacks& callbacks) -> void { 215 | callbacks.addStreamFilter(Http::StreamFilterSharedPtr{new Dynamo::DynamoFilter( 216 | context.runtime(), stat_prefix, context.scope(), context.dispatcher().timeSource())}); 217 | }; 218 | } 219 | 220 | REGISTER_FACTORY(DynamoFilterConfig, Server::Configuration::NamedHttpFilterConfigFactory); 221 | ``` 222 | 223 | 224 | 225 | 6. **PerFilterConfig** (并不是filter,只是为4,5中的http_filter提供route级别的配置数据) 226 | 227 | [route.virtual_hosts.per_filter_config]() 228 | 229 | 位于route上的字段,只有当对应Listener上http_connection_manager包含对应httpfilter的时候才有用,结构为 `map` 用法由filter自己实现 230 | 231 | 7. **ConnectionCallbacks** 232 | 233 | [listener.filter_chains.filters]() 234 | 235 | 接口: 236 | 237 | - `onEvent(event)` 238 | 239 | 事件分为 `RemoteClose`, `LocalClose`, `Connected` 会在各个阶段调用 240 | 241 | - `onAboveWriteBufferHighWatermark()` 242 | 243 | - `onBelowWriteBufferLowWatermark()` 244 | 245 | 类型: 246 | 247 | - `Envoy::Http::CodeClient` 只在向上游的连接用到,且向上游的连接只有这个filter,用于检测上游连接断开 248 | - `envoy.http_connection_manager` 249 | - `envoy.tcp_proxy` 250 | - `envoy.redis_proxy` 251 | 252 | 注册filter: 253 | 254 | ``` 255 | REGISTER_FACTORY(HttpConnectionManagerFilterConfigFactory, 256 | Server::Configuration::NamedNetworkFilterConfigFactory) 257 | ``` 258 | 259 | 8. **access_log_handlers** 260 | 261 | 接口: 262 | 263 | - `log(request_headers, response_headers, response_trailers, stream_info)` 264 | 265 | 类型: 266 | 267 | - `Envoy::Http::Mixer::Filter` 268 | 269 | istio为Envoy添加的Filter,在AccessLogHandlers这边主要用于Report 270 | 271 | - `Envoy::Extensions::AccessLoggers::File::FileAccessLog` 272 | 273 | - `Envoy::Extensions::HttpGrpc::File::HttpGrpcAccessLog` 274 | 275 | - `Envoy::Extensions::HttpFilters::TapFilter::Filter` 276 | 277 | 278 | 279 | # Filter流程中关键步骤解析 280 | 281 | ### 1. findActiveListenerByAddress 282 | 283 | 根据socket的localaddress和port选择合适的Listener处理 284 | 285 | 1. 利用syscall找到iptables转化之前的dst port (如果有`envoy.listener.original_dst`) 286 | 287 | ```c++ 288 | os_syscalls.getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, &orig_addr, &addr_len) 289 | ``` 290 | 291 | 2. 先匹配address和port和socket都一致的Listener,如果没找到再找port一致,address==0.0.0.0的Listener 292 | 293 | ```c++ 294 | auto listener_it = std::find_if( 295 | listeners_.begin(), listeners_.end(), 296 | [&address](const std::pair& p) { 297 | return p.second->listener_ != nullptr && p.first->type() == Network::Address::Type::Ip && 298 | *(p.first) == address; 299 | }); 300 | 301 | // If there is exact address match, return the corresponding listener. 302 | if (listener_it != listeners_.end()) { 303 | return listener_it->second.get(); 304 | } 305 | 306 | // Otherwise, we need to look for the wild card match, i.e., 0.0.0.0:[address_port]. 307 | listener_it = std::find_if( 308 | listeners_.begin(), listeners_.end(), 309 | [&address](const std::pair& p) { 310 | return p.second->listener_ != nullptr && p.first->type() == Network::Address::Type::Ip && 311 | p.first->ip()->port() == address.ip()->port() && p.first->ip()->isAnyAddress(); 312 | }); 313 | return (listener_it != listeners_.end()) ? listener_it->second.get() : nullptr; 314 | ``` 315 | 316 | ### 2. 匹配request选择route和cluster 317 | 318 | 1. 在构造`RouteMatcher`的时候会遍历`virtual_hosts` 下的domains,并根据通配符的位置和domain的长度分为4个 `map, std::greater>` 319 | 320 | - `default_virtual_host_` domain就是一个通配符(只允许存在一个) 321 | 322 | - `wildcard_virtual_host_suffixes_` domain中通配符在开头 323 | 324 | - `wildcard_virtual_host_prefixes_` domain中通配符在结尾 325 | 326 | - `virtual_hosts_` 不包含通配符 327 | 328 | 329 | 330 | ```c++ 331 | for (const auto& virtual_host_config : route_config.virtual_hosts()) { 332 | VirtualHostSharedPtr virtual_host(new VirtualHostImpl(virtual_host_config, global_route_config, 333 | factory_context, validate_clusters)); 334 | for (const std::string& domain_name : virtual_host_config.domains()) { 335 | const std::string domain = Http::LowerCaseString(domain_name).get(); 336 | bool duplicate_found = false; 337 | if ("*" == domain) { 338 | if (default_virtual_host_) { 339 | throw EnvoyException(fmt::format("Only a single wildcard domain is permitted")); 340 | } 341 | default_virtual_host_ = virtual_host; 342 | } else if (!domain.empty() && '*' == domain[0]) { 343 | duplicate_found = !wildcard_virtual_host_suffixes_[domain.size() - 1] 344 | .emplace(domain.substr(1), virtual_host) 345 | .second; 346 | } else if (!domain.empty() && '*' == domain[domain.size() - 1]) { 347 | duplicate_found = !wildcard_virtual_host_prefixes_[domain.size() - 1] 348 | .emplace(domain.substr(0, domain.size() - 1), virtual_host) 349 | .second; 350 | } else { 351 | duplicate_found = !virtual_hosts_.emplace(domain, virtual_host).second; 352 | } 353 | if (duplicate_found) { 354 | throw EnvoyException(fmt::format( 355 | "Only unique values for domains are permitted. Duplicate entry of domain {}", domain)); 356 | } 357 | } 358 | ``` 359 | 360 | 361 | 362 | 2. 按照 `virtual_hosts_` => `wildcard_virtual_host_suffixes_` => `wildcard_virtual_host_prefixes_` => `default_virtual_host_` 的顺序查找 363 | 364 | 同时按照map的迭代顺序(domain len降序)查找最先除去通配符后能匹配到的virtualhost,如果没有直接返回 404 365 | 366 | ```c++ 367 | 368 | const std::string host = Http::LowerCaseString(headers.Host()->value().c_str()).get(); 369 | const auto& iter = virtual_hosts_.find(host); 370 | if (iter != virtual_hosts_.end()) { 371 | return iter->second.get(); 372 | } 373 | if (!wildcard_virtual_host_suffixes_.empty()) { 374 | const VirtualHostImpl* vhost = findWildcardVirtualHost( 375 | host, wildcard_virtual_host_suffixes_, 376 | [](const std::string& h, int l) -> std::string { return h.substr(h.size() - l); }); 377 | if (vhost != nullptr) { 378 | return vhost; 379 | } 380 | } 381 | if (!wildcard_virtual_host_prefixes_.empty()) { 382 | const VirtualHostImpl* vhost = findWildcardVirtualHost( 383 | host, wildcard_virtual_host_prefixes_, 384 | [](const std::string& h, int l) -> std::string { return h.substr(0, l); }); 385 | if (vhost != nullptr) { 386 | return vhost; 387 | } 388 | } 389 | return default_virtual_host_.get(); 390 | 391 | --- 392 | 393 | const VirtualHostImpl* RouteMatcher::findWildcardVirtualHost( 394 | const std::string& host, const RouteMatcher::WildcardVirtualHosts& wildcard_virtual_hosts, 395 | RouteMatcher::SubstringFunction substring_function) const { 396 | // We do a longest wildcard match against the host that's passed in 397 | // (e.g. foo-bar.baz.com should match *-bar.baz.com before matching *.baz.com for suffix 398 | // wildcards). This is done by scanning the length => wildcards map looking for every wildcard 399 | // whose size is < length. 400 | for (const auto& iter : wildcard_virtual_hosts) { 401 | const uint32_t wildcard_length = iter.first; 402 | const auto& wildcard_map = iter.second; 403 | // >= because *.foo.com shouldn't match .foo.com. 404 | if (wildcard_length >= host.size()) { 405 | continue; 406 | } 407 | const auto& match = wildcard_map.find(substring_function(host, wildcard_length)); 408 | if (match != wildcard_map.end()) { 409 | return match->second.get(); 410 | } 411 | } 412 | return nullptr; 413 | } 414 | ``` 415 | 416 | 417 | 418 | 3. 在一个virtualhost上查找对应route和cluster 419 | 420 | - 在通过domain匹配到virtualhost,会在那个virtualhost上匹配查找cluster,如果没匹配上,会直接返回404 421 | 422 | ```c++ 423 | const VirtualHostImpl* virtual_host = findVirtualHost(headers); 424 | if (virtual_host) { 425 | return virtual_host->getRouteFromEntries(headers, random_value); 426 | } else { 427 | return nullptr; 428 | } 429 | 430 | --- 431 | route_ = callbacks_->route(); 432 | if (!route_) { 433 | config_.stats_.no_route_.inc(); 434 | ENVOY_STREAM_LOG(debug, "no cluster match for URL '{}'", *callbacks_, 435 | headers.Path()->value().c_str()); 436 | 437 | callbacks_->streamInfo().setResponseFlag(StreamInfo::ResponseFlag::NoRouteFound); 438 | callbacks_->sendLocalReply(Http::Code::NotFound, "", nullptr, absl::nullopt); 439 | return Http::FilterHeadersStatus::StopIteration; 440 | ``` 441 | 442 | - match可以根据配置分为 `prefix`, `regex`, `path` 三种route进行匹配 443 | 444 | - 如果存在`weighted_clusters` ,会根据`stream_id` , 和clusters的weight进行分发,`stream_id` 本身是每个请求独立随机生成,所以`weighted_clusters` 的权重分发可以视为随机分发 445 | 446 | 447 | 448 | ### 3. 负载均衡策略选择endpoint 449 | 450 | 1. 在上一步查找到了clusterName, 对于clusterEntry,都是从`ThreadLocalClusterManagerImpl` 中取出,每个worker都一份自己的数据 451 | 452 | 2. 对于`ThreadLocalClusterManagerImpl` , 维护了多份根据类型和协议区分的map 453 | 454 | 其中http协议才用的是`host_http_conn_pool_map_` 这个map,大致的结构为 `map>` , 因为http分为 `Http10`, `Http11`, `Http2` 不同协议的connpool都是独立的 455 | 456 | 对于http请求,会从 `host_http_conn_pool_map_` 中查到对应的connpool,每个worker都维护了一份自己独有的threadlocal connpool 457 | 458 | 459 | 460 | # Mixer 461 | 462 | mixerclient是istio基于Envoy,添加filter进行check和report的模块 463 | 464 | ### 注册到Envoy 465 | 466 | ```c++ 467 | Http::FilterFactoryCb createFilterFactory(const HttpClientConfig& config_pb, 468 | const std::string&, 469 | FactoryContext& context) { 470 | std::unique_ptr config_obj( 471 | new Http::Mixer::Config(config_pb)); 472 | auto control_factory = std::make_shared( 473 | std::move(config_obj), context); 474 | return [control_factory]( 475 | Http::FilterChainFactoryCallbacks& callbacks) -> void { 476 | std::shared_ptr instance = 477 | std::make_shared(control_factory->control()); 478 | callbacks.addStreamFilter(Http::StreamFilterSharedPtr(instance)); 479 | callbacks.addAccessLogHandler(AccessLog::InstanceSharedPtr(instance)); 480 | }; 481 | ``` 482 | 483 | 注册到Envoy主要就是两行 484 | 485 | ``` 486 | callbacks.addStreamFilter(Http::StreamFilterSharedPtr(instance)); 487 | callbacks.addAccessLogHandler(AccessLog::InstanceSharedPtr(instance)); 488 | ``` 489 | 490 | 第一行注册了 `StreamDecodeFilter` 和 `StreamEncodeFilter`, `Http::Mixer::Filter` 在`decodeHeader` 这个hook中实现了Check,发送attributes给mixerserver进行检查 491 | 492 | 第二行注册了 `AccessLogHandler` ,这个会在 一个请求结束的时候执行 493 | 494 | ``` 495 | ConnectionManagerImpl::ActiveStream::~ActiveStream() { 496 | ... 497 | for (const auto& log_handler : access_log_handlers_) { 498 | log_handler->log(request_headers_.get(), response_headers_.get(), response_trailers_.get(), 499 | stream_info_); 500 | } 501 | ... 502 | } 503 | 504 | ``` 505 | 506 | 在Mixer filter的log method中,会进行report操作 507 | 508 | ``` 509 | void ReportBatch::Report( 510 | const istio::mixerclient::SharedAttributesSharedPtr& attributes) { 511 | std::lock_guard lock(mutex_); 512 | ++total_report_calls_; 513 | batch_compressor_->Add(*attributes->attributes()); 514 | if (batch_compressor_->size() >= options_.max_batch_entries) { 515 | FlushWithLock(); 516 | } else { 517 | if (batch_compressor_->size() == 1 && timer_create_) { 518 | if (!timer_) { 519 | timer_ = timer_create_([this]() { Flush(); }); 520 | } 521 | timer_->Start(options_.max_batch_time_ms); 522 | } 523 | } 524 | } 525 | ``` 526 | 527 | 可以看到Mixer虽然是每个请求结束都会调用log,但实际的上报mixer是批量发送(累计一定大小或者到达一定时间间隔) 528 | 529 | # 总结 530 | 531 | 1. 可以在Envoy处理请求的各个阶段加入filter来定制化功能,可以自己编写c++的filter,用`REGISTER_FACTORY` 注册到对应的Factory map中 532 | 2. istio通过mixer filter实现了check和report功能 533 | -------------------------------------------------------------------------------- /envoy/解析envoy处理http请求(二)| 事件模型与连接管理.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | Envoy是istio的核心组件之一,以sidecar的方式与服务运行在一起,对服务的流量进行拦截转发。 具有路由,流量控制等等强大特性。 4 | 5 | Envoy利用libevent实现了基于事件触发的异步架构,所有的网络阻塞操作包括 accept,read, connect, write 都是由eventloop进行callback触发 6 | 7 | 本文以istio1.1所对应的Envoy版本进行源码流程分析 8 | 9 | # 名词解释 10 | 11 | - 下游: 发送请求给Envoy的服务,client 12 | - 上游:接收Envoy发送的请求,并返回响应的服务, server 13 | - Eventloop: libevent中的eventloop,可以用于注册事件的callback并触发回调 14 | - fd: Envoy创建的监听Connection和用于传输上下游数据的文件描述符 15 | 16 | # Envoy并发架构 17 | 18 | ![](https://picgo-1259280442.cos.ap-shanghai.myqcloud.com/20190604135318.png) 19 | 20 | > 摘自 21 | 22 | - Envoy进程由一个Main Thread和多个Worker Thread 组成 23 | 24 | - 每个Main和Worker包含一个eventloop,所有的处理都是由eventloop触发开始 25 | 26 | - Main负责xDS等功能,Worker负责处理连接和请求 27 | 28 | - 当一个client向Envoy建立连接的时候,因为所有Worker的EventLoop都注册了listening fd,会由内核决定分配给哪个Worker 29 | 30 | - 当一个下游client连接到了Envoy,在保持连接不断的情况下,会和同一个Worker进行通讯 31 | 32 | 33 | 34 | # libevent函数 35 | 36 | - `evconnlistener_new` 37 | 38 | 把一个 **已经bind port** 的listening fd和callback注册到eventloop,当accept到新连接的时候会触发callback。Envoy里面采用这个函数把同一个listening fd注册到所有的Worker的eventloop中,当新连接来的时候,由内核选择应该分发给哪个Worker 39 | 40 | - `event_assign` 41 | 42 | 把一个fd和一个callback注册到eventloop,当read write closed事件触发的时候触发callback 43 | 44 | - `event_active` 45 | 46 | 立即触发一个eventloop中的event,执行callback 47 | 48 | # 事件触发各阶段 49 | 50 | 1. client向Envoy建立连接 51 | 52 | ![](https://picgo-1259280442.cos.ap-shanghai.myqcloud.com/20190603220321.png) 53 | 54 | 2. client发送请求到Envoy,Envoy挑选节点向上游Server建立连接(如果连接池有空闲连接直接发送请求) 55 | 56 | ![](https://picgo-1259280442.cos.ap-shanghai.myqcloud.com/20190603233221.png) 57 | 58 | 3. Envoy向上游建连接成功,发送请求 59 | 60 | 61 | 62 | ![](https://picgo-1259280442.cos.ap-shanghai.myqcloud.com/20190603220357.png) 63 | 64 | 65 | 66 | 4. 上游server返回响应给Envoy 67 | 68 | ![](https://picgo-1259280442.cos.ap-shanghai.myqcloud.com/20190603220430.png) 69 | 70 | 5. Envoy返回请求给下游的client 71 | 72 | ![](https://picgo-1259280442.cos.ap-shanghai.myqcloud.com/20190603220452.png) 73 | 74 | 75 | 76 | 77 | 78 | # Cluster管理 (HTTP1) 79 | 80 | ## 层次结构图 81 | 82 | - 上面的实线表示下方的头部是上方的属性之一 83 | - 虚线表示两者相关 84 | - `ClusterManagerImpl`是Envoy内的单例,用于管理多个Worker上`ThreadLocalClusterManagerImpl` 85 | - `ThreadLocalCusterManagerImpl` 每个Worker都拥有一个,用于管理上游的连接和负载均衡上下文 86 | 87 | ![](https://picgo-1259280442.cos.ap-shanghai.myqcloud.com/20190531180804.png) 88 | 89 | ## 连接结构简介 90 | 91 | 1. 负载均衡器只挑选host,然后会从conn_pool_map取出对应host的连接池 92 | 93 | 2. 每个worker都都包含自己独立的连接池和负载均衡上下文 94 | 95 | (此处有发现设置RoundRobin负载均衡策略的时候,只有client保持长连接(不换worker)的情况下,才是严格的轮询) 96 | 97 | 3. 同一个上游节点的不同协议(http10, http11, http2, tcp)的连接池都是分开的 98 | 99 | 100 | 101 | ## 连接管理 102 | 103 | 对于同一个Worker,同一个Host,同一个协议,Envoy会维护一个连接池,连接池中http1有关属性如下(一下情况没有对Limit做说明,实际各个阶段会有stats和config limit来进行限制): 104 | 105 | - `busy_clients_ ` 当前发送了请求,还没处理响应的connection 106 | 107 | 变多: 108 | 109 | - 有新的请求需要发送的时候,如果`ready_clients`非空, 就会把`ready_clients_.front()`移到`busy_clients.front()`, 并发送请求 110 | - 向上游的连接connected时,如果 `(!pending_requests_.empty() && !ready_clients_.empty()) ` 就会把`ready_clients_.front()` 移到`busy_clients_` 111 | - 当有连接close的时候,如果`pending_requests_.size() > (ready_clients_.size() + busy_clients_.size())`,就会创建新的connection到`busy_clients_`并发送请求 112 | 113 | 变少: 114 | 115 | - connpool被删除,`busy_clients_`会被清空 116 | - 连接关闭时,如果是正在包含stream_wrapper (正在发送请求)的client,就会从`busy_clients`移除 117 | 118 | - `ready_clients_` 空闲连接 119 | 120 | 变多: 121 | 122 | - ((连接connected的时候 || 有响应完成的时候) && `pending_requests_` 为空) ,就会加入`ready_clients` 123 | 124 | 变少: 125 | 126 | - 在有新的请求需要发送的时候,如果`ready_clients`非空, 就会把`ready_clients_.front()`移到`busy_clients.front()` 127 | 128 | - 向上游的连接connected时,如果 `(!pending_requests_.empty() && !ready_clients_.empty()) ` 就会把`ready_clients_.front()` 移到`busy_clients_` 129 | 130 | - connpool被删除,`ready_clients_`会被清空 131 | 132 | - 空闲的连接被关闭的时候,会从`ready_clients_` 中移除 133 | 134 | 135 | 136 | - `pending_requests_` 代发送的请求 137 | 138 | 变多: 139 | 140 | - `ready_clients` 为空的时候,有新的请求,就会加入到`pending_requests` 141 | 142 | 变少: 143 | 144 | - 当上游连接connected时,发送请求到连接,并从`pending_requests` 中移除 145 | - 请求被终止的时候(超时,或者收到上游的响应结束 ),就会从`pending_requests` 中移除 146 | 147 | 148 | 149 | # 总结 150 | 151 | 1. Envoy的处理请求过程依赖libevent的事件触发,包括从建立连接到断开连接的全部过程 152 | 2. 每个Worker的连接池和负载均衡上下文都是独立的 153 | 3. 连接池内的连接都是同一worker,同一upstream host,同一协议 --------------------------------------------------------------------------------