进阶 docs.memcached.org 2026-10-13 18:51:48 · 19 阅读

第18章 Memcached 代理 API 参考

第18章:Proxy API 参考

## 内置代理 ### Proxy API 参考

本页提供 Memcached 内置代理 API 的说明和参考。该 API 允许在高级使用场景中自定义或替换代理的标准路由库。

如果您希望修改标准路由库、使用自定义路由处理器对其进行扩展,或者从头编写完全自定义的库,请参考本文档。

有关内置代理的总体概述,请参阅[内置代理]。

我们正在追赶文档积压。如果您发现内容缺失或存在错误,请报告。

代理 API 开发状态 API 函数大多已稳定,但仍偶尔会有变动。功能上的大多数更改要么是新增功能,要么遵循向后兼容的弃用周期。

特性

- 支持大多数文本和 meta 协议 - 动态配置的后端池和路由处理 - 可插拔的键分布哈希算法 - 减少对后端服务器的连接数 - 能够有选择地覆盖命令,或提供从代理中嵌入的 memcached 返回的数据 - 通过简单的 Lua 设计精确符合您需求的路由 - 使用 TLS 连接到 memcached 后端 - 灵活的拓扑结构:可作为 sidecar 客户端、大型边界代理运行,或直接运行在现有服务器池上 - 快速:性能关键代码仍为 C。仅执行最少的 Lua 来操作和决定如何路由请求。

配置 API

为了加载配置,专用线程首先编译 Lua 代码。然后调用 `mcp_config_pools` 函数,该函数加载所有后端,将它们收集到池中,并返回一个持有所有最终池对象的 Lua 表。接下来,每个工作线程调用 `mcp_config_routes`。该函数负责设置路由处理器,并设置 memcached 将调用的命令钩子(例如:get、set 等钩子)。

``` --- title: 加载配置 --- flowchart TD subgraph configure A1[加载/编译 Lua 文件] -->B1 B1["执行 mcp_config_pools()"] -->C1 C1[返回 pools/config] end subgraph worker A2["执行 mcp_config_routes(config)"] -->B2 B2[创建路由处理器] -->C2 C2["运行 mcp.attach(CMD, handler)"] end configure --> |每个工作线程:复制 lua 代码和 config 表|worker ```

配置阶段

如上所述,代理配置发生在两个独立的阶段。这些阶段使用完全独立的 Lua VM 实例:一个配置实例,以及每个工作线程各一个。这种分离旨在提高重新加载配置时的安全性,并提升性能,因为使用单独的“配置线程”来执行更繁重的工作。

如果重新加载时配置执行失败,在大多数情况下,代理将继续运行旧配置。

所有线程使用相同的 Lua 代码文件:它只被加载和编译一次。

第一阶段是从“配置线程”调用 `mcp_config_pools`。此阶段定义后端并将它们组织成池。这也是决定如何路由键和命令的好位置。该函数随后返回一个包含信息表树的表。在该函数运行期间,代理不会被阻塞或受到其他影响。

第二阶段是从每个“工作线程”调用 `mcp_config_routes`。出于性能原因,这些 VM 独立运行:没有涉及锁,且任何垃圾回收都是按线程而非整个进程进行的。

在第二阶段,我们利用第一阶段生成的信息创建函数生成器对象,这些生成器产生实际处理用户请求的协程。我们还生成决定哪个函数应处理特定命令或键前缀的路由器对象。最后,我们将函数生成器或路由器对象附加到代理的命令钩子。

由于第二阶段在与处理请求相同的工作线程中执行,最好保持代码简洁。此处的错误也会停止进程,且难以恢复。

```lua function mcp_config_pools() -- 创建一个包含池和路由信息的表 local t = {} -- 用池/信息填充 t return t end

function mcp_config_routes(t) -- 创建函数生成器和路由器。 local router = -- 见下文 -- 在这里我们覆盖任何命令。也可以只覆盖 -- 特定命令:例如:只覆盖 'set',但处理 'get' 时就像 -- 我们不是代理而是普通存储服务器一样。 mcp.attach(mcp.CMD_ANY_STORAGE, router) end ```

我们的 Route 库可以为您处理这些,是一个很好的起点抽象。对于大多数人来说,剩余的 API 参考可用于创建自己的路由处理器。由于 routelib 处理执行阶段,您可以专注于处理请求,而不必关心配置是如何构建的。

使用新处理器扩展 routelib

有关扩展 routelib 的示例,可以在[此处]找到,查找以 custom 开头的文件。这些文件包含注释。更详细的信息在此重复。

加载自定义处理器

必须指示 memcached 加载您的自定义路由处理器文件。如果您已经在使用 routelib,您的启动行可能如下所示:

`-o proxy_config=routelib.lua,proxy_arg=config.lua`

如果您的自定义路由处理器定义在 `customhandler.lua` 中,您需要将其作为参数添加到 `proxy_config`:

`-o proxy_config=routelib.lua:customhandler.lua,proxy_arg=config.lua`

这允许代理为您管理配置的加载和重新加载,而无需过多关注 Lua。

自定义处理器配置阶段

如上所述在配置阶段中,配置加载是一个两阶段过程。在为 routelib 编写自定义处理器时也需要考虑这一点。

对于 routelib,我们在第一阶段调用 `_conf` 函数,在第二个工作线程阶段调用 `_start`。

此外,我们在代码加载时必须通过调用 `register_route_handlers` 将我们的处理器注册到 routelib。

例如:

```lua -- 此函数在第一阶段,在配置 VM 中执行。 -- 这是我们可以检查和转换从 routelib 配置传入的参数的地方。 function route_myhello_conf(t, ctx) -- 用户提供的参数在 `t` 表中。 -- 还提供了一个 `ctx` 上下文对象,可以查询 -- 有关处理器的信息、统计计数器等帮助。 t.msg = string.format("%s: %s", t.msg, ctx:label()) -- 获取内部 ID,以后用作全局计数器,统计 -- 我们的 hello 函数被调用的次数。 if t.stats then ctx:stats_get_id("myhello") end

-- 此返回表必须是纯 lua 对象。它在 -- 配置 VM 和每个工作 VM 之间复制,因此特殊对象、 -- 元表等无法传输。 return t end ```

接下来,我们定义 `_start` 例程,它创建一个函数生成器。此生成器函数用于创建处理请求的协程。

当代理处理请求时,它首先需要获取一个请求上下文槽。这提供了一个处理请求的 lua 函数。请求完成后,槽可以重复用于另一个请求。这意味着我们需要与并行请求数量一样多的槽。如果工作线程正在并行处理三个 get 请求,它将需要创建三个上下文来执行它们。

预创建和缓存请求槽让我们在使用脚本语言处理请求的同时获得良好的性能。

```lua -- 在这里我们准备稍后生成每个请求槽的生成器, -- 槽最终将处理请求。 -- 这种间接引用允许我们与每个槽共享预处理的信息, -- 进一步提高性能。它还允许所有槽共享对池和后端的内部引用。

-- 此函数在第二阶段,在每个工作线程上执行。 -- `t` 参数是由 _conf 函数返回的表。 -- 生成此处理器所需的所有信息 _必须_ 包含在此 -- 表中。在此阶段访问全局变量是不好的形式。 function route_myhello_start(t, ctx, fgen) -- 不为该函数添加任何子项。

-- 我们将参数表直接传递给函数生成器, -- 该表然后在创建槽时再次传递。 -- `n` 参数为此函数创建名称,可通过运行 -- `stats proxyfuncs` 查看 -- `f` 是当我们需要新槽时要调用的函数。 fgen:ready({ a = t, n = ctx:label(), f = route_myhello_f }) end

-- 这是每次我们需要新槽时调用的函数。 function route_myhello_f(rctx, a) -- 对消息字符串进行一些最终格式化。 -- 注意,我们在 `_start` 函数中执行此操作没有理由, -- 但我们在这里这样做是为了一个简单的示例。 local msg = string.format("SERVER_ERROR %s\r\n", a.msg)

local stats_id = a.stats_id local s = mcp.stat -- 小的 lua 优化,缓存 stat 函数

-- 这是处理实际请求时调用的函数。 -- 它将被缓存并在下一个请求时再次调用。 -- 因此,此 func 中的上述代码不会重复执行。 return function(r) if stats_id then s(stats_id, 1) end return msg end end

-- 最后,我们注册此处理器,以便 routelib 允许我们在 -- 配置文件中使用它。 register_route_handlers({ "myhello", }) ```

使用更高级 lua 语法格式化 `_start` 函数的另一个示例:

```lua function route_myhello_start(t, ctx, fgen) local msg = string.format("SERVER_ERROR %s\r\n", a.msg)

fgen:ready({ n = ctx:label(), f = function(rctx, a) -- 以前这是 `route_myhello_f` -- 区别在于我们只为每个稍后创建的槽生成一次 msg, -- 且不使用传递的参数表 local stats_id = a.stats_id local s = mcp.stat

return function(r) if stats_id then s(stats_id, 1) end return msg end end}) end ```

带子项的自定义处理器

路由到池(或其他处理器)的处理器必须定义子项。Routelib 会神奇地将两个配置阶段之间子项的名称转换为池对象。这简化了否则我们需要做的许多对象管理。

最简单的例子是 `route_direct` 处理器。给定一个 routelib 配置,如下所示:

```lua pools{ main = { backends = { "127.0.0.1:11211" } } }

routes{ map = { main = route_direct{ child = "main" } }, } ```

如上所示,`route_direct` 处理器被赋予单个 child 参数。

```lua function route_direct_conf(t) -- 此表包含:t.child = "main",但我们不需要做任何 -- 处理,可以简单传递它。 -- Routelib 寻找任何以 `child` 开头的键,并尝试 -- 将它们从单词转换为我们内部的池对象。 return t end

local function route_direct_f(rctx, handle) -- 生成此槽时没有其他可做的。 return function(r) -- 处理实际请求时我们唯一做的就是将其发送到 -- 我们之前创建的 handle。 return rctx:enqueue_and_wait(r, handle) end end

function route_direct_start(t, ctx, fgen) -- t.child 已被转换为我们在此可以使用的东西。 -- 此时我们将其引用为我们稍后可以路由到的 handle。 -- Handles 必须生成一次,并为每个生成的槽重用。 local handle = fgen:new_handle(t.child) fgen:ready({ a = handle, n = ctx:label(), f = route_direct_f }) end ```

除了 child 之外,最常见的参数是 children,它将是表。此表将包含池的数组或 name = pool 的表,常用于分区分区路由处理器(参见 `route_zfailover` 作为示例)。

所有这些间接引用(child -> 池对象 -> handle)是代理管理后端连接生命周期的方式,这在实时重新加载配置时很重要。现有的在途请求不受配置更改的影响,我们尽快释放资源。

请求上下文 API 和执行请求

函数生成器 API

```lua -- 创建一个新工厂对象。将此对象作为函数参数 -- 传递给 mcp.attach() 或 rctx:new_handle。 fgen = mcp.funcgen_new()

-- 引用池或 funcgen 到此 funcgen 并返回一个 handle。此 -- handle 稍后在执行请求处理期间用于排队请求。 handle = fgen:new_handle(pool||funcgen)

-- 此标记 funcgen 生成器准备就绪,并传入更多 -- 参数。 fgen:ready({ -- 这是我们将使用的函数工厂。详见下文 f = route_handler, -- 传递给 route_handler 的任意参数 (bool/string/num/table) a = argument, -- 此函数生成器的名称,用于 'stats proxyfuncs' 输出 n = string, })

-- 'route_handler' 在每个生成的请求槽中 _一次_ 调用。如果 -- 有很多并行在途请求,这可能被调用多次。 -- 输出函数被缓存并重用,直到配置 -- 重新加载。 -- 'route_handler' 的原型为: function route_handler(rctx, arg) -- 'rctx' 是刚创建的新请求上下文对象。 -- 'arg' 是通过 fgen:ready() 在上面 'a' 中传入的参数 -- 这必须返回一个新函数,具有以下原型。它 -- 随后被反复调用来服务单个请求。 return function(r) -- 代码 end end ```

请求工厂 API。这些 API 调用可以从最终提供服务的函数生成中可用的函数中可用。

```lua -- 当此 handle 被执行时执行此函数回调。 -- 这允许添加额外处理(统计计数器、日志记录)或覆盖 -- 等待条件 rctx:handle_set_cb(handle, func)

-- 回调看起来像: local cb = function(res, req) -- 可以检查和执行的结果和请求

-- 我们可以 _选择_ 返回一个条件,以防我们希望做出自己的 -- 关于 GOOD/OK/ANY 意味着什么的判断。 return mcp.WAIT_ANY

-- 如果我们想提前结束等待条件,也可以返回第二个参数 return mcp.WAIT_ANY, mcp.WAIT_RESUME end

rctx:handle_set_cb(handle, cb) ```

请求处理 API

```lua -- 从请求函数中调用,将请求排队到 -- 指定的槽 handle,或 N 个 handles 的数组样式表 rctx:enqueue(r, handle || table)

-- 等待指定的未排队 handle 后直接返回单个结果对象。 res = rctx:enqueue_and_wait(r, h)

-- 等待指定的已排队 handle 后直接返回单个结果对象。 res = rctx:wait_handle(h)

-- 异步等待当前所有 -- 排队 handles 中最多 "count" 个结果。采用模式以过滤要计数的有效响应: -- mcp.WAIT_OK, WAIT_GOOD, WAIT_ANY -- WAIT_FASTGOOD 等待第一个 "Good"(即;命中)请求或总共 N 个 -- 响应 -- 如果为 count 提供 "0",它将执行排队请求并 -- 立即恢复。 num_good = rctx:wait_cond(count, mode)

-- 如果队列响应被认为是 "Good",则返回结果对象,否则 -- nil。 rctx:res_good(handle) -- 相同但 "WAIT_ANY" rctx:res_any(handle) -- 相同但 "WAIT_OK" rctx:res_ok(handle) -- 返回结果对象,mcp.RES_GOOD|OK|ANY res, mode = rctx:result(handle) ```

请求如何分派到后端

请求通过各种 enqueue* 函数排队。 请求不会立即传输到后端。每当要求 rctx 等待时,即:对于 enqueue_and_wait 和 wait_cond,所有排队的请求都会立即执行。

这意味着如果你排队两个请求,然后排队并等待第三个,所有三个请求都会批量同时执行。

``` --- title: 一次性 enqueue 所有 --- flowchart TD A["rctx:enqueue(r, table)"] -->B["rctx:wait_cond(etc)"] B --> C{分派} C -->|请求| D[后端] C -->|请求| E[后端] C -->|请求| F[后端] ```

``` --- title: 分阶段 enqueue --- flowchart TD A["rctx:enqueue(r, handle1)"] -->B["rctx:enqueue_and_wait(r, handle2)"] B --> C{分派} C -->|请求| D[后端] C -->|请求| E[后端] ```

结果表在哪里?

此核心 API 旨在执行而不引起任何 Lua 分配,包括返回给用户的表。因此,我们的 wait 函数不能简单地返回结果表。

然而,使用此 API 时,你几乎总是拥有用于向其发起请求的所有后端的 handles。因此,常见的模式将是:

```lua -- 在生成阶段 local handles = {} for k, v in pairs(pools) do table.insert(handles, rctx:new_handle(v)) end

-- ... 稍后,在请求时 rctx:enqueue(r, handles) -- 批量向所有 handles enqueue 请求 rctx:wait_cond(#handles) -- 等待所有请求完成 -- 再次使用相同的表迭代结果 for x=1, #handles do local result = rctx:res_good(handles[x]) end

-- 在此示例中,我们多次引用我们的池列表而 -- 未进行任何分配。 ```

路由器对象

假设我们想根据请求中键的一部分将请求路由到不同的 memcached 实例池:为此我们使用路由器对象。

如果你使用 routelib,你从未需要直接创建路由器。Routelib 在其 `routes{}` 部分下暴露了此功能的大部分。

```lua -- 路由器的最小示例。这里: -- "get foo/etc" 将由 "funcgen_foo" 处理器处理 -- "get bar/etc" 将由 "funcgen_bar" 处理器处理 -- 默认情况下,路由器检查到 "/" 字符为止的 map key。 local m = { foo = funcgen_foo, bar = funcgen_bar, } local router = mcp.router_new({ map = m, mode = "prefix", stop == "/" }) mcp.attach(mcp.CMD_GET, router) ```

路由器选项说明:

```lua local r = mcp.router_new({ -- 路由请求到路由处理器的表 -- 更多请参阅下方的 "command maps"。 map = m, -- mode 可以是(默认 "prefix"): -- "prefix": 我们将键的前缀与 map 进行检查。 -- 看到 "stop" 字符时停止匹配。 -- "anchor": 我们检查并跳过 "start" 中的字符,然后匹配直到 -- 看到 "stop"。 mode = "etc", -- start 在键的开头寻找这些字符并在找到子 -- 字符串匹配之前跳过它们。默认为 "" -- 如果 start 是单个字符,则使用优化算法。 -- 它必须是 5 个字符或更少。 start = "_", -- stop 在此字符处停止匹配,并在此 -- 字符串之前检查 map。默认为 "/"。 -- 它遵循与 "start" 相同的规则:单个字符更快,最大 -- 是 5。 stop = "/" -- 如果请求不匹配 map,使用此路由处理器 -- 作为默认。 default = funcgen })

-- command maps

-- 路由器 map 条目可以直接引用 funcgen 处理器,或 -- 另一个将命令映射到 funcgen 处理器的表 local m = { -- 任何 "foo/etc" key 的 get/set/touch 都将路由到这里 foo = handler1, bar = { -- 只有 "get bar/etc" 将使用 handler2。 [mcp.CMD_GET] = handler2, -- 如果也提供了 CMD_ANY_STORAGE 条目,若没有精确匹配则使用它 [mcp.CMD_ANY_STORAGE] = handler3, -- 如果没有提供 CMD_ANY_STORAGE,且没有精确的 CMD 匹配,则 -- 使用路由器的默认条目。 } } local r = mcp.router_new({ map = m }) mcp.attach(mcp.CMD_ANY_STORAGE, r) ```

后端对象

什么是 Backend

Backend 对象描述单个 memcached 服务器,代理希望与之通信。Backend 对象的集合被组装成 Pools。

Backends 不能直接在路由处理器中使用。它们必须首先包装在池中。

为什么 backends 与 pools 分离?

我们允许同一个 backend 对象在多个 pool 对象中被引用。这在诸如随时间调整池大小的场景中可能很有用。这通常要求临时创建两个池:如果扩大,则一个是带有额外后端的,另一个是带有原始集合的。由于我们可以在这两者之间共享 backend 对象,我们避免了创建过多的 TCP 连接。

这也允许我们在基于对象的基础上自定义某些选项。虽然此功能是否向用户暴露将取决于他们的路由库。

后端对象缓存

Backends 按其标签建立索引。当您尝试创建 backend 时,代理将检查它是否已有该标签的对象。如果有,它将检查提供的参数并在某些内容发生变化时替换对象(例如;ip 地址,端口,超时设置等)。

这为重新加载配置带来巨大的速度提升,并避免通过创建所有新对象使配置重新加载将 TCP 连接加倍。

由于配置代码不需要管理此缓存,它可以随意描述对象,代理将在它们更改时处理更新。

后端 API

允许从 `mcp_config_pools` 使用

短形式 backend 创建

```lua -- 从短描述创建 backend 对象 mcp.backend(label, host|ip, port)

-- label: 唯一标识此 backend 对象以便可以重用 -- host|ip: 是服务器的主机名或 IP 地址,虽然截至编写此文档时强烈建议使用 IP 地址,因为 DNS 查找可能导致 -- 性能问题。 -- port: 是服务器监听的服务端口。

-- backend 的 `label` 赋予其唯一的缓存 id。如果 backend 在重新加载时具有相同的标签和选项,底层连接将被重用。 ```

长形式 backend 创建。 这允许在基于 backend 的基础上覆盖一些全局设置。

```lua mcp.backend({ -- 唯一标识此 backend 对象以便可以重用 label = "string",

-- 服务器的主机名或 IP 地址,虽然截至编写此文档时强烈建议使用 IP 地址,因为 DNS 查找可能导致 host = "string",

-- 服务器监听的服务端口。 port = number,

-- 为此对象使用的 TCP 连接数。代理将 -- 尝试将请求分散到多个套接字上。如果您有 -- 很多大项目,这很有用。 -- 这会为 _每个工作线程_ 创建 'count' 个连接,所以请小心。 -- 不要设置为大数字!1 是默认值,2 可能就够了。 connections = count,

-- 如果为 true,任何访问此后端的尝试都会立即失败 (`SERVER_ERROR backend failure`) down = true|false,

-- 如果为 true,且 backend TLS 支持已通过 -- `--enable-proxy-tls` 编译在内,我们将通过 TLS 连接到此后端。 tls = true|false,

-- 以下选项覆盖全局设置,详见下文。 -- 秒可以是小数,例如:0.5 表示 500ms 或 2.75 表示 2750ms -- mcp.tcp_keepalive tcpkeepalive = true|false, -- mcp.backend_failure_limit failurelimit = count, -- mcp.backend_connect_timeout connecttimeout = seconds, -- mcp.backend_retry_waittime retrywaittime = seconds, -- mcp.backend_read_timeout readtimeout = seconds, -- mcp.backend_flap_time flaptime = seconds, -- mcp.backend_backoff_flap_ramp flapbackofframp = seconds, -- mcp.backend_backoff_flap_max flapbackoffmax = seconds, }) ```

池对象

什么是池

池对象通常是持有后端对象引用的非常轻量级容器。它们还持有如何从提供的键中选择后端的配置。

通常,你会期望“路由处理器”接收后端列表并能够自己执行键映射,这更灵活。对我们来说,我们需要仔细管理性能并希望最小化执行的 Lua 量。因此,池对象完全用 C 实现。

池的默认键分布方法是使用 XXHash 对键进行哈希,并使用 Jumphash 算法将键分布到后端列表中。这是一种非常快且分布非常均匀的方法。如果你使用 ketama 或类似的哈希,强烈建议尝试转换为此新方法。

如果需要其他方法用于兼容性或其他目的,哈希 algo 和键分布 algo 都可以用 C 覆盖。

池 API

允许从 `mcp_config_pools` 函数使用:

```lua mcp.pool({backend1, backend2, etc}, { -- 如果为 true,此池中列出的后端使用共享线程进行 IO 访问,减少 TCP 连接。如果为 false,每个 memcached 工作 -- 线程为此池维护自己的后端 TCP 套接字。 -- [TODO: 添加详细信息锚点] iothread = true|false,

-- 如果提供,此前缀将添加到所有指定后端的标签中。这意味着此池对象将为 -- 其后端获得唯一的 TCP 套接字。可用于为不同目的(gets vs sets)分配不同的套接字。 beprefix = string,

-- 用于“种子”此池哈希算法的字符串。如果两个池 -- 使用相同的后端创建,但种子不同,它们将 -- 将键分布到后端的处理方式不同。 seed = string,

-- 参见 `filter_conf`。这允许将相关的键集 -- 定向到相同后端节点,改进批量性能。 -- 如果 "stop":在配置的 stop 文本处停止,仅哈希键的第一部分。例如,像 '|#|' 这样的三字符字符串 -- 如果 "tags":仅哈希键中两个给定字符之间的部分。例如,"{}" 或 "$$" filter = string,

-- 请求的 filter 类型的配置。见上方示例。 filter_conf = string,

-- 允许覆盖哈希或分布算法。 -- TODO: 文档。 hash = object, dist = object, }) ```

编程注意事项

全局值和配置重新加载

创建路由处理器时,我们力求将他们使用的所有上下文/数据放在函数环境中。当配置重新加载发生时,所有代码都可能改变。现有请求可能正在等待后端响应,我们不希望他们的代码在他们脚下改变。

只要所有数据都在函数环境中,重新加载就不能破坏在途请求。然而,如果你依赖全局变量,且这些全局变量可能在重新加载期间改变,你可能会陷入困境。

示例:

```lua function generator(rctx, arg) -- 好:从传入参数中获取 toggle local toggle = arg.toggle

-- 可以:在函数生成期间从全局变量中获取 toggle local toggle = GLOBAL_TOGGLE_BOOLEAN

local handle = etc

return function(r) if toggle then -- 一些特定的准备工作 else -- 不同的准备工作 end

local res = rctx:enqueue_and_wait(r, handle)

if toggle then -- 如果在第一个 toggle 检查和这个之间发生了重新加载, -- 且我们直接引用了 -- GLOBAL_TOGGLE_BOOLEAN 而不是 'toggle',我们可能会犯错误。 else -- 同上 end end end ```

对于查找映射表之类的事情也是如此。在 lua 中,表和对象按引用传递。这意味着如果我们在加载时生成查找表并将其传递给函数,使用它是完全安全的。

示例:

```lua function mcp_config_routes(c) -- 跳过:各种准备工作。

-- 这创建了一个新的顶级查找表。 local lookup = { one = 1, two = 2, three = 3 } -- 然后我们将其传递给函数生成器 fgen:ready({ f = generator, a = lookup }) -- 下一次重新加载发生时,无论 `lookup` 如何改变都无所谓, -- 因为由于我们在 mcp_config_routes() 内创建 lookup,它在每次加载时都是唯一的 end ```

另一个示例:

```lua -- lookup 现在是全局变量 lookup = { one = 1, two = 2, three = 3 }

function mcp_config_routes(c) -- 跳过:各种准备工作。

-- 这仍然没问题,因为 'lookup' 在重新加载期间被新表覆盖, -- 且我们在这里传递的是该表的引用。 fgen:ready({ f = generator, a = lookup }) end ```

不好的示例:

```lua -- lookup 是全局表 lookup = { one = 1, two = 2, three = 3 }

function mcp_config_routes(c) -- 没有传入引用。 fgen:ready({ f = generator }) end

function generator(rctx) return function(r) -- 可能会破坏,因为我们现在直接引用全局 -- 表,这可能会改变。很多次这无关紧要,但最佳 -- 实践是始终在需要时向下传递引用。 local foo = lookup[input] end end ```

通用 API 文档

允许从 `mcp_config_pools` 函数使用:

`mcp.add_stat(number, label)`: 创建快速自定义计数器,可通过 stats proxy 命令查看。对 number 参数使用唯一的常数,对 label 使用描述性文本。

设置:

```lua -- 在重试前等待连接的分数秒时间 mcp.backend_connect_timeout(seconds)

-- 在后端达到失败限制并被标记为坏之后,尝试建立新连接前等待的整秒时间。 mcp.backend_retry_waittime(seconds)

-- 向 后端写入请求后等待读响应的分数秒时间。 mcp.backend_read_timeout(seconds)

-- 后端在连续多少次无法正常连接和验证后会被标记为坏。 mcp.backend_failure_limit(seconds)

-- 所有新后端是否使用 TCP Keepalive mcp.tcp_keepalive(bool)

-- 如果超过此数量正在积极处理的请求,则快速失败。 mcp.active_req_limit(count)

-- 如果请求或响应值缓冲区中正在积极使用的千字节数超过此大略数值,则快速失败。 mcp.buffer_memory_limit(kilobytes)

-- 后端必须保持打开而不发生错误的秒数,否则被认为 -- 是 flapping。 (TODO: docs) mcp.backend_flap_time(seconds)

-- 一个小数值(分数秒)。它乘以 `retry_waittime` -- 以其 flapping 的次数,提供对重试不健康服务器频率的回退。 mcp.backend_flap_backoff_ramp(seconds)

-- 重试 flapping 服务器前等待的最大整秒数。 -- 确保具有短暂问题的服务器在合理的时间内偶尔被带回。 mcp.backend_flap_backoff_max(seconds)

-- 后端是否默认由工作线程或专用 IO 线程处理。 -- 默认禁用,这以更多 -- TCP 连接和更少的后端系统调用批量处理为代价提供更好的可扩展性。 -- 这可以在池设置中覆盖。 mcp.backend_use_iothread(false) ```

允许从 `mcp_config_routes` 函数使用:

`mcp.attach(CMD, function)`: 将函数附加到命令钩子。详见 配置 API。

`mcp.request(request, value)`: 接收字符串请求并从中创建请求对象。这解析请求以确保它是有效协议。可选地,可以添加 value 字符串以提供 set 样式请求的值部分。value 也可以是响应对象,代理将在内部将响应值复制到新对象中,而不需要通过 lua 复制。

`mcp.log(message)`: 将文本消息发送到可通过 watch proxyuser 查看的日志流

`mcp.log_req(request, response, detail)`: 通过比较提供的请求和响应对象,在 watch proxyreqs 流中产生详细日志行。detail 是也传递给日志条目的字符串。

`mcp.log_reqsample(milliseconds, rate, allerrors, request, response, detail)`: 此函数允许有条件地记录详细请求日志,与 mcp.log_req 相同。request、response 和 detail 相同。milliseconds,如果非零,将记录所有获取结果耗时超过此限制的请求。rate,如果非零,将记录平均 “每 rate 个请求中一个” 的请求,允许随机采样。allerrors,如果为 true,如果在产生典型响应(例如;后端宕机,畸形请求/响应等)时产生了错误,将始终生成日志行。

`mcp.stat(number, change)`: 给定从 mcp.add_stat 创建的数字索引,将该整数 change 添加到该计数器中。change 可以是正数或负数。

对象方法: (目前缺少许多有文档的方法)

请求对象:

`request:key()`: 返回解析请求对象的键 `request:ltrimkey(number)`: 从键的左侧移除 number 个字符。 `request:rtrimkey(number)`: 从键的右侧移除 number 个字符。 `request:ntokens()`: 请求中参数的总数 `request:token(n, "token")`: 获取或替换此位置的 token。如果第二个参数传入空白 (""),则 token 被移除。这可用于,例如,查看或替换 SET 请求的 TTL 部分。 `request:vlen()`: 附加到此请求的值的字节长度 `request:has_flag('F')`: 用于 meta,测试请求字符串中是否存在标志的快速函数 `request:flag_token("F", "Freplacement")`: 用于 meta,查找和修改请求行的快速函数。返回 (exists, previous_token),一个布尔值表示标志是否存在,如果标志附加了 token,则将在 previous_token 中返回。如果传递第二个参数,它将用于替换请求中的标志和/或 token 参数。空白 ("") 值将完全移除标志。注意:如果你对同一 token 重复运行此命令,它将不会返回先前更新值。此行为未来可能会改变。

响应对象:

`resp:hit()`: 请求是否成功命中,如果从原始请求的角度有意义的话。 `resp:ok()`: 请求是否成功执行到后端。对于 GET 请求,HIT 和 MISS 都是 OK。 `resp:line()`: 用于 meta,返回完整的响应头行。 `resp:code()`: 响应码。常见码包括: `mcp.MCMC_CODE_OK` `mcp.MCMC_CODE_STORED` `mcp.MCMC_CODE_EXIST` `mcp.MCMC_CODE_MISS`

代理设置参考

评论 (0)