☰
Nginx Stream代理Redis实战:从透明转发到TLS加密与高可用
2026/10/9 4:06:44 网站建设 项目流程

给 Redis 前面加一层 nginx,这事儿是不是听起来有点多此一举?Redis 本身不是已经在用一个端口提供服务了吗,再造一层代理好像纯粹是给自己添堵。可等你真的维护过那种多服务共用一套 Redis、生产环境跨网段调用、甚至需要临时对外暴露某个缓存实例的场景,就会明白直连 Redis 带来的麻烦远不止“端口裸奔”这么简单。ngxin 代理 redis 这个组合,说白了就是把“访问入口”和“实际存储”剥离开,用 nginx 的 stream 模块在客户端和 Redis 之间做一层纯 TCP 转发,从而把连接收敛、流量镜像、TLS 加密、连接超时控制这些运维痛点一次性收拢到网关层。这篇文章我会把我自己实际配置过、踩过坑、最后稳定跑在测试环境里的整套做法完整讲一遍,包括模块检查、网络规划、配置文件解析、验证方法,以及那些文档里不会直接告诉你的细节,帮你少走弯路。

1. 整体方案设计:为什么要在 Redis 门前加一道 nginx

1.1 哪些场景下需要这个代理

先说结论:不是所有项目都需要给 Redis 加代理,但如果你遇到下面几种情况,ngxin 代理 redis 就非常值得考虑。

第一个是“入口收敛”。公司内部往往不止一套业务系统,而缓存实例可能分散在多台机器上,每个系统各连各的 Redis,端口、密码、网络策略都不一样,运维起来特别乱。如果让所有客户端都先访问一个统一的 nginx 端口,再由 nginx 按照配置转发到不同的 Redis 实例,对外只暴露一个接入点,IP 白名单只需要在 nginx 上收紧一次,后续扩容、迁移 Redis 对客户端也是透明的。

第二个是“跨网段安全访问”。有些场景里,Redis 所在网段不能直接暴露给业务服务器,比如数据库主机只开放了 80/443 端口,或者安全组只允许公网入口访问。这时候可以用一台处在“公共区”的 nginx 作为跳板,把 Redis 端口 map 到自己可管理的端口上。这种部署方式在混合云、容器化、微服务网关拆分时很常见。

第三个是“负载均衡与故障转移”。Redis 主从架构手动切换后,客户端连接地址要立刻跟过去很费劲。通过 nginx upstream 把两个 Redis 地址配好,切换主从时,只需要在 nginx 层调整转发目标,客户端不用改动任何配置。多多少少能帮你在高可用层面省下一些手忙脚乱的功夫。

第四个是我自己觉得最容易忽略的:标准化客户端的网络配置。很多基础组件和框架只允许配置一个连接地址,不支持配多个冗余地址,或者不支持自动 failover。你用 nginx 做代理,客户端就只需要记住一个固定端口,剩下的事全部交给代理层处理。对业务方来说这是最省心的接入方式。

1.2 为什么选 nginx 而不是其他代理工具

选定 nginx 之前,我也对比过 HAProxy、LVS,甚至直接用 Redis 官方的方式(直接暴露端口 + 密码保护)。这里简单说说我的取舍逻辑。

HAProxy 在纯 TCP 四层代理这块确实做得非常成熟,配置语法也很轻巧,但它往往需要单独部署一套进程;如果你的环境里已经铺开使用了 nginx,或者你日常更熟悉 nginx 配置体系,那就没必要额外引入一个维护栈。nginx 从 1.9.0 开始内置了stream模块,专门用来处理 TCP 和 UDP 代理,性能和 HAProxy 在四层转发的差距并不明显,但你可以直接复用现有的 nginx 日志体系、监控体系和证书配置,对运维来说心智负担最低。

LVS 要动内核网络栈,通常用于大型集群入口流量分发,对实验环境和小规模业务来说有点“杀鸡用牛刀”。至于 Redis 直连,虽然省掉了中间层,但只要 Redis 节点需要做 TLS、ACL、流量审计,你就得在每个客户端侧分别处理,分散而难管理。综合来看,nginx stream 对绝大多数场景是“性价比”最高的选择。

1.3 数据链路是怎么跑的

要理解 nginx 代理 redis,得先理解它代理的是什么。Redis 客户端和 Redis 服务端之间走的是 RESP(REdis Serialization Protocol)协议,本质上就是基于 TCP 的文本协议。nginx 的stream模块不“翻译”这个协议,它只是把 TCP 字节流原样从客户端转发到后端 Redis。换句话说,nginx 在这个链路里更像一个“透明管道”:客户端redis-cli -p 16379发出的请求,原封不动到达 Redis,Redis 的响应原封不动返回客户端。

这个设计有一个很大的好处:不管是 Redis 的 AUTH、SELECT、GET、SET,还是复杂的 Pipeline 和事务,nginx 都不关心、不介入、不修改,天然兼容所有 Redis 客户端。坏处也很明显:nginx 无法像 HTTP 代理那样解析 Redis 内容做缓存或改写,但这对绝大多数基础设施使用场景来说完全不需要。

2. 开工前的环境检查:少了这一步后面全白搭

2.1 确认 nginx 带上了 stream 模块

很多初次接触的朋友最容易在这里踩坑:nginx 默认编译参数里是没有stream模块的。如果你用的是系统包管理器直接装的 nginx,比如 apt 的,通常编译得比较全,一般会带;但如果你用的是网上某些精简包,或者自己源码编译的 nginx,很有可能没带。我见过不止一次,老哥把配置写好了,执行nginx -t报unknown directive "stream",一脸懵。

验证方法很简单:

nginx -V 2>&1 | grep -- '--with-stream'

如果输出里出现了--with-stream,说明模块已经在编译参数里。没有的话,要么安装动态模块ngx_stream_module.so,要么重新编译 nginx。源码编译时加--with-stream --with-stream_ssl_module,其中stream_ssl_module是后面要讲“TLS 加密延伸”时用到的,提前按进去省得后面折腾。也可以执行nginx -t来确认当前配置是否正确。

2.2 redis 端到底要怎么定位

传统误区是“Redis 反正后面要加代理,干脆把 bind 设成 0.0.0.0 算了”。千万别这么干。即便前面有 nginx 挡着,Redis 服务端仍然应该尽可能收窄自己的监听范围。比较推荐的姿势是:如果 nginx 和 Redis 同机,就用bind 127.0.0.1;如果 nginx 和 Redis 是分开的两台机器,那就把 Redis 的bind设置成只监听 nginx 所在服务器的内网 IP,比如bind 10.10.10.5。顺便检查一下protected-mode,如果没设密码且只绑定本地,其实问题不大;但只要 bind 了非本地地址,就要把密码设上、protected-mode保持yes,这叫“纵深防御”。

# redis.conf 片段 bind 10.10.10.5 127.0.0.1 protected-mode yes requirepass your_strong_password

2.3 网络规划里的两个关键前置

接下去是端口规划。假设客户端要访问的端口是16379,nginx 所在服务器记得在安全组/防火墙放行这个端口;nginx 到 Redis 的端口6379不需要对外放行,但要保证 nginx 所在机器在网络上能到达 Redis。容易忽略的是:如果你在云服务器上测试,Linux 本地防火墙(firewalld/ufw)和安全组是两层,都要放行,缺一个连接也会失败。另外,这个端口一旦对外开,最好在云安全组里把允许的源 IP 收紧到自己办公网段或业务网段,不要把 0.0.0.0/0 放开,给后面运维留个干净环境。

3. nginx 配置的核心细节:stream 与 upstream 的正确写法

3.1 先搞懂 stream 配置块的层级关系

nginx 的主配置文件里,http、events、stream这是三个独立块,彼此平行。代理 Redis 的配置要写在stream块里,不能误写进http块,否则会因为listen指令冲突直接报错。用个生活化类比:http块是专门接待“浏览器”这类会话的柜台,各种 Location、Proxy 都是围绕 HTTP 七层语义设计的;而stream块是“纯快递收发室”,不拆包裹、不看内容,只负责把包裹从一个门洞转送到另一个门洞。Redis 是 TCP 流,所以必须走后面这个收发室。

整个结构大致是这样的:

stream { upstream redis_backend { server 127.0.0.1:6379; } server { listen 16379; proxy_pass redis_backend; } }

stream块出现在 nginx.conf 顶层,和http块平级。如果你为了整洁想拆分成独立配置文件,用include /etc/nginx/stream.d/*.conf;在顶层引入就好了。

3.2 一个最少必要配置:先让请求通起来

在没加任何复杂功能之前,一个能用的基础配置长这样:

# /etc/nginx/nginx.conf 片段 stream { server { listen 16379; proxy_pass 127.0.0.1:6379; proxy_connect_timeout 3s; proxy_timeout 300s; } }

proxy_pass这里可以直接写 IP:Port,不一定要用 upstream 变量。listen 16379表示 nginx 会在 16379 端口上等 TCP 连接,所有字节流量都会被原样转发到本机 6379 端口。proxy_timeout控制的是“任意一个方向上连续两次读写之间的最大空闲时间”,不是总连接时长,我习惯设到 300s,避免 Redis 客户端发起阻塞命令时连接被中间设备掐断。

配置改完后先执行nginx -t检查语法,再systemctl reload nginx或nginx -s reload平滑生效。

3.3 upstream 帮你做负载均衡与故障转移

当你有多个 Redis 实例/副本时,upstream就派上用场了。比如你有一套 Redis 主从结构,虽然平时业务主要打在主库上,但你希望在迁移或者主从切换时能快速切流量,那么 upstream 是最直接的工具。

stream { upstream redis_ha { server 192.168.1.10:6379; server 192.168.1.11:6379; } server { listen 16379; proxy_pass redis_ha; } }

stream模块里的 upstream 和 HTTP 模块的 upstream 很类似,默认采用轮询方式分配新连接。需要注意的是,Redis 不是无状态协议,客户端发出 AUTH 和 SELECT 之后,后续请求依赖这个连接上的状态;如果你把流量均衡到两台 Redis,客户端看到的数据库内容可能会不一致。所以在做流量的负载均衡时,一定要想清楚你的 Redis 之间是彼此独立(冷备/影子库)还是主从关系。单纯靠 stream 的轮询并不能帮你实现“自动把写流量切到主库”,它只是很机械地“建立一条 TCP 连接并选择一个后端”。

如果你需要按客户端来源固定哈希路由到某个 Redis,可以用hash $remote_addr consistent;指令。这种方式能保证来自同一个 IP 的连接尽量打到同一台 Redis,适合某些对会话一致性有要求的场景。

3.4 访问控制、连接数与日志:代理层能干的杂活

既然多了一层代理,就可以在代理层顺手做之前直连时很头疼的控制工作。

首先是访问控制。stream 块里支持allow和deny,按 IP 做简单白名单。比如只允许办公网段的客户端连:

server { listen 16379; allow 10.0.0.0/8; allow 192.168.1.0/24; deny all; proxy_pass redis_backend; }

这种白名单的优点是可以从网络层挡住绝大多数乱扫端口的流量,配合安全组双重设防。但注意它并不是应用层安全,如果办公网段里任何一台机器中了马,依然可以发 Redis 指令,因此 Redis 自己的requirepass还得照设。

其次是调大连接数。nginx 默认worker_connections影响单进程能承载的最大连接数,stream 代理同样受这个参数约束。如果业务上并发连接数高,需要同步调大:

events { worker_connections 65535; }

而且 worker 进程数建议先按 CPU 核心数来,比如 4 核就worker_processes 4;,不要一言不合就翻倍,否则大量进程空转反而增加上下文切换开销。

最后是日志。stream 模块的日志格式和 HTTP 模块完全不同,字段层面更接近 TCP 连接状态。我常用的格式配置如下:

stream { log_format proxy '$remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received $session_time'; access_log /var/log/nginx/redis_proxy.log proxy; }

这里的$protocol是 TCP,$status按正常会话记录 200,连接异常中断时可能是 502 或 500。用这套日志可以看到客户端来源 IP、连接时长和收发字节数,排查问题非常方便。

3.5 想要远程安全访问?在 nginx 层做 TLS 加密

Redis 原生的 TLS 支持需要编译redis-server时带 TLS,很多发行版默认不带。如果你不能动 Redis 服务端,但客户端又必须通过不安全的网络访问,那就让 nginx 来做 TLS 终结:客户端和 nginx 之间是加密链路,nginx 和 Redis 之间走本机可信内网。这个姿势对 Redis 完全透明,服务端不需要开 TLS。

配置很简单,在 stream 块的 server 里加上 SSL 相关参数:

stream { server { listen 16379 ssl; ssl_certificate /etc/nginx/certs/redis_proxy.crt; ssl_certificate_key /etc/nginx/certs/redis_proxy.key; ssl_protocols TLSv1.2 TLSv1.3; proxy_pass 127.0.0.1:6379; } }

注意这里要求你在编译 nginx 时有--with-stream_ssl_module,否则listen ... ssl会直接报错。证书可以参考内部 CA 签发的,也可以用 openssl 临时生成自签名证书用来测试。这么做之后,客户端redis-cli -p 16379 --tls -a 密码就能连接了。对业务客户端来说,只是多了--tls参数或一条 SSL 开启配置,其它逻辑完全不用改。

4. 一份可以直接抄作业的完整实例

4.1 从零写一份稳妥的配置

假设场景是这样的:公司内网有三台 Redis 节点,ip 分别是 192.168.1.10、192.168.1.11、192.168.1.12,其中主库是 10。客户端来自办公网段 10.0.0.0/8。我要求客户端统一从 nginx 的 16379 端口访问,并能承受 TLS 加密链路。最终配置如下:

# /etc/nginx/nginx.conf user nginx; worker_processes 4; events { worker_connections 65535; } stream { log_format proxy '$remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received $session_time'; access_log /var/log/nginx/redis_proxy.log proxy; upstream redis_backend { server 192.168.1.10:6379 max_fails=2 fail_timeout=10s; server 192.168.1.11:6379 max_fails=2 fail_timeout=10s backup; server 192.168.1.12:6379 max_fails=2 fail_timeout=10s backup; } server { listen 16379 ssl; ssl_certificate /etc/nginx/certs/redis_proxy.crt; ssl_certificate_key /etc/nginx/certs/redis_proxy.key; ssl_protocols TLSv1.2 TLSv1.3; allow 10.0.0.0/8; deny all; proxy_pass redis_backend; proxy_connect_timeout 3s; proxy_timeout 300s; proxy_socket_keepalive on; } }

这个配置里我做了几件事:

第一,用max_fails=2 fail_timeout=10s让 nginx 在连续 2 次连接失败后,把该节点临时标记为不可用,10 秒后再恢复试探。这种机制对追查后端故障和自动摘除故障节点很管用。第二,把 11、12 标记为backup,正常时只有主库 10 接收流量,一旦主库探测失败,nginx 会自动把流量切到备用节点。第三,proxy_socket_keepalive on;会让 nginx 向后端 Redis 发送 TCP keepalive 报文,降低连接被中间设备静默清理的概率。

4.2 重载配置并验证三个关键节点

配置更新后,先执行:

nginx -t

这个命令会检查/etc/nginx/nginx.conf及 include 进来的全部文件,如果有语法问题会精确报出文件行号。状态正常再执行:

nginx -s reload

然后我在连接测试时最常用下面三条命令:

redis-cli -p 16379 --tls ping redis-cli -p 16379 --tls info server redis-cli -p 16379 --tls -a your_password set hello world

如果 TLS 证书是自签名的,redis-cli可能会因证书校验失败拒绝连接,可以加--insecure参数跳过校验。这三条命令分别验证了“链路通不通”、“后端数据通不通”和“认证写读是否正常”。如果所有环节都正常,你会依次看到 PONG、server 信息、和 OK。

线上环境我还习惯同时观察两个方向的日志:nginx 的 access_log 里能看到连接状态,Redis 的redis-cli monitor或运行日志里能看到对应请求是否真的落到了后端。两边对照,基本能确认代理链路没有丢数据、没有多转发。

4.3 验证后别急着走,压测和调优很关键

确认基础功能没问题之后,我通常会顺手做一个简单压测,看看 nginx 代理层会不会成为瓶颈。可以用redis-benchmark -h 127.0.0.1 -p 16379 -n 100000 -c 50 --tls --insecure -a your_password来打一下。这个工具是 Redis 官方自带的,直接用它生成实际负载最方便。

压测时注意观察三个数值:QPS、P99 延迟、nginx worker 进程数。如果 QPS 很低,先看是不是 nginx worker 进程数没跑满,用top -H或pidstat查看 CPU 占用;如果 CPU 没打满但 QPS 上不去,多半是worker_connections或backlog不够。nginx 的listen参数背后还有一个backlog,默认 511,在高并发握手场景下可以调整:

server { listen 16379 ssl backlog=1024; }

否则 TCP 队列一满,客户端握手就超时。这个参数我以前忽略过,直到线上突然涌入大量短连接才发现问题就出在这里。

5. 实操过程中最常见的几个报错与处理思路

5.1 从客户端连不上:超时与连接重置

先表一个最常踩的坑:客户端redis-cli -p 16379连接后一直卡住,最后报 connection timeout。这种情况八成不是 nginx 配置问题,而是网络安全组/防火墙没放行,或者 nginx 所在服务器的firewalld没打开 16379 端口。排查时先在 nginx 服务器本机执行redis-cli -p 16379 ping,如果本机能通,外部不通,那就是防火墙或者云安全组的问题;如果本机都不通,说明 nginx 根本没正常监听,执行ss -lntp | grep 16379看一下进程状态,再去看 nginx 错误日志/var/log/nginx/error.log。

还有一种是客户端连接一会儿就断开,报connection reset by peer。我在实践中遇到这种情况,多半是proxy_timeout设置过短,连接空闲超过阈值被 nginx 主动掐掉。Redis 客户端如果长期闲置,这种问题会特别明显,把proxy_timeout调到 300s 以上基本能解决。

5.2 后端 Redis 明明活着,nginx 却说 upstream no live

nginx 日志里出现no live upstreams while connecting to upstream,意思是它把后端节点都标记为不可用了。这就要分两层排查。第一层:Redis 是否真的存活,用redis-cli -h 192.168.1.10 -p 6379 ping验证;第二层:nginx 连接 Redis 是否被限制,比如 Redis 的requirepass未设或允许访问 IP 网段不对。特别提醒一点,max_fails和fail_timeout排障的逻辑是:如果后端 Redis 所在主机上同时有大量业务连接,短暂出现maxconn限制或者连接数打满,nginx 可能会在几秒内连续失败然后把节点摘掉。“误杀”是常见的坑,所以配置时不要把max_fails设成 1,否则偶发抖动就会触发摘节点,设成 2 或 3 更稳。

5.3 客户端收到 ERR AUTH called without any password configured for the default user

这个报错很明显:你在 redis-cli 命令里加了-a password,但 Redis 本身没设置密码。代理是透传端口,Redis 直接告诉你“没有配置密码”,说明这条 AUTH 指令已经到达 Redis,但后端不接受。这不是 nginx 的问题,而是 Redis 的requirepass没配置或者配置为空。反过来,如果 Redis 设置了密码但客户端没带密码,会收到NOAUTH Authentication required。这两种场景都可以通过“在 Redis 本地直接连接测试”来确认是后端响应。

5.4 公司要求访问审计,但不知道该看哪个日志

如果你把访问控制和审计加到了代理层,那就要养成看 nginxredis_proxy.log的习惯。这个日志字段里的$remote_addr就是你客户端的真实 IP,$bytes_sent/$bytes_received可以估算单个连接的数据量。想更精细地看到每次执行的 Redis 指令,只能在 Redis 端开redis-cli monitor或开启 Redis 的慢日志/SENTINEL 审计,但生产环境不建议长期开 monitor,毕竟它会整体拉低 Redis 性能。代理层日志更多是用来做“连接层面”的审计,比如谁在何时连过、连接了多久、传了多少字节,配合 Redis 内部日志,基本能还原一条完整的访问链路。

5.5 一张速查表帮你快速定位问题

现象大概率原因第一步排查方式
外部连不上,本机可以安全组/防火墙未放行firewall-cmd --list-ports,对比云安全组
本机也连不上nginx 未启动或配置错误ss -lntp看端口监听,nginx -t看语法
连上后很快断开proxy_timeout 过短调大proxy_timeout到 300s
报 NOAUTH 错误Redis 有密码,客户端未提供客户端带-a 密码,或检查 redis.conf
报 AUTH 无密码Redis 没设密码,客户端多带了-a去掉客户端-a参数或设置 requirepass
no live upstreams后端 Redis 不可达或误摘单独 ping 后端 IP,重启 Redis 后观察 fail_timeout

这张表虽然简单,但这些场景基本涵盖了我日常运维中 90% 的问题。很多时候问题并不在 nginx 本身,而是周边的网络策略、后端状态、配置一致性,记得总是从链路两端的角度去排查,别只盯着代理层看。

最后再提醒两句

ngxin 代理 redis 这组配置,难度门槛其实不高,真正考验人的是对“四层转发”和“七层转发”的边界理解。nginx 的 stream 模块就是一条透明的 TCP 管道,它不会帮你做 Redis 指令缓存,也不会理解 Redis 的 key 分片逻辑,它只负责帮你把网络入口收拢、做些访问控制和加密。特别是在云环境下,这个组合是我比较推荐的一种 Redis 暴露方式:安全组只开一个端口,客户端只配一个地址,后端 Redis 想怎么迁移、怎么横向扩,代理层改两行配置就能完成。我个人在实际使用中最受益的一点,是给 nginx 层加好访问日志和监控告警,这样 Redis 一旦出现异常,我能第一时间通过代理日志定位是哪批客户端、哪个时段在打流量,而不是跑到 Redis 里去大海捞针。如果你也打算这么部署,建议先从最简单的 TCP 转发做起,通了一条链路再加 TLS、再加 upstream、再加 ACL,一层一层加上去,出问题时定位成本是最低的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询