ALittleGuy

ALittleGuy

Traffic Server 代码阅读(一) 架构介绍

18
2025-04-20

背景

Apache Traffic Server (Traffic Server) 简称 ATS或者TS,是一个高性能的代理服务器,在数据缓存和反代的场景下表现极佳,能和 Nginx 相媲美。2010 年时成为 Apache 基金的顶级项目。接近15年前的项目,现在依旧还能打。这里学习一下ATS 线程架构相关的核心组件。

https://github.com/apache/trafficserver

branch:master

commit_id:756c49ff52219e376bd60420e6081f64f8b5d3c7

架构

ATS 采用了单进程多线程的架构,使用经典的工作线程模式 以及 状态机/Continuation 的手法,线程的事件循环驱动状态机运转,将所有阻塞操作包括网络、IO、加锁 等场景 异步到状态机上的不同状态或者定时重新调度避免阻塞,实现异步的工作流程。

主要线程分3类

  1. 工作线程,事件处理线程,一个线程对应一个核,从多种队列中循环处理工作事件、网络事件
  2. Accept线程,处理新建连接的线程,将新连接进行封装,调度到工作线程上执行。
  3. 长时任务线程,处理长时间的任务,工作线程将长时运算或者需要大量拆分子任务的任务调度到这类线程,减少工作线程处理延迟。

一个普通的工作线程调度如下:

trafficserver1-CodW.jpg

架构特点:

  1. 每个线程基本对应一个逻辑核,可以控制CPU使用率,以及一定程度上控制线程亲和性
  2. 单个TCP连接的所有操作可以在一个线程上完成,不需要加锁
  3. 配合状态机和工作线程,方便做统一的内存管理,适配一些内存技术比如大页内存、NUMA
  4. 工作线程数和核数基本绑定,线程数量可控的场景下可以使用TLS实现高速缓存
  5. 使用状态机的模式,一个连接在一个大循环的结构中处理多个小循环。

核心结构

事件结构

class Continuation : private force_VFPT_to_top
{
public:
  Ptr<ProxyMutex> mutex;
  EThread *thread_affinity = nullptr;
  ContinuationHandler handler = nullptr;
  TS_INLINE int
  handleEvent(int event = CONTINUATION_EVENT_NONE, void *data = nullptr)
  {
    // If there is a lock, we must be holding it on entry
    ink_release_assert(!mutex || mutex->thread_holding == this_ethread());
    return (this->*handler)(event, data);
  }

...
};
class Action
{
public:
  Continuation *continuation = nullptr;
  bool cancelled = false;
  
  virtual void
  cancel(Continuation *c = nullptr)
  {
    ink_assert(!c || c == continuation);
    ink_assert(!cancelled);
    cancelled = true;
  }
class Event : public Action
{
public:
 EThread *ethread = nullptr;
 int          callback_event = 0;
 ink_hrtime timeout_at = 0;
 ink_hrtime period     = 0;
}

ATS 的时间结构划分了3个层级,Event <- Action <- Continuation

  • Continuation 提供最基本任务函数handle的指针,后续基本所有的业务状态机都要继承这个结构
  • Action 内部包裹了一个Continuation,同时新增了一个cacelled字段,提供cancel的逻辑,大部分异常场景可能会需要把已经调度出去的任务取消掉,这里就发挥了作用
  • Event 结构是线程最终处理的结构,继承了Action之后,其补充处理线程、超时时间、定时时间等等控制信息

线程结构

ATS 的线程结构是EThread,用的是posix thread的接口,EThread的核心内容如下:

class EThread : public Thread
{
public:
  static thread_local EThread *this_ethread_ptr;
  
  Event *schedule_imm(Continuation *c, int callback_event = EVENT_IMMEDIATE, void *cookie = nullptr);
  Event *schedule_at(Continuation *c, ink_hrtime atimeout_at, int callback_event = EVENT_INTERVAL, void *cookie = nullptr);
  Event *schedule_in(Continuation *c, ink_hrtime atimeout_in, int callback_event = EVENT_INTERVAL, void *cookie = nullptr);
  void execute() override;
  
  EventIO *ep = nullptr;
  char thread_private[PER_THREAD_DATA];
  ProtectedQueue     EventQueueExternal;
  PriorityEventQueue EventQueue;
  
  ...
}

EThread的核心组件本地队列、外部队列,这里两个队列的结构其实都存放在了EventQueueExternal

  • 本地队列用于调度当前线程发起的事件,不需要加锁
  • 外部队列用于调度其他线程发起的事件,采用了无锁队列的技术

EventQueue (priority queue)则是一个基于时间桶的优先队列,用于处理定时事件

可以看到EThread中没有NetHandler的字段,这是因为EThread在自身的内存结构上开辟了一个buf thread_private,ATS 把NetHandler还有其他部分结构直接映射到了这个buf,后续直接操作这个buf

NetHandler *
get_NetHandler(EThread *t)
{
  return static_cast<NetHandler *>(((void *)((char *)(t) + (unix_netProcessor.netHandler_offset))));
}

EThread自身提供了调度的接口,schedule_imm schedule_at schedule_in schedule_every等等,可以调度立即执行的任务、延迟任务、轮询任务 等等。只需要设置好状态机的状态,直接执行即可,这些函数会把Continuation封装成Event放进去队列

EThread 事件循环的调度逻辑相对比较复杂,这里介绍一下总体的逻辑

事件处理

trafficserver2-HwMs.jpg

上述流程中对比上文中多了一个队列 negative queue,这个队列是存放超时事件小于0的事件,negative queue属于栈变量,目前最新的代码中看到的schedule超时时间 < 0 的场景较少