Fastify 生产部署最佳实践:反向代理配置、性能退化根因分析与容量规划指南
2026/9/6 15:51:47 网站建设 项目流程

Fastify 生产部署最佳实践:反向代理配置、性能退化根因分析与容量规划指南

【免费下载链接】fastifyFast and low overhead web framework, for Node.js项目地址: https://gitcode.com/GitHub_Trending/fa/fastify

本文基于 Fastify 官方部署建议文档(docs/Guides/Recommendations.md)系统讲解生产环境中部署 Fastify 的完整方法论:为什么必须使用反向代理、如何用 HAProxy / Nginx 完成 TLS 终结与多实例负载均衡、哪些写法会导致性能退化、Kubernetes 探针为什么连接不上、以及如何按 vCPU 规模做容量规划。读完本文,你将能够直接落地一套可复制的生产部署方案,并结合 Fastify 源码理解每条建议背后的实现依据。

为什么必须使用反向代理:Node.js 直连互联网是反模式

Fastify 官方建议明确指出:让 Fastify 应用直接处理多域名、多端口(HTTP 与 HTTPS 都监听)并直接暴露给互联网,是被强烈反对的反模式。这与 PHP、Python 时代需要专用 Web 服务器或 CGI 网关不同——Node.js 的标准库内置了易用性很高的 HTTP 服务器,使得应用可以"直接"处理 HTTP 请求,这带来了一种危险的诱惑。

官方列出这条建议背后的两条核心理由:

  1. 应用被要求同时处理 TLS 终结、域名路由、静态资源等职责,稀释了应用本身的专注度,引入了不必要的复杂度
  2. 这种架构阻碍了水平扩展——当流量增长时,你无法简单地在前面加机器。

官方文档列举了一个典型场景,说明反向代理如何解决一组常见生产需求:

  • 应用需要多个实例来处理负载;
  • 应用需要TLS 终结(TLS termination);
  • 应用需要把 HTTP 请求重定向到 HTTPS
  • 应用需要同时服务多个域名
  • 应用需要服务静态资源(例如 jpeg 文件)。

结论是:这些职责全部应交给反向代理(HAProxy、Nginx,或云厂商的 LB/Ingress),Fastify 实例只专注于处理 HTTP 请求本身。下面的两节分别给出可直接使用的 HAProxy 与 Nginx 完整配置。

HAProxy 配置:TLS 终结、HTTP→HTTPS 重定向与多域名分发

以下配置来自官方文档,覆盖前述五个需求:80 端口 HTTP 全量 308 重定向到 443、443 端口按 SNI 加载证书、/static前缀流量切到静态资源后端、按Host头分发到不同域名的 Node.js 后端组。

# The global section defines base HAProxy (engine) instance configuration. global log /dev/log syslog maxconn 4096 chroot /var/lib/haproxy user haproxy group haproxy # Set some baseline TLS options. tune.ssl.default-dh-param 2048 ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 ssl-default-server-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS # Each defaults section defines options that will apply to each subsequent # subsection until another defaults section is encountered. defaults log global mode http option httplog option dontlognull retries 3 option redispatch # The following option makes haproxy close connections to backend servers # instead of keeping them open. This can alleviate unexpected connection # reset errors in the Node process. option http-server-close maxconn 2000 timeout connect 5000 timeout client 50000 timeout server 50000 # Enable content compression for specific content types. compression algo gzip compression type text/html text/plain text/css application/javascript # A "frontend" section defines a public listener, i.e. an "http server" # as far as clients are concerned. frontend proxy # The IP address here would be the _public_ IP address of the server. # Here, we use a private address as an example. bind 10.0.0.10:80 # This redirect rule will redirect all traffic that is not TLS traffic # to the same incoming request URL on the HTTPS port. redirect scheme https code 308 if !{ ssl_fc } # Technically this use_backend directive is useless since we are simply # redirecting all traffic to this frontend to the HTTPS frontend. It is # merely included here for completeness sake. use_backend default-server # This frontend defines our primary, TLS only, listener. It is here where # we will define the TLS certificates to expose and how to direct incoming # requests. frontend proxy-ssl # The `/etc/haproxy/certs` directory in this example contains a set of # certificate PEM files that are named for the domains the certificates are # issued for. When HAProxy starts, it will read this directory, load all of # the certificates it finds here, and use SNI matching to apply the correct # certificate to the connection. bind 10.0.0.10:443 ssl crt /etc/haproxy/certs # Here we define rule pairs to handle static resources. Any incoming request # that has a path starting with `/static`, e.g. # `https://one.fastify.example/static/foo.jpeg`, will be redirected to the # static resources server. acl is_static path -i -m beg /static use_backend static-backend if is_static # Here we define rule pairs to direct requests to appropriate Node.js # servers based on the requested domain. The `acl` line is used to match # the incoming hostname and define a boolean indicating if it is a match. # The `use_backend` line is used to direct the traffic if the boolean is # true. acl example1 hdr_sub(Host) one.fastify.example use_backend example1-backend if example1 acl example2 hdr_sub(Host) two.fastify.example use_backend example2-backend if example2 # Finally, we have a fallback redirect if none of the requested hosts # match the above rules. default_backend default-server # A "backend" is used to tell HAProxy where to request information for the # proxied request. These sections are where we will define where our Node.js # apps live and any other servers for things like static assets. backend default-server # In this example we are defaulting unmatched domain requests to a single # backend server for all requests. Notice that the backend server does not # have to be serving TLS requests. This is called "TLS termination": the TLS # connection is "terminated" at the reverse proxy. # It is possible to also proxy to backend servers that are themselves serving # requests over TLS, but that is outside the scope of this example. server server1 10.10.10.2:80 # This backend configuration will serve requests for `https://one.fastify.example` # by proxying requests to three backend servers in a round-robin manner. backend example1-backend server example1-1 10.10.11.2:80 server example1-2 10.10.11.2:80 server example2-2 10.10.11.3:80 # This one serves requests for `https://two.fastify.example` backend example2-backend server example2-1 10.10.12.2:80 server example2-2 10.10.12.2:80 server example2-3 10.10.12.3:80 # This backend handles the static resources requests. backend static-backend server static-server1 10.10.9.2:80

关键配置点解读

  • option http-server-close:让 HAProxy 主动关闭到后端的连接而不是保持长连接。官方注释特别指出,这能缓解 Node 进程侧出现的"意外连接被重置"类错误——这是 Node.js 后端与反向代理组合时的一个实战经验项。
  • redirect scheme https code 308 if !{ ssl_fc }:80 端口上所有非 TLS 流量按原 URL 做 308 重定向到 HTTPS。308 与 301 的语义差别在于它会保留请求方法与请求体语义,适合需要严格保持请求行为的重定向。
  • bind 10.0.0.10:443 ssl crt /etc/haproxy/certs:启动时加载整个证书目录,并按SNI 匹配为每条连接选择对应域名的证书,从而实现单监听器服务多域名。
  • acl+use_backend规则对hdr_sub(Host)按 Host 头做子串匹配,把不同域名分发到不同后端组;未匹配的 Host 落到default_backend
  • 后端均为纯 HTTP:后端服务器不需要自己服务 TLS,TLS 在反向代理处终结(TLS termination),这正是把安全职责从 Fastify 应用剥离的体现。

Nginx 配置:upstream 负载均衡、HTTPS 强制与 HTTP/2

Nginx 示例展示了一个更常见的单机代理形态:upstream 定义 2 主 1 备的 Fastify 后端组,80 端口全量 301 跳转 HTTPS,443 端口开启 TLS 1.3 与 HTTP/2 后反代到 upstream。

# This upstream block groups 3 servers into one named backend fastify_app # with 2 primary servers distributed via round-robin # and one backup which is used when the first 2 are not reachable # This also assumes your fastify servers are listening on port 80. upstream fastify_app { server 10.10.11.1:80; server 10.10.11.2:80; server 10.10.11.3:80 backup; } # This server block asks NGINX to respond with a redirect when # an incoming request from port 80 (typically plain HTTP), to # the same request URL but with HTTPS as protocol. # This block is optional, and usually used if you are handling # SSL termination in NGINX, like in the example here. server { # default server is a special parameter to ask NGINX # to set this server block to the default for this address/port # which in this case is any address and port 80 listen 80 default_server; listen [::]:80 default_server; # With a server_name directive you can also ask NGINX to # use this server block only with matching server name(s) # listen 80; # listen [::]:80; # server_name example.tld; # This matches all paths from the request and responds with # the redirect mentioned above. location / { return 301 https://$host$request_uri; } } # This server block asks NGINX to respond to requests from # port 443 with SSL enabled and accept HTTP/2 connections. # This is where the request is then proxied to the fastify_app # server group via port 3000. server { # This listen directive asks NGINX to accept requests # coming to any address, port 443, with SSL. listen 443 ssl default_server; listen [::]:443 ssl default_server; # With a server_name directive you can also ask NGINX to # use this server block only with matching server name(s) # listen 443 ssl; # listen [::]:443 ssl; # server_name example.tld; # Enable HTTP/2 support http2 on; # Your SSL/TLS certificate (chain) and secret key in the PEM format ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/private.pem; # A generic best practice baseline for based ssl_session_timeout 1d; ssl_session_cache shared:FastifyApp:10m; ssl_session_tickets off; # This tells NGINX to only accept TLS 1.3, which should be fine # with most modern browsers including IE 11 with certain updates. # If you want to support older browsers you might need to add # additional fallback protocols. ssl_protocols TLSv1.3; ssl_prefer_server_ciphers off; # This adds a header that tells browsers to only ever use HTTPS # with this server. add_header Strict-Transport-Security "max-age=63072000" always; # The following directives are only necessary if you want to # enable OCSP Stapling. ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /path/to/chain.pem; # Custom nameserver to resolve upstream server names # resolver 127.0.0.1; # This section matches all paths and proxies it to the backend server # group specified above. Note the additional headers that forward # information about the original request. You might want to set # trustProxy to the address of your NGINX server so the X-Forwarded # fields are used by fastify. location / { proxy_http_version 1.1; proxy_cache_bypass $http_upgrade; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; 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_set_header X-Forwarded-Proto $scheme; # This is the directive that proxies requests to the specified server. # If you are using an upstream group, then you do not need to specify a port. # If you are directly proxying to a server e.g. # proxy_pass http://127.0.0.1:3000 then specify a port. proxy_pass http://fastify_app; } }

关键配置点解读

  • upstream主备结构:两台主服务器轮询(round-robin),第三台标记backup,仅当前两台不可达时接管。这里假设 Fastify 实例监听 80 端口。
  • HSTS 头Strict-Transport-Security: max-age=63072000,即两年):告知浏览器今后只走 HTTPS,与 80 端口的 301 跳转配合形成闭环。
  • OCSP Staplingssl_stapling/ssl_stapling_verify/ssl_trusted_certificate三件套用于加速证书吊销检查,按需启用。
  • WebSocket 支持proxy_set_header Upgrade/Connection 'upgrade'转发升级请求所需的头部。
  • X-Forwarded-*头与 FastifytrustProxy的配合:这是反向代理场景下最容易踩的坑。Nginx 配置把X-Real-IPX-Forwarded-ForX-Forwarded-Proto传给 Fastify,但 Fastify 默认不会信任这些头。Fastify 提供了 trustProxy 选项,启用后才会基于这些字段还原真实客户端地址与协议,例如const fastify = Fastify({ trustProxy: true }),也可传入具体 IP/CIDR 列表(如'127.0.0.1,192.168.1.1/24')只信任指定代理。相关行为在 test/trust-proxy.test.js 中有系统覆盖。

性能退化的常见根因:五条来自官方的生产经验

官方文档单列了"Common Causes Of Performance Degradation"一节,列出五类会增加延迟或降低吞吐的写法。这一节值得单独记住,因为每一条都对应 Fastify 或路由引擎的某个具体机制。

1. 热路径优先使用静态路由或简单参数路由

正则(RegExp)路由代价高,参数很多的路由也会拖累路由引擎的匹配性能。官方指向 Routes 文档的 Url building 小节:如果某个 URL 模板需要动态拼接大量参数,应考虑用更静态的路径结构替代,或减少路径参数数量。Fastify 的测试套件中 test/constrained-routes.test.js 与 test/versioned-routes.test.js 覆盖了约束路由的行为,也侧面说明约束路由是功能特性而非免费的默认能力。

2. 谨慎使用路由约束

版本约束(version constraint)可能降低路由性能,异步自定义约束应被视为最后手段。背景可参考 Routes 文档的 Constraints 小节。从源码结构看,约束信息参与路由节点的匹配判定,约束越多、匹配时的条件判断越多,热路径开销越高——这与"正则路由昂贵"是同一类问题:不要让路由匹配本身成为瓶颈。

3. 优先使用 Fastify 插件/Hooks,而非通用中间件

Fastify 的中间件适配器(middleware adapter)"能用",但在性能敏感路径上,原生的插件与 hooks 集成方式通常更好。详见 Middleware 参考文档。从框架设计看,hooks 是 Fastify 请求生命周期的一等公民(docs/Reference/Hooks.md),而中间件本质上是对 Express 风格的适配层,多一层抽象就多一层开销。

4. 定义 response schema 加速 JSON 序列化

为响应定义 schema 后,Fastify 可用预编译的序列化器替代通用的JSON.stringify,官方在路由文档中给出的经验值是约 10%–20% 的吞吐提升。操作方式见 Getting Started 的 Serialize your data 小节。

5. 默认关闭 Ajv 的allErrors

官方建议保持allErrors禁用,仅在需要详细校验反馈的场景(例如表单密集型 API)才开启,延迟敏感的端点应避开它。理由有两层:

  • 开启allErrors: true后,校验器会收集全部校验错误而不是遇到第一个就返回,单请求做的校验工作更多;
  • 对不可信输入而言,更重的校验流程会让拒绝服务(DoS)攻击更容易达成。

allErrors属于校验器的自定义选项,可在全局或单个 schema 上通过customOptions配置,用法与显式关闭示例见 Validation and Serialization 文档(例如customOptions: { allErrors: false })。测试用例 test/schema-validation.test.js 中也有allErrors: true的针对性验证,可作为行为参照。

Kubernetes 部署:readinessProbe 连不上应用的根因

Fastify 实例默认监听回环地址,而 Kubernetes 的readinessProbe默认使用 Pod IP 作为主机名发起探测。如果应用只监听回环地址,探针请求根本到不了应用,Pod 会一直判定为未就绪。官方给出的解决方案是二选一:

  1. 让应用监听0.0.0.0
  2. 或在readinessProbe.httpGet中显式指定自定义 hostname。

官方示例(探针请求/health,端口 4000):

readinessProbe: httpGet: path: /health port: 4000 initialDelaySeconds: 30 periodSeconds: 30 timeoutSeconds: 3 successThreshold: 1 failureThreshold: 5

这一点在源码中有直接印证:lib/server.js 中listen的默认参数为{ port: 0, host: 'localhost' },即不显式指定 host 时绑定回环地址;当 host 为localhost时,lib/server.js 还会额外做 IPv4/IPv6 双栈绑定(multipleBindings)以同时覆盖127.0.0.1::1。因此要暴露到 Pod 网络,必须显式传入host: '0.0.0.0',这与 docs/Reference/Server.md 中listen的说明一致。

生产容量规划:vCPU 分配的经验法则

官方强调:要为生产环境选对规格,最可靠的方式是对不同环境配置做自己的压测,环境可能使用物理核、vCPU 甚至分数 vCPU,文档中统一用 vCPU 指代任意 CPU 形态。可用的压测工具包括 Grafana k6 与 autocannon。

在此前提下,官方给出三条经验法则(rule of thumb):

  • 追求最低延迟:每个应用实例(如一个 k8s Pod)建议分配 2 vCPU。第二个 vCPU 主要被垃圾回收(GC)与 libuv 线程池使用。好处是:GC 可以更频繁地运行,从而降低内存占用;主线程也不用停下来让位给 GC,用户感知延迟最低。
  • 追求最大吞吐(单位 vCPU 处理尽可能多的请求/秒):应减少每个实例的 vCPU 数量,Node.js 应用跑在 1 vCPU 上完全没有问题,此时用更多小实例换总吞吐。
  • 极限实验:可以再尝试更小的规格,某些场景下吞吐反而更好。文档提到有 API 网关方案在 Kubernetes 上以100m–200m vCPU工作良好的报告——注意这只是"有报告",属于可实验的方向而非保证。

官方同时建议了解 Node.js 事件循环的内部机制(GC、libuv 线程池与事件循环主线程的关系),以便为自己的应用做出正确判断。

单进程运行多个 Fastify 实例

有些场景需要在同一台服务器上跑多个 Fastify 应用,官方给出的典型用例是:在没有反向代理或 Ingress 防火墙可用的情况下,把 metrics 端点暴露在独立端口上,避免被公网访问

官方的结论很明确:在同一个 Node.js 进程内启动多个 Fastify 实例并发运行是完全可行的,即使在高负载系统下也没问题。原因是"每个 Fastify 实例只产生与其接收流量相匹配的负载,加上该实例占用的内存"——空闲实例几乎不消耗 CPU,因此共享进程的心跳开销可以忽略。这与反向代理建议并不矛盾:多实例解决的是"职责隔离/端口隔离",反向代理解决的是"对外入口的统一治理",两者通常组合使用。

小结

Fastify 的部署建议可以浓缩为四件事:入口交给反向代理(HAProxy/Nginx 负责 TLS、重定向、多域名与静态资源,Fastify 专注请求处理,并用trustProxy正确还原客户端信息)、避开已知性能退化写法(正则/多参数路由、异步自定义约束、通用中间件、缺失 response schema、开启allErrors)、修好 Kubernetes 探针的监听地址0.0.0.0或自定义 hostname)、按延迟或吞吐目标规划 vCPU(2 vCPU 换延迟,1 vCPU 或更小规格换吞吐,最终以自行压测为准)。以上结论均可在当前仓库中交叉验证:监听默认行为见 lib/server.js,代理头信任见 docs/Reference/Server.md,校验选项见 docs/Reference/Validation-and-Serialization.md,路由与约束行为见 docs/Reference/Routes.md。

【免费下载链接】fastifyFast and low overhead web framework, for Node.js项目地址: https://gitcode.com/GitHub_Trending/fa/fastify

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询