乐于分享
好东西不私藏

Envoy 锁优化技术文档

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. 定期检查锁的使用是否合理