描述: | 为每个请求提供一个具有唯一标识符的环境变量 |
---|---|
状态: | 延期 |
模块标识符: | unique_id_module |
源文件: | mod_unique_id.c |
该模块为每个请求提供一个魔术令牌,该令牌在非常特定的条件下保证在“所有”请求中都是唯一的。唯一标识符甚至在正确配置的机器群集中的多台机器之间也是唯一的。环境变量UNIQUE_ID
设置为每个请求的标识符。出于各种原因,唯一标识符很有用,这些原因超出了本文档的范围。
首先简要回顾一下Apache服务器在Unix计算机上的工作方式。Windows NT当前不支持此功能。在Unix机器上,Apache创建多个子进程,这些子进程一次请求一个子进程。每个孩子在其一生中可以满足多个请求。出于讨论的目的,孩子们彼此之间没有共享任何数据。我们将这些子项称为httpd进程。
您的网站上有一台或多台由您管理的计算机,我们将它们统称为计算机集群。每台机器都可以运行Apache的多个实例。所有这些统称为“ Universe”,在某些假设下,我们将证明在此Universe中,我们可以为每个请求生成唯一的标识符,而无需在集群中的机器之间进行广泛的通信。
群集中的计算机应满足这些要求。(即使只有一台计算机,也应将其时钟与NTP同步。)
就操作系统假设而言,我们假设pid(进程ID)适合32位。如果操作系统为pid使用的位数超过32位,则此修复很简单,但必须在代码中执行。
根据这些假设,我们可以在单个时间点从所有其他httpd进程中识别集群中任何计算机上的任何httpd进程。机器的IP地址和httpd进程的pid足以完成此操作。如果您使用多线程MPM,则httpd进程可以同时处理多个请求。为了识别线程,我们使用Apache httpd内部使用的线程索引。因此,为了生成请求的唯一标识符,我们只需要区分不同的时间点即可。
为了区分时间,我们将使用Unix时间戳(自UTC 1970年1月1日以来的秒数)和一个16位计数器。时间戳仅具有一秒的粒度,因此该计数器用于在一秒钟内表示最多65536个值。四倍(ip_addr,pid,time_stamp,counter)足以枚举每个httpd进程每秒65536个请求。但是随着时间的推移,pid重用会出现问题,并且使用计数器来缓解此问题。
创建httpd子级时,将使用(当前微秒除以10)以65536为模数初始化计数器(选择此公式是为了消除某些系统上微秒计时器的低序位引起的一些方差问题)。生成唯一标识符时,使用的时间戳是请求到达Web服务器的时间。每次生成标识符(并允许翻转)时,计数器都会递增。
内核在派生该进程时会为每个进程生成一个pid,并且允许pid进行翻转(在许多Unix上,它们为16位,但较新的系统已扩展为32位)。因此,随着时间的流逝,相同的pid将被重用。但是,除非它在同一秒内被重用,否则它不会破坏我们四元组的唯一性。也就是说,我们假设系统不会在一秒钟的间隔内产生65536个进程(在某些Unix上它甚至可能是32768个进程,但是这种情况不太可能发生)。
假设时间由于某种原因而重演。就是说,假设系统的时钟搞砸了,并且它会重新查看过去的时间(或者它太远了,正确地重置了,然后重新考虑了将来的时间)。在这种情况下,我们可以轻松地表明我们可以获得pid和时间戳重用。选择计数器的初始化程序旨在帮助克服这一问题。请注意,我们确实希望使用随机数来初始化计数器,但是在大多数系统上都没有任何可用的数字(即,您不能使用rand(),因为您需要对生成器进行种子设置,也无法对其进行种子设置时间,因为时间(至少以一秒的分辨率)已经重复了一次)。这不是一个完美的防御。
防御有多好?假设您的一台计算机每秒最多可处理500个请求(在撰写本文时,这是一个非常合理的上限,因为系统通常不只是清除静态文件而已)。为此,将需要多个子代,具体取决于您有多少个并发客户端。但是我们会感到悲观,并假设一个孩子每秒能够处理500个请求。有1000个可能的起始计数器值,以使500个请求的两个序列重叠。因此,如果时间(以一秒钟的分辨率)重复一次,则此孩子有1.5%的机会重复该计数器值,并且唯一性将被破坏。这是一个非常悲观的例子,而按照现实世界的价值,它发生的可能性甚至更低。
您可能会担心在夏令时期间时钟会“倒退”。但这不是问题,因为这里使用的时间是UTC,“总是”向前发展。请注意,基于x86的Unix可能需要适当的配置才能实现-应当将它们配置为假定主板时钟为UTC并进行适当补偿。但是即使如此,如果您正在运行NTP,那么重新启动后不久,您的UTC时间也将正确。
在UNIQUE_ID
环境变量是通过使用字母编码144位(32位的IP地址,32比特的PID,32位的时间标记,16位计数器,32位的线程索引)四重构建[A-Za-z0-9@-]
以类似于MIME base64编码的方式,生产24个字符。该MIME BASE64字母表其实[A-Za-z0-9+/]
不过
+
和/
需要在URL中进行特殊编码,这使得它们不太受欢迎。所有值均以网络字节顺序编码,因此该编码在不同字节顺序的体系结构之间具有可比性。编码的实际顺序为:时间戳,IP地址,PID,计数器。这种排序是有目的的,但是应该强调的是,应用程序不应剖析编码。应用程序应将整个编码的代码
UNIQUE_ID
视为不透明令牌,可以将它们与其他UNIQUE_ID
s进行比较,以确保相等。
选择顺序以使将来可以更改编码而不必担心与现有UNIQUE_ID
s 数据库冲突。新编码还应将时间戳记作为第一个元素,否则可以使用相同的字母和位长。由于时间戳从本质上来说是一个递增的序列,因此具有一个秒标记就足够了,群集中的所有计算机都停止服务任何请求,并停止使用旧的编码格式。之后,他们可以恢复请求并开始发布新的编码。
我们认为这是解决此问题的相对便携式的解决方案。生成的标识符本质上具有无限的使用寿命,因为可以根据需要使将来的标识符更长。基本上,集群中的机器之间不需要通信(仅需要NTP同步,这是较低的开销),并且httpd进程之间也不需要通信(通信隐含在内核分配的pid值中)。在非常特殊的情况下,可以缩短标识符的长度,但是需要假定更多的信息(例如,对于任何站点,32位IP地址都是过大的,但是没有可移植的,更短的替换项)。