Druid作为实时OLAP引擎,通常在内部网络中运行,Broker节点对外暴露HTTP查询接口。直接把Druid交给业务方调用虽然省事,但很快就会遇到三个问题:缺少统一的TLS终止、查询请求没有限流保护、上游连接在高并发下反复建立。引入Nginx作为查询入口,本质上是把这些横向关注点从Druid Broker中剥离出来,让集群集中资源执行聚合任务。下面会结合实际的Nginx配置片段,说明如何转发Druid的SQL查询、如何处理缓存与实时性的冲突,以及生产环境必须关注的超时和连接池参数。

一、Druid查询接口与Nginx反向代理基础配置
Druid提供了两类主要查询入口:原生JSON查询使用/druid/v2/,SQL查询使用/druid/v2/sql。实时OLAP场景通常推荐SQL查询,因为表达更直观,而且Druid的Broker节点会负责查询规划与路由。Nginx需要把特定路径的POST请求转发到Broker节点,同时保持上游连接复用。
下面是一个基础配置块:
upstream druid_broker {
server 192.168.10.11:8082;
server 192.168.10.12:8082;
keepalive 64;
}
server {
listen 443 ssl http2;
server_name olap.ipipp.com;
ssl_certificate /etc/nginx/ssl/olap.crt;
ssl_certificate_key /etc/nginx/ssl/olap.key;
location /druid/v2/sql {
proxy_pass http://druid_broker;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
client_max_body_size 2m;
proxy_request_buffering on;
}
}
上述配置把/druid/v2/sql的POST请求转发到名为druid_broker的上游组,里面包含两个Broker节点。为了启用长连接,proxy_http_version 1.1和proxy_set_header Connection "";必须同时出现,否则Nginx与上游之间每次请求都会重新握手。Druid Broker基于Jetty,支持HTTP keep-alive,复用连接可以明显降低P99延迟。
如果业务同时使用Druid的原生JSON查询接口,可以再增加一个location匹配/druid/v2/。但要注意Nginx的location匹配顺序,前缀较长的/druid/v2/sql会优先于较短前缀,所以两种接口不会互相干扰。请求体大小限制client_max_body_size建议根据真实查询中filter字段的长度来设置,过小会误伤高基维度的查询,过大又给恶意请求留下空间。
二、缓存策略:如何在Nginx层缓存Druid查询结果又不破坏实时性
Nginx的proxy_cache模块在缓存GET请求时非常简单,但Druid的SQL查询都是POST方式发送JSON文本。默认缓存键不包含请求体,如果我们直接对不同SQL请求开启缓存,必然会出现查询结果串号的问题。这不是Nginx的bug,而是缓存机制的设计默认值。要安全地在Nginx层缓存Druid查询结果,必须自定义缓存键,把请求体内容的哈希值纳入其中。
一种安全做法是在access_by_lua_block中读取请求体并计算MD5:
-- access_by_lua_block
ngx.req.read_body()
local body = ngx.req.get_body_data()
if not body then
return
end
local md5 = ngx.md5(body)
ngx.var.sql_hash = md5
然后Nginx配置中使用该变量:
set $cache_key "";
location /druid/v2/sql {
access_by_lua_block {
ngx.req.read_body()
local body = ngx.req.get_body_data()
if body then
ngx.var.sql_hash = ngx.md5(body)
else
ngx.var.sql_hash = "no_body"
end
}
proxy_cache_key "$scheme$host$uri$sql_hash";
proxy_cache_valid 200 10s;
proxy_cache_methods POST;
proxy_cache_bypass $arg_nocache;
}
在上面的Lua片段中,ngx.req.read_body()负责把请求体读取到内存,ngx.md5(body)生成固定长度摘要,再赋给自定义变量sql_hash。Nginx配置用proxy_cache_key "$scheme$host$uri$sql_hash";把该摘要编码进缓存键。这样即使两个查询的URL相同,只要JSON内容有差异,也会落到不同的缓存条目。
缓存有效期proxy_cache_valid 200 10s;设置成10秒是为了兼顾实时性和命中率。对于监控看板这类实时性要求高的场景,5到10秒的短缓存足以吸收突发流量;如果业务允许分钟级延迟,可以适当延长到60秒。需要提醒的是,Nginx缓存只适用于相同查询参数重复出现的场景,对于过滤条件高度随机化的探索式分析,缓存命中率会很低,此时不如关闭Nginx缓存,依赖Druid自身的历史段缓存。
三、限流与鉴权:保护Druid查询资源的必要手段
Druid查询对内存和CPU的消耗不可忽视,特别是groupBy、topN和近似分位数等操作。如果没有入口限流,一个失控的客户端就可能让Broker节点频繁Full GC,影响所有查询。Nginx的limit_req模块可以做第一层保护,限制每个来源IP的SQL查询频率。
limit_req_zone $binary_remote_addr zone=druid_sql:10m rate=10r/s;
location /druid/v2/sql {
limit_req zone=druid_sql burst=20 nodelay;
proxy_pass http://druid_broker;
}
limit_req zone=druid_sql burst=20 nodelay;表示在10r/s的平均速率下,允许最多20个请求的突发流量。超出部分会被立即拒绝,返回503状态码。这种策略能防止瞬时洪峰打垮Druid,但对合法的高并发看板可能过于严格。实际配置时应根据业务QPS画像调整数值,必要时可以按租户维度使用不同的limit_req区域。
除了限流,鉴权也是Nginx在OLAP入口的重要职责。Druid虽然支持基础认证,但把认证放到Nginx层可以减少Broker处理无效请求的开销。使用auth_request模块可以委托内部的认证服务进行JWT或API Key校验,只有收到204响应才会继续转发查询。
location /druid/v2/sql {
auth_request /auth;
proxy_pass http://druid_broker;
}
location = /auth {
internal;
proxy_pass http://127.0.0.1:9000/validate;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
认证服务的可用性直接影响到查询链路,生产环境中必须部署多个实例并设置合理的超时。如果认证服务响应变慢,Nginx会阻塞请求,因此proxy_read_timeout在认证location中建议控制在1秒以内。对于跨域场景,还可以在Nginx中统一处理OPTIONS预检请求,避免Druid Broker处理大量无意义的CORS请求。
location /druid/v2/sql {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, Content-Type";
add_header Access-Control-Max-Age 3600;
return 204;
}
proxy_pass http://druid_broker;
}
上述配置用if ($request_method = OPTIONS)提前返回,能有效减少上游连接占用。虽然Nginx官方不推荐在复杂环境中滥用if,但这里判断请求方法简单可靠。更保守的做法是用map变量控制CORS响应头,对于中小型部署,可读性比微弱的性能差异更重要。
四、性能调优:从连接复用、超时到压缩
性能调优的第一件事是把Nginx与Druid之间的连接复用配置好。前面已经提到upstream中的keepalive参数,它决定每个worker进程维持多少个空闲长连接。设置成64或128通常足够,太小会频繁建立连接,太大会占用Druid Broker的Jetty线程资源。
查询超时是所有实时OLAP系统都绕不开的参数。Druid查询如果碰到冷数据或复杂过滤条件,执行时间可能从几十毫秒涨到几十秒。Nginx的proxy_read_timeout必须大于Druid查询上下文中设置的timeout,否则客户端拿到504后,Druid仍在后台计算,造成资源浪费。推荐Nginx读取超时设为35秒,Druid查询超时设为25秒。
响应压缩开启gzip后,JSON结果的体积通常能减少70%以上,但CPU消耗也会增加。如果Nginx与Druid处于同一内网且带宽充足,关闭gzip可以降低延迟;如果是跨机房或公网链路,开启gzip收益明显。压缩级别gzip_comp_level 4在CPU与压缩率之间取得平衡,不建议设置到9。
gzip on; gzip_types application/json; gzip_min_length 1024; gzip_comp_level 4;
日志方面也值得留意。如果Nginx记录了完整的request_body,实时查询SQL中可能包含敏感维度值或用户标识,长期存储会有合规风险。建议至少在公网入口关闭请求体日志,只保留请求方法、状态码、耗时和缓存命中情况。通过调整log_format可以精确控制信息量,同时保持排障能力。