Envoy 源码分析--LDS
Envoy 源码分析--LDS
LDS 是 Envoy 用来自动获取 listener 的 API。 Envoy 通过 API 可以增加、修改或删除 listener。
先来总结下 listener 的更新语义如下:
- 每个 listener 必须有一个唯一的名称。如果没有提供名称,Envoy 会生成一个 UUID 来作为它的名字。要动态更新 listener,管理服务必须提供一个唯一名称。
- 当 listener 被添加,在接收流量之前,会先进入 “预热” 阶段。
- 一旦 listener 被创建,就会保持不变。因此,listener 更新时,会创建一个全新的 listener(同一个侦听套接字)。这个新增加的 listener 同样需要一个 “预热” 过程。
- 当更新或删除 listener 时,旧的 listener 将被置于 “draining(驱逐)” 状态,和整个服务重新启动时一样。在删除侦听器并关闭任何其余连接之前,侦听器拥有的连接将在一段时间内优雅关闭(如果可能的话)。逐出时间通过
--drain-time-s设置。 - 相同名称的 listener 必须要有相同的配置地址。
接下来是对 lds 进行源码分析,对各种语义的情况都可以在源码中看到。
初始化
在初始化 bootstrap 配置时,如果有 lds 配置会进行初始化。
// Instruct the listener manager to create the LDS provider if needed. This must be done later
// because various items do not yet exist when the listener manager is created.
if (bootstrap_.dynamic_resources().has_lds_config()) {
listener_manager_->createLdsApi(bootstrap_.dynamic_resources().lds_config());
}
在 ListenerManagerImpl 类中通过 ProdListenerComponentFactory 配置类创建 lds。
// Server::ListenerManager
void createLdsApi(const envoy::api::v2::core::ConfigSource& lds_config) override {
ASSERT(lds_api_ == nullptr);
lds_api_ = factory_.createLdsApi(lds_config);
}
// Server::ListenerComponentFactory
LdsApiPtr createLdsApi(const envoy::api::v2::core::ConfigSource& lds_config) override {
return std::make_unique<LdsApiImpl>(lds_config, server_.clusterManager(), server_.dispatcher(),
server_.random(), server_.initManager(),
server_.localInfo(), server_.stats(),
server_.listenerManager(), server_.api());
}
新建 lds 时,会创建一个通道。
LdsApiImpl::LdsApiImpl(const envoy::api::v2::core::ConfigSource& lds_config,
Upstream::ClusterManager& cm, Event::Dispatcher& dispatcher,
Runtime::RandomGenerator& random, Init::Manager& init_manager,
const LocalInfo::LocalInfo& local_info, Stats::Scope& scope,
ListenerManager& lm, Api::Api& api)
: listener_manager_(lm), scope_(scope.createScope("listener_manager.lds.")), cm_(cm),
init_target_("LDS", [this]() { subscription_->start({}, *this); }) {
//创建 subscription 对象
subscription_ = Envoy::Config::SubscriptionFactory::subscriptionFromConfigSource(
lds_config, local_info, dispatcher, cm, random, *scope_,
"envoy.api.v2.ListenerDiscoveryService.FetchListeners",
"envoy.api.v2.ListenerDiscoveryService.StreamListeners",
Grpc::Common::typeUrl(envoy::api::v2::Listener().GetDescriptor()->full_name()), api);
Config::Utility::checkLocalInfo("lds", local_info);
init_manager.add(init_target_);
}
更新
有数据下发时,通道调用 callback 触发 onConfigUpdate。
// the configuration update targets.
callbacks_->onConfigUpdate(resources, version_info);
配置更新过程中如何判断哪些是移除的 listener?。首先创建一个移除的列表,将现有的 listener 加入,然后去除数据下发的 listener,剩下的就是需要移除的 listener。移除的 listener 会打印 lds: remove listener '{}'。
// We build the list of listeners to be removed and remove them before
// adding new listeners. This allows adding a new listener with the same
// address as a listener that is to be removed. Do not change the order.
for (const auto& listener : listener_manager_.listeners()) {
listeners_to_remove.emplace(listener.get().name(), listener);
}
for (const auto& listener : listeners) {
listeners_to_remove.erase(listener.name());
}
for (const auto& listener : listeners_to_remove) {
if (listener_manager_.removeListener(listener.first)) {
ENVOY_LOG(info, "lds: remove listener '{}'", listener.first);
}
}
移除 listener 时,如果是在 “预热” 阶段的 listener 直接删除,如果是在活动的 listener 将其置于驱逐中,过一段时间关闭。
// Destroy a warming listener directly.
if (existing_warming_listener != warming_listeners_.end()) {
(*existing_warming_listener)->debugLog("removing warming listener");
warming_listeners_.erase(existing_warming_listener);
}
// If there is an active listener it needs to be moved to draining.
if (existing_active_listener != active_listeners_.end()) {
drainListener(std::move(*existing_active_listener));
active_listeners_.erase(existing_active_listener);
}
// ListenerManagerImpl::drainListener
// 关闭accept,不再接收新连接
draining_it->listener_->debugLog("draining listener");
for (const auto& worker : workers_) {
worker->stopListener(*draining_it->listener_);
}
// 等待一段时间
draining_it->listener_->localDrainManager().startDrainSequence([this, draining_it]() -> void {
draining_it->listener_->debugLog("removing listener");
for (const auto& worker : workers_) {
// 移除 listener
worker->removeListener(*draining_it->listener_, [this, draining_it]() -> void {
// 通知主线程(移除是在工作线程移除的,主线程无法知道)
server_.dispatcher().post([this, draining_it]() -> void {
if (--draining_it->workers_pending_removal_ == 0) {
draining_it->listener_->debugLog("listener removal complete");
draining_listeners_.erase(draining_it);
stats_.total_listeners_draining_.set(draining_listeners_.size());
}
});
});
}
});
接下来是对配置内的 listener 进行增加或更新。增加或更新 listener 打印日志 lds: add/update listener '{}'。
for (const auto& listener : listeners) {
const std::string& listener_name = listener.name();
try {
if (listener_manager_.addOrUpdateListener(listener, version_info, true)) {
ENVOY_LOG(info, "lds: add/update listener '{}'", listener_name);
} else {
ENVOY_LOG(debug, "lds: add/update listener '{}' skipped", listener_name);
}
} catch (const EnvoyException& e) {
exception_msgs.push_back(fmt::format("{}: {}", listener_name, e.what()));
}
}
增加 listener 时,如果没有名称,Envoy 生成一个 UUID。更新 listener 如果没有名称,Envoy 生成 UUID,无法通过名称进行关联,因此不带名称进行更新,只会变成新增。
std::string name;
if (!config.name().empty()) {
name = config.name();
} else {
name = server_.random().uuid();
}
不论是增加还是更新 listener 都是直接新建,旧的 listener 会被 “draining(驱逐)” 。
ListenerImplPtr new_listener(
new ListenerImpl(config, version_info, *this, name, modifiable, workers_started_, hash));
ListenerImpl& new_listener_ref = *new_listener;
判断相同名称的 listener 必须有相同的配置地址。
if ((existing_warming_listener != warming_listeners_.end() &&
*(*existing_warming_listener)->address() != *new_listener->address()) ||
(existing_active_listener != active_listeners_.end() &&
*(*existing_active_listener)->address() != *new_listener->address())) {
const std::string message = fmt::format(
"error updating listener: '{}' has a different address '{}' from existing listener", name,
new_listener->address()->asString());
ENVOY_LOG(warn, "{}", message);
throw EnvoyException(message);
}
如果更新的 listener 在 “预热” 阶段的 listener 中,直接内部替换。
if (existing_warming_listener != warming_listeners_.end()) {
ASSERT(workers_started_);
new_listener->debugLog("update warming listener");
new_listener->setSocket((*existing_warming_listener)->getSocket());
*existing_warming_listener = std::move(new_listener);
}
如果更新的 listener 在活动中的 listener 中。当前工作线程已经启动的话,加入 “预热” 阶段的 listener,同时后继会把旧的 listener 置于 “draining(驱逐)” 状态。当前工作线程未启动状态,直接内部替换。
if (existing_active_listener != active_listeners_.end()) {
new_listener->setSocket((*existing_active_listener)->getSocket());
if (workers_started_) {
new_listener->debugLog("add warming listener");
warming_listeners_.emplace_back(std::move(new_listener));
} else {
new_listener->debugLog("update active listener");
*existing_active_listener = std::move(new_listener);
}
}
新增 listener 时,会先去 “draining(驱逐)” 状态的 listener 中查找是否已有相同地址的 listener,有相同的重新拉回来,并根据当前工作线程是否已就绪加入对应的 listener。
Network::SocketSharedPtr draining_listener_socket;
auto existing_draining_listener = std::find_if(
draining_listeners_.cbegin(), draining_listeners_.cend(),
[&new_listener](const DrainingListener& listener) {
return *new_listener->address() == *listener.listener_->socket().localAddress();
});
if (existing_draining_listener != draining_listeners_.cend()) {
draining_listener_socket = existing_draining_listener->listener_->getSocket();
}
new_listener->setSocket(draining_listener_socket
? draining_listener_socket
: factory_.createListenSocket(new_listener->address(),
new_listener->socketType(),
new_listener->listenSocketOptions(),
new_listener->bindToPort()));
if (workers_started_) {
new_listener->debugLog("add warming listener");
warming_listeners_.emplace_back(std::move(new_listener));
} else {
new_listener->debugLog("add active listener");
active_listeners_.emplace_back(std::move(new_listener));
}
最后把旧的 listener 置于 “draining(驱逐)” 状态。
// 调用 initialize()
new_listener_ref.initialize();
void ListenerImpl::initialize() {
last_updated_ = timeSource().systemTime();
if (workers_started_) {
// init_watcher
dynamic_init_manager_.initialize(*init_watcher_);
}
}
// init_watcher 在构造函数时创建
init_watcher_(std::make_unique<Init::WatcherImpl>(
"ListenerImpl", [this] { parent_.onListenerWarmed(*this); })),
void ListenerManagerImpl::onListenerWarmed(ListenerImpl& listener) {
auto existing_active_listener = getListenerByName(active_listeners_, listener.name());
auto existing_warming_listener = getListenerByName(warming_listeners_, listener.name());
(*existing_warming_listener)->debugLog("warm complete. updating active listener");
if (existing_active_listener != active_listeners_.end()) {
// 旧的 listener 置于 “draining(驱逐)” 状态。
drainListener(std::move(*existing_active_listener));
// 预热的 listener 放到活动的 listener
*existing_active_listener = std::move(*existing_warming_listener);
} else {
active_listeners_.emplace_back(std::move(*existing_warming_listener));
}
}
Envoy 源码分析--LDS的更多相关文章
- Envoy 源码分析--程序启动过程
目录 Envoy 源码分析--程序启动过程 初始化 main 入口 MainCommon 初始化 服务 InstanceImpl 初始化 启动 main 启动入口 服务启动流程 LDS 服务启动流程 ...
- Envoy 源码分析--network L4 filter manager
目录 Envoy 源码分析--network L4 filter manager FilterManagerImpl addWriteFilter addReadFilter addFilter in ...
- Envoy 源码分析--network
目录 Envoy 源码分析--network address Instance DNS cidr socket Option Socket ListenSocket ConnectionSocket ...
- Envoy 源码分析--buffer
目录 Envoy 源码分析--buffer BufferFragment RawSlice Slice OwnedSlice SliceDeque UnownedSlice OwnedImpl Wat ...
- Envoy 源码分析--event
目录 Envoy 源码分析--event libevent Timer SignalEvent FileEvent RealTimeSystem 任务队列 延迟析构 dispacth_thread E ...
- u-boot源码分析之C语言段
题外话: 最近一直在学习u-boot的源代码,从代码量到代码风格,都让我认识到什么才是真正的程序.以往我所学到的C语言知识和u-boot的源代码相比,实在不值一提.说到底,机器都是0和1控制的.感觉这 ...
- Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)
http://blog.chinaunix.net/uid-20543672-id-3157283.html Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3 ...
- u-boot源码分析
Uboot源码分析 源码以u-boot-1.3.4为基准,主芯片采用at91sam9260,主要介绍uboot执行流程. uboot官网:http://www.denx.de/wiki/U-Boot/ ...
- U-BOOT概述及源码分析(一)
嵌入式Linux系统从软件角度通常可以分为以下4个层次: 引导加载程序 | Linux内核 | 文件系统 | 用户应用程序 嵌入式Linux系统中典型分区结构: 正常启动过程中,Bootloader首 ...
随机推荐
- 如何在 Xcode 中进行 Qt 开发(可使用使用 Homebrew 来安装qt)
第一步 安装 Qt 分为两种情况: 1. 希望使用 Qt Quick 等先进Qt5技术, 2. 只需要 Qt 4.8的类库即可. 第一种, 直接去官网下载 Mac安装包, 在此不作过多说明, 开发时也 ...
- Delphi中inherited问题
inherited Create(AOwner); 和直接写inherited有区别吗 有区别,inherited Create是指定调用父类的Create方法,当然你也可以inherited Des ...
- javascript 实现ajax
AJAX 英文名称 Asynchronous JavaScript and XML即异步的 JavaScript 和 XML AJAX 是与服务器交换数据并更新部分网页一门无刷新技术构建自己的ajax ...
- ABP开发框架前后端开发系列---(8)ABP框架之Winform界面的开发过程
在前面随笔介绍的<ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理>里面,介绍了如何改进和完善审计日志和登录日志的应用服务端和Winform客户端,由于篇幅限制,没有进 ...
- .Net for Spark 实现 WordCount 应用及调试入坑详解
.Net for Spark 实现WordCount应用及调试入坑详解 1. 概述 iNeuOS云端操作系统现在具备物联网.视图业务建模.机器学习的功能,但是缺少一个计算平台产品.最近在调研使用 ...
- spring cloud 系列第1篇 —— eureka 服务的注册与发现 (F版本)
源码仓库地址:https://github.com/heibaiying/spring-samples-for-all 一.eureka 简介 Spring Cloud Eureka使用Netflix ...
- Docker安装MySql-挂载外部数据和配置
环境 CentOS:7 Docker:1.31.1 MySql:5.7 拷贝mysql配置文件 1.首先创建mysql容器 -p : -e MYSQL\_ROOT\_PASSWORD= -d my ...
- 【设计模式】行为型11解释器模式(Interpreter Pattern)
解释器模式(Interpreter Pattern) 解释器模式应用场景比较小,也没有固定的DEMO,中心思想就是自定义解释器用来解释固定的对象. 定义:给定一个语言,定义它的文法表示,并定义一个 ...
- Programming In Lua 第八章
1, 也就是说,lua虽然会把代码预编译成中间码,以提高运行速度.但其会在程序运行过程中需要编译器,所以其仍然是解释型语言.loadfile会加载一个文件并将其编译成中间码,并返回一个函数. 2, 3 ...
- CentOS7系统安装
CenOS7安装系统 镜像下载地址: http://isoredirect.centos.org/centos/7/isos/x86_64/ https://mirrors.aliyun.com/ce ...