跳转至

txid: 为 nginx-module-lua/nginx 生成可排序的唯一事务或请求 ID

安装

如果您尚未设置 RPM 仓库订阅,请 注册。然后您可以继续以下步骤。

CentOS/RHEL 7 或 Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-txid

CentOS/RHEL 8+、Fedora Linux、Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-txid

要在 NGINX 中使用此 Lua 库,请确保已安装 nginx-module-lua

本文档描述了 lua-resty-txid v1.0.0,于 2018 年 4 月 1 日发布。


CircleCI

lua-resty-txid 提供了一个函数,可以用于为 OpenResty/nginx 生成唯一的事务/请求 ID。这些 ID 可用于关联日志或上游请求,并具有以下特征:

  • 20 个字符
  • base32hex 编码
  • 时间上和字典上可排序
  • 不区分大小写
  • 96 位标识符

lua-resty-txid 是 OpenResty(或带有 ngx_lua 的 nginx)中 ngx_txid 的 LuaJIT 移植。lua-resty-txid 生成的 ID 遵循完全相同的模式,并与 ngx_txid 兼容。

用法

该模块暴露了一个单一的 txid() Lua 函数来生成 ID:

local txid = require "resty.txid"
local id = txid() -- b2g6q94qdn6h84an7vfg

每次调用 txid() 时,将返回一个新的唯一 ID,因此如果希望在单个请求的多个地方重用相同的 ID,您需要缓存结果。根据您的使用情况,ngx.ctxset_by_lua 提供了一些简单的选项,以便在每个请求的基础上缓存该值。

txid() -- b2g83t2oshrg092mjggg
txid() -- b2g83t2oodncokuges00

ngx.ctx.txid = txid() -- b2g83t2od939mdvb2l0g
ngx.ctx.txid          -- b2g83t2od939mdvb2l0g

最后,txid() 接受一个可选参数,用于在生成 ID 时使用哪个时间戳(以毫秒为单位)。默认情况下,使用当前时间戳。由于生成的 ID 在时间上和字典上都是可排序的,因此可以用来生成基于之前日期或时间的 ID。

local timestamp_ms = 655829050000 -- 1990-10-13 14:44:10
txid(timestamp_ms) -- 4om9qi54la8ffr4bd9sg

local timestamp_ms = 655929050000 -- 1990-10-14 12:30:50
txid(timestamp_ms) -- 4on1lg74nt0ud2ssllu0

示例

一个更完整的示例,包括缓存、设置请求/响应头以及与 nginx 日志的集成:

http {
  log_format agent "$lua_txid $http_user_agent";
  log_format addr "$lua_txid $remote_addr";

  init_by_lua_block {
    # 预加载模块。
    require "resty.txid"
  }

  server {
    listen 8080;
    access_log logs/agents.log agent;
    access_log logs/addrs.log addr;

    # 设置一个 nginx 变量,该变量在每个请求中缓存并可用于 nginx 的 log_format。
    set_by_lua_block $lua_txid {
      local txid = require "resty.txid"
      return txid()
    }

    location / {
      # 在响应中设置一个头部提供 ID。
      more_set_headers "X-Request-Id: $lua_txid";

      # 在请求中设置一个头部提供 ID(将发送到代理的上游)。
      more_set_input_headers "X-Request-Id: $lua_txid";

      proxy_pass http://localhost:8081;
    }
  }
}

性能

基准测试表明性能与 ngx_txid C 扩展相当。

设计

事务 ID 的设计是 ngx_txid 的直接移植,因此以下是关于 ngx_txid 设计的所有原始信息:

背景

此事务 ID 的设计应满足以下要求:

  • 在大约秒粒度下,数字上大致可时间排序。
  • 具有大致在秒粒度上可字典排序的表示。
  • 在每秒 100 万个事务的情况下,碰撞概率小于 1e-9。
  • 高效且易于解码为固定大小的 C 类型。
  • 始终可用,尽管碰撞概率可能更高。
  • 尽可能使用较少的字节。
  • 与 IPv4 和 IPv6 网络兼容。

技术

在高 42 位中使用单调毫秒分辨率时钟,在低 54 位中使用系统熵。使用足够的熵位以满足所需的全局请求速率下的碰撞概率。

+------------- 64 bits------------+--- 32 bits ----+
+------ 42 bits ------+--22 bits--|----------------+
| msec since 1970-1-1 | random    | random         |
+---------------------+-----------+----------------+

在所有服务器上每秒 100 万个请求意味着每毫秒 1000 个随机值。使用 生日悖论 估算碰撞概率可以使用以下公式:1 - e^(-((m^2)/(2*n))),其中 m 是 ID 的数量,n 是可能的随机值数量。

使用 54 位熵时:

1mil req/s  = 1 - exp(-((1000^2) /(2*2^54))) = 2.775558e-11
10mil req/s = 1 - exp(-((10000^2)/(2*2^54))) = 2.775558e-09

即使在每秒 1000 万个请求的情况下,碰撞的几率也很小。

Nginx 通过配置指令 timer_resolution 以增量方式跟踪当前时钟。$txid 的时钟分辨率为 1 毫秒,因此大于 1 毫秒的定时器分辨率意味着碰撞概率将增加。如果您的 timer_resolution 为 10 毫秒,则每秒 100 万个请求在最坏情况下将需要每秒 10,000 个随机值。

编码

选择使用 base32hex 进行编码,采用小写字母且不带填充字符,原因如下:

  • 字典排序顺序与数字排序顺序相同
  • 不区分大小写的相等性
  • 小写更易于视觉比较
  • 比十六进制编码密度高 4 字节

其他技术

  • snowflake:使用时间(41) + 唯一 ID(10) + 序列(12)。
  • 优点:保证唯一序列
  • 优点:适合 63 位
  • 缺点:每个服务器需要唯一 ID 协调 - 每个主机 16 个工作进程意味着 nginx 实例的限制为 64
  • 缺点:唯一 ID 仅有 11 位可用,需要监控
  • 缺点:仅在同一进程中可能实现总排序
  • 缺点:当时钟失去同步时可能会发生服务中断

  • flake:使用时间 + MAC ID + 序列。

  • 优点:保证唯一序列
  • 缺点:使用 128 位
  • 缺点:浪费 22 位时间戳数据
  • 缺点:每个主机只能有一个进程生成 ID - 需要同步访问每个工作进程的序列
  • 缺点:当时钟失去同步时可能会发生服务中断
  • 缺点:种子跨平台 MAC 地址查找。

  • UUIDv4:122 位熵

  • 优点:碰撞概率非常低
  • 缺点:不可排序

  • 带时间戳的 UUID:48 位时间 + 74 位熵

  • 优点:碰撞概率非常低
  • 缺点:字符串表示不是时间局部的

  • httpd mod_unique_id:主机 IP(32) + PID(32) + 时间(32) + 序列(16) + 线程 ID(32)

  • 优点:确定性
  • 缺点:使用 144 位
  • 缺点:假设主机名接口的唯一 IPv4
  • 缺点:不可排序的区分大小写自定义表示 - 使用自定义字母表的 base64
  • 缺点:每个 PID 每秒最多 65535 个 ID 的硬限制 - 时钟步进的容忍度小

开发

在检出仓库后,可以使用 Docker 运行测试套件:

docker-compose run --rm app make test

发布流程

要将版本发布到 OPM 和 LuaRocks:

VERSION=x.x.x make release

GitHub

您可以在 nginx-module-txid 的 GitHub 仓库 中找到此模块的其他配置提示和文档。