Squid Web Cache Wiki

Squid Web Cache 文档

🔗 Feature: SMP Scalability

🔗 术语

本节介绍 SMP 相关术语。术语及其定义仍在不断发展中,并非所有 Squid 文档和开发人员都使用相同的术语。

请注意,同一个进程可能扮演多个角色。例如,当您使用 -N 命令行选项启动 Squid 时,将只有一个 Squid 进程在运行,并且该单个进程扮演 Master 和 Worker 的角色。

🔗 当前状态和架构

Squid-3.2 使用 workers 支持基本的 SMP 扩展。管理员可以配置和运行一个 Squid 实例,该实例可以生成多个 worker 进程来利用所有可用的 CPU 核心。

Worker 接受新的 HTTP 请求,并处理每个接受的请求直到完成。Worker 可以共享 http_ports,但它们之间不传递事务。Worker 具有单个非 SMP Squid 的所有功能,但 Worker 可以配置不同(例如,服务于不同的 http_ports)。cpu_affinity_map 选项允许为每个 Worker 分配一个 CPU 核心。

🔗 Worker 如何协调?

当需要时,一个特殊的 Coordinator 进程会启动 Worker 并协调它们的活动。以下是 Coordinator 的一些职责:

Coordinator 不参与常规事务处理,也不决定哪个 Worker 处理传入的连接或请求。Coordinator 进程通常是空闲的。

🔗 Worker 可以共享什么?

通过使用 Coordinator 和通用的配置文件,Squid Worker 可以接收相同的配置信息并同步它们的一些活动。默认情况下,Squid Worker 共享以下内容:

缓存索引是共享的,无需复制。其他共享信息通常在 RAM 使用量方面很小,并且本质上是复制的,以避免锁定和相关的性能开销。

条件配置和与 Worker 相关的宏可用于限制共享。例如,每个 Worker 可以获得一个专用的 http_port 来监听。

目前,Squid Worker 不共享也不同步其他资源和服务,包括(但不限于):

一些不熟悉 SMP 的功能在 SMP 模式下仍然可以工作(例如,DNS 响应将被单个 Worker 缓存),但它们的性能会因缺乏同步而受到影响,并且由于信息重复而需要更多资源(例如,每个 Worker 可能独立解析并缓存同一个域名)。一些不熟悉 SMP 的功能会严重损坏(例如,基于 ufs 的 cache_dir 会损坏),除非使用 squid.conf 条件来防止此类损坏。一些不熟悉 SMP 的功能似乎可以工作,但会不正确地工作(例如,延迟池将在每个 Worker 的基础上限制带宽,而不会在 Worker 之间共享流量信息,也不会在 Worker 之间分配带宽限制)。

🔗 为什么是进程?线程不是更好吗?

有几个原因决定了为 Worker 选择进程而不是线程:

总而言之,我们使用进程而不是线程,因为它们使我们能够在合理的时间范围内实现相似的 SMP 性能。使用线程被认为是不切实际的。

🔗 谁决定哪个 Worker 获得请求?

所有共享 http_port 的 Worker 都监听相同的 IP 地址和 TCP 端口。操作系统使用锁保护共享的监听套接字,并决定哪个 Worker 获得等待接受的新 HTTP 连接。一旦 Worker 接受了传入连接,该连接就一直保留在该 Worker 上。

🔗 相似的 Worker 会收到相似的工作量吗?

我们期望操作系统通过适当地将下一个传入连接请求分配给负载最轻的 Worker/核心来在多个 Worker 之间平衡负载。实验室测试和初始部署的 Worker 统计数据证明我们错了。以下是几个相同的 Worker 在处理中等负载一段时间后的累计 CPU 时间示例:

累计 CPU Worker
利用率(分钟)  
20 (squid-3)
16 (squid-4)
13 (squid-2)
8 (squid-1)

该表显示了 Worker 负载的显着倾斜:squid-3 Worker 花费了 20 分钟处理流量,而 squid-1 只工作了 8 分钟。随着运行时间的推移,这种不平衡不会改善,因为最忙的 Worker 仍然是最忙的。

虽然我们不知道这种不平衡是否会导致忙碌 Worker 的响应时间变差,但从系统整体平衡的角度来看,这是相当不受欢迎的。

经过数天的 Squid 实验、研究 Linux TCP 堆栈源代码以及与其他开发人员讨论问题,我们最终找到了一种相对紧凑且开销低的变通方法。事实证明,最先被唤醒接受新客户端连接的 Worker 通常是最后将其监听描述符注册到 epoll(2) 的 Worker。这种依赖关系相当奇怪,因为 epoll 集合之间没有在 Squid Worker 之间共享;它必须在监听套接字级别工作(这些套接字共享的)。特别感谢 HenrikNordström 进行的一次发人深省的讨论,它提供了解决问题的最后一块拼图。

负责此调度行为的确切内核 TCP 堆栈代码目前未知,并且即使对专家 Linux 内核开发人员来说也很难找到。最终,我们可能会找到它并提出一个内核模块或补丁,以更好地平衡 Squid 环境中的监听器选择。

但是,我们已经有足够的信息来在 Squid 层面解决这个问题。我们开发了一个补丁,该补丁每隔几秒钟指示一个 Squid Worker 从 epoll(2) 集合中删除并立即重新插入其监听描述符。这种 epoll 操作相对便宜。该补丁确保 Worker 通过这种方式轮流“轻触”其监听描述符,因此在一个给定的“轻触”间隔内只有一个 Worker 最活跃。

此更改导致 Worker 之间的负载分布合理。这是每台 Worker 的当前 CPU 核心利用率以及该 Worker 累积的总 CPU 时间的即时快照。

CPU 利用率 Worker  
现在 累积  
(%) (分钟)  
41 2826 (squid-3)
23 2589 (squid-2)
9 2345 (squid-4)
7 2303 (squid-5)
9 2221 (squid-6)
11 2107 (squid-1)

即时 CPU 利用率(最左边的列)如预期般不平衡。管理员监控该列会看到最活跃的 Worker 组每隔几秒钟就会变化,新的最忙碌的 Worker 离开,而该组中最老的 Worker 变得大部分空闲。

累积 CPU 时间分布(中间的列)现在更加均匀。尽管有七个 Worker,Squid 显示历史最活跃和最不活跃 Worker 之间的差异仅为 25%,而没有补丁的四个 Worker 之间的差异为 60%。通过调整补丁参数(如“轻触”间隔)或增加 Worker 负载,可以实现更好的分布,使得每个 Worker 不太可能错过“轻触”其 epoll(2) 集合的机会。

我们正在努力使该补丁与非 epoll 环境兼容,并将提议将其集成到 Squid v3.2 中。

🔗 为什么没有专用的进程来接受请求?

当前设计的一个常见替代方案是使用一个专用的进程来接受传入连接并将其交给一个 Worker 线程。我们已考虑并拒绝了这种方法作为初始实现,原因如下:

如果需要,可以稍后添加一个专用的接受进程(带有一些额外的 HTTP 感知调度逻辑)。

🔗 一个 Squid 实例有多少个进程?

在未运行 -N 命令行选项的单个 Squid 实例中,Squid 进程的数量通常是:

+ 1   master process (counted only in SMP mode) plus
+ W   workers (workers in squid.conf; defaults to 1) plus
+ D   diskers (rock cache_dirs in squid.conf; defaults to 0) plus
+ 1   Coordinator process (exists only in SMP mode).

例如,如果您没有显式配置 Squid workers 和 rock cache_dirs,那么 Squid 将在非 SMP 模式下运行,总共将获得 0+1+0+0=1 个 Squid 进程。另一方面,如果您使用 3 个 worker 和 1 个 rock cache_dir 显式配置 Squid,那么 Squid 将在 SMP 模式下运行,总共将获得 1+3+1+1=6 个 Squid 进程。

上述公式不包括辅助进程和其他未运行 Squid 可执行代码的进程。

🔗 如何配置 SMP Squid 以获得最佳性能?

如果您拥有强大的硬件,希望优化性能,并愿意花费大量时间/精力/金钱来实现这一目标,那么请考虑以下 SMP 经验法则:

  1. 如果您想缓存,请使用系统能够安全处理的最大 cache_mem。请注意,Squid 不会告诉您何时分配过多,但可能会崩溃。如果您不想缓存,请将 cache_mem 设置为零,使用 cache 指令禁止缓存,并忽略以下规则 #3。
  2. 一个或两个 CPU 核心保留给操作系统,具体取决于网络使用级别。使用操作系统的 CPU 亲和性配置网络中断,将 NIC 中断限制在这些“仅限 OS”的核心上。
  3. 每个物理磁盘主轴一个 Rock cache_dir,没有其他 cache_dir。不使用 RAID。Disker 可能能够使用虚拟 CPU 核心。Rock 的调优很棘手。请参阅 Rock Store 功能页面 上的性能调优建议。请注意,与其他 cache_dir 类型相比,Rock cache_dir 在 Squid 启动期间加载速度较慢,并且可能存在其他与您的部署需求不兼容的问题。当然,您可以使用其他 cache_dir 类型而不是 Rock。这些SMP规则使用 Rock,因为其他 cache_dir 不是 SMP 感知的。
  4. 每个剩余的非虚拟 CPU 核心一个 SMP worker。请注意,不要过度加载共享同一物理 CPU 核心的多个虚拟 CPU 核心——这些虚拟核心通常能完成的有用工作比一个负载过重的物理 CPU 核心少,因为多个虚拟核心会浪费资源来争夺对它们唯一的物理核心的访问权,而这个物理核心是唯一能完成有用工作的地方。虚拟核心通常最适合半空闲的后台任务,而不是具有近乎实时约束的繁忙 Worker。
  5. 为每个 Squid kid 进程(disker 和 worker)使用 CPU 亲和性。禁止内核将 kid 从一个 CPU 核心移动到另一个 CPU 核心。没有您的帮助,通用的操作系统内核很可能无法很好地平衡您的 Squid 服务器负载。
  6. 监视单个 CPU 核心的利用率(而不仅仅是所有核心的平均值或总和!)。调整 worker 的数量、disker 的数量以及 CPU 亲和性映射,以在留有健康的过载安全裕度的同时实现平衡。

免责声明:结果可能因人而异。上述一般规则可能不适用于您的环境。遵循它们不太可能足以实现顶级的性能,尤其是在不理解根本问题的情况下。在给定硬件上实现顶级的 Squid 性能需要大量工作。如果您的 Squid 已经运行正常,请不要为此烦恼。

🔗 SMP 架构层

经过长时间的讨论,项目开发人员一致认为,在 Squid 架构的不同层面上可能存在不同的设计选择。本节试图记录这些与 SMP 相关的层。然而,对于基本层(顶层和底层)之外的许多细节,尚不清楚是否存在共识。

🔗 1. 顶层:Workers

多个 Squid worker 进程和/或线程。每个 worker 负责一部分事务,Worker 之间除了缓存外,交互很少。有一个主进程或线程用于 worker 协调。

🔗 2. 中间层:多线程进程

最终 SMP 架构的中间层将是一个处理多个线程中混合操作的进程。现有的二进制文件需要大量工作来识别哪些部分适合成为单独的线程,并清理代码以实现这一点。

某些组件可以被隔离成单独的应用程序进程。

此开发部分已知工作量巨大,预计将在多个发行版中逐步完成。这项工作始于早期 Squid-2 发行版,目前仍在进行中。

处理磁盘删除、ICMP、身份验证和 URL 重写的辅助进程的接口属于此层。

🔗 3. 最底层:多个事件和信号驱动的线程

在每个操作线程的最底层,将保留当前非阻塞事件的设计。这已被证明在扩展方面非常有效。

这项工作与中间层的工作是联合的。识别可以作为线程运行且与其他线程交互最少的事件组。每个线程拉取数据并在其上运行许多事件段的方法比频繁启动和停止线程更受青睐。

目前现有的处理路径需要进行审计,有些可能需要进行修改以减少资源交互。

🔗 进展和依赖关系

这构成了 Squid-3 维护者如何看待当前朝着 SMP 支持的工作流程。在每一个转折点都可能出现问题和意想不到的事情,首先是对此视图本身的分歧。

https://squid.org.cn/Devel/papers/threading-notes.txt 虽然已过时,但仍然包含对 Squid 内部 SMP 问题的一个良好且有效的分析,这些问题必须克服。

我们将 Squid 的 SMP 要求分解成一系列较小的工件,并尝试在有空余时间时完成以下工作:

  1. 将 Squid 代码模块化为适合 SMP 考虑的紧凑逻辑工作单元。跟踪为 Features/SourceLayout
  2. 然后,生成的模块库需要成为完全的异步 Features/NativeAsyncCalls 作业。
  3. 最后,将生成的作业制成 SMP 线程,以利用多个 CPU 之一。代码已检查线程安全性和高效资源处理。回想一下,一个作业需要它的调用按顺序发生。可能错了,但似乎异步调用可以在 CPU 之间浮动,只要它们保持顺序性。异步作业可以完全并行交错运行,可能在某个 Job 依赖于另一个 Job 的地方进行一些锁定。

一些其他功能旨在减少 SMP 的阻塞问题。并非完全沿着 SMP 功能路径前进,但为了使这些步骤成为可能而必需。

🔗 资源和服务的共享

要安全地在 Worker 之间共享资源或服务,该资源或服务必须以安全共享的方式重写,或者其所有用法都必须被全局锁定。前者通常很困难,而后者通常效率低下。此外,由于众多相互依赖关系,通常无法重写单个资源/服务算法(因为所有底层代码都必须是安全共享的)或全局锁定它(因为这会导致死锁)。

目前已隔离但可能受益于共享的资源和服务包括:

可能需要以安全共享的方式重写或替换的低级组件包括:

经常被提及的一种可能性是使用具有更好线程安全实现的公共实现替换一个或多个低级组件(通常指链表和哈希算法)。但是,需要进行深入测试以检查是否适合快速高效的版本。

🔗 故障排除

🔗 Ipc::Mem::Segment::create 失败,无法 shm_open(…): (13) Permission denied

在 Linux 上,页面池应该“直接工作”。然而,它仍然依赖于 SHM 设备映射的初始化。

将以下行添加到您的 /etc/fstab 文件

shm        /dev/shm    tmpfs    nodev,nosuid,noexec    0    0

之后,使用(作为 root)

mount shm

🔗 Ipc::Mem::Segment::create 失败,无法 shm_open(…): (2) No such file or directory

参见上文。

🔗 无法将套接字 FD NN 绑定到 [::]: (13) Permission denied

:information_source: 如果您禁用了 IPv6,也可能显示为 Cannot bind socket FD NN to 0.0.0.0: (13) Permission denied

Squid 在 localstatedir 中注册其 IPC 通道套接字(Unix Domain Sockets 或 UDS)。对于标准安装,这通常是 /var/run/squid。如果您将 Squid 安装到其他目录,请参阅 –localstatedir ./configure 选项。

检查 /var/run/squid(或您的任何 localstatedir)的权限。localstatedir 目录需要由 Squid 用户拥有,SMP Squid 才能工作。

🔗 写入失败 (40) 消息过长

Squid Worker 交换 Unix Domain Sockets (UDS) 消息(请勿与 UDP 消息或 System V IPC 消息混淆)。这些消息的大小应该小于 16KB,但即使如此,在某些环境中也会因为非常低的默认 UDS 消息大小和缓冲区大小限制而产生问题。

通常,限制可以使用 sysctl 进行调整,但确切的控制名称未记录,并且因操作系统而异。以下是一个已知的变体,其中包含建议的最小设置(如果您知道,请添加更多!):

net.local.dgram.recvspace: 262144
net.local.dgram.maxdgram: 16384

类别:功能

导航:站点搜索站点页面类别🔼 向上