Envoy 锁优化技术文档
1. 介绍
Envoy 是一个高性能的开源边缘和服务代理,在处理高并发请求时需要高效的线程同步机制。为了实现这一目标,Envoy 实现了多种锁优化技术,包括:
– 细粒度锁策略
– 线程本地存储优化
– 原子操作使用
– 无锁数据结构
– 死锁预防机制
– 锁争用检测和诊断
这些优化使得 Envoy 能够在高并发场景下保持高性能和稳定性。
2. 常见锁类型和实现
2.1 MutexBasicLockable
Envoy 基于 Abseil 的 `absl::Mutex` 实现了自己的锁机制:
“`cpp
class MutexBasicLockable : public BasicLockable {
public:
void lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override { mutex_.Lock(); }
bool tryLock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true) override { return mutex_.TryLock(); }
void unlock() ABSL_UNLOCK_FUNCTION() override { mutex_.Unlock(); }
private:
friend class CondVar;
absl::Mutex mutex_;
};
“`
2.2 条件变量
Envoy 还提供了基于 `absl::CondVar` 的条件变量实现:
“`cpp
class CondVar {
public:
enum class WaitStatus {
Timeout,
NoTimeout, // Success or Spurious
};
void notifyOne() noexcept { condvar_.Signal(); }
void notifyAll() noexcept { condvar_.SignalAll(); };
void wait(MutexBasicLockable& mutex) noexcept ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) {
condvar_.Wait(&mutex.mutex_);
}
template <class Rep, class Period>
WaitStatus waitFor(MutexBasicLockable& mutex,
std::chrono::duration<Rep, Period> duration) noexcept
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) {
return condvar_.WaitWithTimeout(&mutex.mutex_, absl::FromChrono(duration))
? WaitStatus::Timeout
: WaitStatus::NoTimeout;
}
private:
absl::CondVar condvar_;
};
“`
2.3 锁守卫
Envoy 提供了标准的 RAII 锁守卫:
“`cpp
template <class Lockable> class LockGuard {
public:
explicit LockGuard(Lockable& lockable) ABSL_EXCLUSIVE_LOCK_FUNCTION(lockable)
: lockable_(lockable) {
lockable_.lock();
}
~LockGuard() ABSL_UNLOCK_FUNCTION() { lockable_.unlock(); }
LockGuard(const LockGuard&) = delete;
LockGuard& operator=(const LockGuard&) = delete;
private:
Lockable& lockable_;
};
“`
3. 线程本地存储优化
3.1 线程本地存储的使用
Envoy 大量使用线程本地存储(Thread Local Storage, TLS)来避免锁竞争:
“`cpp
// 在 common/buffer 中使用线程本地存储优化
static thread_local absl::InlinedVector<Slice::StoragePtr, free_list_max_> free_list_;
“`
3.2 线程本地对象管理
Envoy 使用 `ThreadLocal` 类来管理线程本地对象:
“`cpp
// 在 runtime/runtime_impl.h 中
class RuntimeImpl : public Runtime {
public:
// …
private:
ThreadLocal::Instance& tls_;
// …
};
“`
3.3 线程本地存储的优势
使用线程本地存储的主要优势:
– 避免锁竞争,提高性能
– 每个线程独立访问,无同步开销
– 简化代码逻辑
4. 原子操作优化
4.1 原子操作的使用
Envoy 大量使用 `std::atomic` 进行原子操作:
“`cpp
// 在 connection_impl.h 中使用原子操作管理全局连接 ID
static std::atomic<uint64_t> next_global_id_;
// 在 runtime/runtime_impl.cc 中使用原子操作进行状态管理
std::atomic<bool> RuntimeImpl::initialized_(false);
“`
4.2 原子指针管理
Envoy 提供了 `AtomicPtrArray` 和 `AtomicPtr` 类来管理原子指针:
“`cpp
template <class T, uint32_t size, AtomicPtrAllocMode alloc_mode>
class AtomicPtrArray : NonCopyable {
public:
T* get(uint32_t index, const MakeObject& make_object) {
std::atomic<T*>& atomic_ref = data_[index];
// 首先使用原子加载检查是否已分配
if (atomic_ref.load() == nullptr) {
absl::MutexLock lock(&mutex_);
// 再次检查以避免竞态条件
if (atomic_ref.load() == nullptr) {
atomic_ref = make_object();
}
}
return atomic_ref.load();
}
private:
std::atomic<T*> data_[size];
absl::Mutex mutex_;
};
“`
4.3 无锁数据结构
Envoy 使用无锁数据结构来提高并发性能:
“`cpp
// 在 fine_grain_logger.cc 中使用无锁队列
class FineGrainLoggerImpl : public Logger::FineGrainLogger {
public:
// …
private:
absl::flat_hash_map<absl::string_view, absl::MutexLock> loggers_;
// …
};
“`
5. 细粒度锁策略
5.1 细粒度锁的使用
Envoy 使用细粒度锁策略来减少锁的持有时间:
“`cpp
// 在 http/conn_manager_impl.cc 中使用细粒度锁管理路由表
class ConnectionManagerImpl : public Http::ConnectionManager {
public:
// …
private:
mutable absl::Mutex route_table_mutex_;
std::shared_ptr<const Router::Config> route_table_ ABSL_GUARDED_BY(route_table_mutex_);
// …
};
“`
5.2 读写锁优化
Envoy 使用读写锁来优化读多写少的场景:
“`cpp
// 在 config/grpc_mux_impl.h 中使用读写锁
class GrpcMuxImpl : public Config::GrpcMux {
public:
// …
private:
mutable absl::Mutex config_mutex_;
// …
};
“`
6. 死锁预防机制
6.1 死锁预防技术
Envoy 使用多种技术预防死锁:
1. **锁顺序固定**:确保所有线程以相同的顺序获取锁
2. **超时机制**:设置锁获取超时时间
3. **锁层次化**:将锁按优先级分层
4. **死锁检测**:使用工具检测死锁
6.2 锁跟踪和诊断
Envoy 提供了锁跟踪机制来帮助诊断死锁:
“`cpp
// 在 mutex_tracer_impl.h 中
class MutexTracerImpl : public MutexTracer {
public:
// …
void onLockStart(absl::Mutex* mutex) override;
void onLockEnd(absl::Mutex* mutex) override;
// …
};
“`
7. 性能优化策略
7.1 避免锁的策略
Envoy 使用以下策略避免锁:
“`cpp
// 使用线程本地存储避免锁
static thread_local absl::InlinedVector<Slice::StoragePtr, free_list_max_> free_list_;
// 使用无锁数据结构
absl::flat_hash_map<absl::string_view, absl::MutexLock> loggers_;
// 使用原子操作
std::atomic<bool> initialized_;
“`
7.2 锁优化技术
Envoy 使用以下技术优化锁的性能:
“`cpp
// 使用 TryLock 避免等待
if (mutex_.TryLock()) {
// 处理临界区
mutex_.Unlock();
} else {
// 处理未获取到锁的情况
}
// 使用共享锁(读锁)
absl::MutexSharedLock lock(&mutex_);
// 使用延迟初始化
T* get(uint32_t index, const MakeObject& make_object) {
std::atomic<T*>& atomic_ref = data_[index];
if (atomic_ref.load() == nullptr) {
absl::MutexLock lock(&mutex_);
if (atomic_ref.load() == nullptr) {
atomic_ref = make_object();
}
}
return atomic_ref.load();
}
“`
8. 使用例子
8.1 线程本地存储的使用
“`cpp
// 在 buffer/own_impl.cc 中
class OwnedImplReservationSlicesOwnerMultiple : public OwnedImplReservationSlicesOwner {
public:
static constexpr uint32_t free_list_max_ = Buffer::Reservation::MAX_SLICES_;
Slice::SizedStorage newStorage() {
Slice::SizedStorage storage{nullptr, Slice::default_slice_size_};
if (!free_list_ref_.empty()) {
storage.mem_ = std::move(free_list_ref_.back());
free_list_ref_.pop_back();
} else {
storage.mem_.reset(new uint8_t[Slice::default_slice_size_]);
}
return storage;
}
private:
static thread_local absl::InlinedVector<Slice::StoragePtr, free_list_max_> free_list_;
};
“`
8.2 原子操作的使用
“`cpp
// 在 connection_impl.h 中
class ConnectionImpl : public ConnectionImplBase {
public:
static uint64_t nextGlobalIdForTest() { return next_global_id_; }
// …
private:
static std::atomic<uint64_t> next_global_id_;
};
“`
8.3 使用锁的例子
“`cpp
// 在 config/grpc_mux_impl.cc 中
class GrpcMuxImpl : public Config::GrpcMux {
public:
void addWatch(const std::string& type_url, Config::Watch* watch) override {
absl::MutexLock lock(&config_mutex_);
// 临界区操作
auto& watches = watches_[type_url];
watches.insert(watch);
}
// …
private:
mutable absl::Mutex config_mutex_;
absl::flat_hash_map<std::string, absl::flat_hash_set<Config::Watch*>> watches_
ABSL_GUARDED_BY(config_mutex_);
};
“`
9. 最佳实践
9.1 锁的选择
根据场景选择适当的锁类型:
| 场景 | 推荐锁类型 |
| 读多写少 | `absl::Mutex` (支持共享锁) |
| 写多读少 | `absl::Mutex` |
| 高并发 | 原子操作 + 无锁数据结构 |
| 线程本地 | 线程本地存储 |
9.2 锁优化建议
1. 避免使用大粒度锁,使用细粒度锁
2. 优先使用线程本地存储
3. 考虑使用无锁数据结构
4. 使用原子操作替代简单的锁
5. 减少锁的持有时间
6. 使用锁跟踪工具检测锁竞争
9.3 性能优化建议
1. 避免在锁保护的临界区执行耗时操作
2. 使用 TryLock 避免等待
3. 考虑使用异步操作
4. 对锁竞争进行性能分析
5. 定期检查锁的使用是否合理
夜雨聆风