共计 8144 个字符,预计需要花费 21 分钟才能阅读完成。
这是由 Denis Brækhus 和 Espen Braastad 撰写的客座文章, 他们是来自Varnish Software 的 Varnish API Engine 的开发者。Varnish 长期用于认证后端,所以让我们来看看他们在做什么。
Varnish Software 刚刚放出 Varnish API Engine 的发布版,它是一个高性能的 HTTP API 网关,用于处理认证、授权和所有基于 Varnish Cache 之上的调节。Varnish API Engine 可以用一个统一访问控制层轻易地扩展你目前的 API 集。这个统一访问控制层内置了高容量读取操作缓存能力,而且它提供了实时度量。
Varnish API Engine 使用了众所周知的组件如 memcached、SQLite 和最重要的 Varnish Cache。管理 API 是由 Python 写成的。该产品的核心部分在 Varnish 的基础上使用 VCL (Varnish Configuration Language)编写成一个应用并使用 VMODs (Varnish Modules)提供扩展能力。
我们希望以这篇文章作为一个机会来向您展示怎样在 VMODs 的协助下使用 VCL 创建一个您自己的灵活且高性能的应用。
VMODs (Varnish 模块)
VCL 是用于配置 Varnish Cache 的语言。当 varnishd 加载 VCL 配置文件时,它将会把这个文件转换成 C 代码,将之编译并动态加载。因此可以通过在 VCL 配置文件中直接嵌入 C 代码来使 VCL 具有扩展功能,但是从 Varnish Cache 3 开始,已经变成使用 Varnish Modules,或简称 VMODs。
在 Varnish Cache 的一个堆栈中,典型的请求流程入下:
客户端发出的 HTTP 请求被 Varnish Cache 接收并处理。Varnish Cache 将会检查请求的内容是否在缓存中,最后它可能从后端读取内容。这工作的很好,但我们能做更多。
VCL 语言是为性能设计的,因此它本身并不提供循环或外部调用。VMODs,换句话说,是为了打破这些限制的。这对灵活性来说非常棒,但将确保性能和避免延迟的责任放到了 VMOD 代码和行为上了。
API Engine 用于说明用 VCL 和自定义的 VMOD 的组合来开发新应用是多么强大。在 Varnish API Engine 中,请求流程是:
每个请求匹配一个使用 SQLite VMOD 的规则集合和使用 memcached VMOD 的 Memcached 计数器组。如果任何一个不匹配,请求都将会被拒绝,例如:认证失败或超过了请求限制。
应用示例
下面的例子是 Varnish API Engine 中一些概念的非常简单版本。我们将创建一个用 VCL 写的小应用,它将在一个包含限流规则的数据库中搜索请求 URL,并在每一个 IP 的基础上执行。
由于开发应用程序时的测试和可维护性是至关重要的,我们将用 Varnish 的集成测试工具:varnishtest。Varnishtest 是一个测试 Varnish 缓存所用方面的强大工具。Varnishtest 的简单接口意味着开发者和操作工程师利用它来测试他们的 VCL/VMOD 配置。
Varnishtest 读取一个描述一组模拟服务器、客户端和 varnish 实例的文件。客户端执行通过 varnish 到达服务器的请求。Expectations 可以被设置为内容、标题、HTTP 响应代码,或者更多。使用 varnishtest,我们可以快速测试我们的示例应用,并验证我们的请求对每一个定义的 expectations 是通过还是阻塞。
首先,我们需要一个带着我们限流规则的数据库。使用 sqlite3 命令,我们在 /tmp/rules.db3 创建这个数据库并增加两三个规则。
$ sqlite3 /tmp/rules.db3 “CREATE TABLE t (rule text, path text);”
$ sqlite3 /tmp/rules.db3 “INSERT INTO t (rule, path) VALUES (‘3r5’, ‘/search’);”
$ sqlite3 /tmp/rules.db3 “INSERT INTO t (rule, path) VALUES (’15r3600′, ‘/login’);”
这些规则将允许每秒 3 个请求到 /secarch 和每小时 15 个请求到 /login。这个想法是在每个 IP 基础上执行这些规则。
为了简单起见,我们将在一个文件中编写测试和 VCL 配置,throttle.vtc。然而,在测试文件中使用包含语句包含单独的 VCL 配置,分离 VCL 配置和不同的测试也是可行的。
这个文件中的第一行是用来设置这个测试的标题或名字。
varnishtest “Simple throttling with SQLite and Memcached”
我们的测试环境由一个调用 s1 的后端构成。我们将首先 expect 一个请求到一个在数据库中没有规则的 URL。
server s1 {
rxreq
expect req.url == “/”
txresp
根据接下来的 expectations,到达后我们再 expect 4 个请求到 /search。注意,查询参数略有不同,来制作所有这些独特的请求。
rxreq
expect req.url == “/search?id=123&type=1”
expect req.http.path == “/search”
expect req.http.rule == “3r5”
expect req.http.requests == “3”
expect req.http.period == “5”
expect req.http.counter == “1”
txresp
rxreq
expect req.url == “/search?id=123&type=2”
expect req.http.path == “/search”
expect req.http.rule == “3r5”
expect req.http.requests == “3”
expect req.http.period == “5”
expect req.http.counter == “2”
txresp
rxreq
expect req.url == “/search?id=123&type=3”
expect req.http.path == “/search”
expect req.http.rule == “3r5”
expect req.http.requests == “3”
expect req.http.period == “5”
expect req.http.counter == “3”
txresp
rxreq
expect req.url == “/search?id=123&type=4”
expect req.http.path == “/search”
expect req.http.rule == “3r5”
expect req.http.requests == “3”
expect req.http.period == “5”
expect req.http.counter == “1”
txresp
} -start
现在是时候写一个 VCL 的迷你程序了。我们的测试环境由一个 varnish 的实例 v1 组成。首先,加入 vaernish 版本和 VMOD imports。
varnish v1 -vcl+backend {
vcl 4.0;
import std;
import sqlite3;
import memcached;
VOMD 通常在 vcl_init 中配置,sqlite3 和 memcacheed 也是这样。对于 sqlite3,我们设置数据库路径和用在多列结果中的分隔符。memcached VMOD 可以有各种各样 libmemcached 支持的配置选项。
sub vcl_init {
sqlite3.open(“/tmp/rules.db3”, “|;”);
memcached.servers(“–SERVER=localhost –BINARY-PROTOCOL”);
}
在 vcl_recv 中,传入的 HTTP 请求被接受。我们首先提取没有查询参数和潜在危险字符的请求路径。这非常重要,因为这个路径是稍后 SQL 请求的一部分。接下来的正则表达式将从一行的开始直到字符 (?&;”’) 或空格结束匹配 req.url。
sub vcl_recv {
set req.http.path = regsub(req.url, {“^([^?&;”‘]+).*”}, “\1”);
在正则表达式中使用 {”“} 可以支持正则表达式规则中“字符的处理。我们刚刚提取的路径仅在数据库中查找规则时使用。响应 (如果有的话) 存储在 req.hhtp.rule 中。
set req.http.rule = sqlite3.exec(“SELECT rule FROM t WHERE path='” + req.http.path + “‘ LIMIT 1”);
如果我们得到一个响应,它将会是 RnT 格式的,这里的 R 是 T 秒时间段内允许的请求量。由于这是一个字符串,我们需要应用额外的正则表达式来分割。
set req.http.requests = regsub(req.http.rule, “^([0-9]+)r.*$”, “\1”);
set req.http.period = regsub(req.http.rule, “^[0-9]+r([0-9]+)$”, “\1”);
只有当我们从前面的正则表达式过滤器中获得正确的值时才限制请求。
if (req.http.requests != “” && req.http.period != “”) {
给这个 client.ip 增加或创建一个独一无二的 Memcached 计数器。并将 path 值设置为 1。我们将失效时间指定为与数据库中限制规则设置的时间相同。在这种方式中,限制规则可以灵活地设置时间。返回值就是计数器的新值,与这个 client.ip 在当前路径和当前时间段内的请求数量相符。
set req.http.counter = memcached.incr_set(
req.http.path + “-” + client.ip, 1, 1, std.integer(req.http.period, 0));
检查计数器是否高于数据库中设置的限制。如果是,放弃当前的请求并返回一个 429 响应码。
if (std.integer(req.http.counter, 0) > std.integer(req.http.requests, 0)) {
return (synth(429, “Too many requests”));
}
}
}
在 vxl_deliver 中我们设置了显示限流限制的响应 headers 和有助于用户的每一个请求状态。
sub vcl_deliver {
if (req.http.requests && req.http.counter && req.http.period) {
set resp.http.X-RateLimit-Limit = req.http.requests;
set resp.http.X-RateLimit-Counter = req.http.counter;
set resp.http.X-RateLimit-Period = req.http.period;
}
}
在 vcl_synth 中,错误将获得一个同样的 headers 设置。
sub vcl_synth {
if (req.http.requests && req.http.counter && req.http.period) {
set resp.http.X-RateLimit-Limit = req.http.requests;
set resp.http.X-RateLimit-Counter = req.http.counter;
set resp.http.X-RateLimit-Period = req.http.period;
}
}
配置完成,是时候增加一些客户端来确认一下配置是否正确。首先,我们发送一个不被限流的请求,这意味着这个 URL 在数据库中没有限流规则。
client c1 {
txreq -url “/”
rxresp
expect resp.status == 200
expect resp.http.X-RateLimit-Limit == <undef>
expect resp.http.X-RateLimit-Counter == <undef>
expect resp.http.X-RateLimit-Period == <undef>
} -run
我们知道客户端发送的下一个请求的 URL 与限制数据库是匹配的,我们希望设置速率限制报头,对 /search 的限制规则是 3r5,也就是说在 5 秒的时间段内,前三个请求应该是成功的(返回状态码 200),当第四次请求时,就应该被限制了(返回状态码 429)。
client c2 {
txreq -url “/search?id=123&type=1”
rxresp
expect resp.status == 200
expect resp.http.X-RateLimit-Limit == “3”
expect resp.http.X-RateLimit-Counter == “1”
expect resp.http.X-RateLimit-Period == “5”
txreq -url “/search?id=123&type=2”
rxresp
expect resp.status == 200
expect resp.http.X-RateLimit-Limit == “3”
expect resp.http.X-RateLimit-Counter == “2”
expect resp.http.X-RateLimit-Period == “5”
txreq -url “/search?id=123&type=3”
rxresp
expect resp.status == 200
expect resp.http.X-RateLimit-Limit == “3”
expect resp.http.X-RateLimit-Counter == “3”
expect resp.http.X-RateLimit-Period == “5”
txreq -url “/search?id=123&type=4”
rxresp
expect resp.status == 429
expect resp.http.X-RateLimit-Limit == “3”
expect resp.http.X-RateLimit-Counter == “4”
expect resp.http.X-RateLimit-Period == “5”
} -run
在这一点,我们知道请求将会被限制,为了确定限制时间结束之后新的请求将会被允许,在我们发送下一个和最后一个请求之前,我们添加了一个延迟。这个请求应该是成功的,因为我们已经进入了一个新的限制窗口期。
delay 5;
client c3 {
txreq -url “/search?id=123&type=4”
rxresp
expect resp.status == 200
expect resp.http.X-RateLimit-Limit == “3”
expect resp.http.X-RateLimit-Counter == “1”
expect resp.http.X-RateLimit-Period == “5”
} -run
要运行测试文件,先要确保 memcached 服务正在运行,然后执行:
$ varnishtest example.vtc
# top TEST example.vtc passed (6.533)
添加 -v 选项启用详细模式,从运行测试中获得更多的信息。
对我们示例应用发送请求,将会接收到如下的响应头。第一个表示请求被接受,第二个表示请求被限制。
$ curl -iI http://localhost/search
HTTP/1.1 200 OK
Age: 6
Content-Length: 936
X-RateLimit-Counter: 1
X-RateLimit-Limit: 3
X-RateLimit-Period: 5
X-Varnish: 32770 3
Via: 1.1 varnish-plus-v4
$ curl -iI http://localhost/search
HTTP/1.1 429 Too many requests
Content-Length: 273
X-RateLimit-Counter: 4
X-RateLimit-Limit: 3
X-RateLimit-Period: 5
X-Varnish: 32774
Via: 1.1 varnish-plus-v4
完整的 throttle.vtc 文件将会在 VMOD 处理前后输出时间戳信息,用以给出 Memcached 和 SQLite 查询引入 VMOD 的开销数据。在一个本地虚拟机上运行着 Memcached 服务的 varnishtest 上运行 60 个请求,返回如下的每个操作的时间信息(单位 ms):
•SQLite SELECT,最大:0.32,最小:0.08,平均值:0.115
• Memcached incr_set(),最大:1.23,最小:0.27 平均值:0.29
这并不是个科学的结果,但是暗示了在大多数情况下,性能并不高。性能也是关于水平的能力。本文中给出的简单示例在需要的情况下,通过一个使用 Memcached 实例池的全局计数器,它的性能将会有规模性的扩展。
延伸阅读
现在已经有许多可用的 VMODs 了,VMODs Directory 是一个好的起点。这个目录中的一些亮点是关于 cURL、Redis、Digest 函数的 VMODs 和多种认证模块。
Varnish Plus,Varnish Cache 的完整商业支持版,已经经捆绑了一组高质量,有支持的 VMODs。对于开源版本,你可以手动下载和编译你需要的 VMODs。
缓存服务器 Varnish 概念篇 http://www.linuxidc.com/Linux/2014-05/101389.htm
缓存服务器 Varnish 概念篇 http://www.linuxidc.com/Linux/2014-05/101389.htm
Varnish Cache 的架构笔记 http://www.linuxidc.com/Linux/2013-10/91016.htm
CentOS 5.8 下 Varnish-2.1.5 的安装配置 http://www.linuxidc.com/Linux/2013-09/89916.htm
RedHat 脚本改用 CentOS 源更新安装 Nginx、PHP 5.3、Varnish http://www.linuxidc.com/Linux/2012-07/65801.htm
利用 Varnish 构建 Cache 服务器笔记 http://www.linuxidc.com/Linux/2012-07/65234.htm
缓存服务 Varnish 安装配置 http://www.linuxidc.com/Linux/2012-07/65228.htm
Varnish 编译安装所需准备 http://www.linuxidc.com/Linux/2012-07/65230.htm
Linux 下 Varnish 缓存的配置优化 http://www.linuxidc.com/Linux/2012-03/56435.htm
Varnish 的详细介绍:请点这里
Varnish 的下载地址:请点这里
英文原文:Varnish Goes Upstack with Varnish Modules and Varnish Configuration Language
本文永久更新链接地址:http://www.linuxidc.com/Linux/2015-05/118244.htm