一些思考与解答

发布时间:2026/10/21 5:17:34
一些思考与解答 20260914NginxNginx 是一款高性能的 HTTP 和反向代理服务器Nginx 这个软件既能充当 HTTP 服务器也能充当反向代理服务器具体扮演哪个角色取决于你怎么配置它。HTTP服务器HTTP 服务器也叫 Web 服务器的核心职责是接收 HTTP 请求返回 HTTP 响应。当 Nginx 作为 HTTP 服务器时它直接处理请求并自己生成响应典型场景提供静态文件浏览器请求 http://example.com/logo.pngNginx 直接从磁盘读出这个文件通过 HTTP 响应返回。这是 Nginx 最擅长的场景之一。返回简单内容比如配置一个健康检查接口直接返回 200 OK。处理动态请求Nginx 本身不擅长运行动态语言如 PHP、Java但可以通过 FastCGI、uWSGI 等协议把请求交给后端程序处理。关键点作为 HTTP 服务器时Nginx 是请求的终点它自己负责给出响应内容。反向代理服务器正向代理Forward Proxy正向代理站在客户端这一边代表客户端去访问外部服务器。客户端 → 正向代理 → 目标服务器典型例子你翻墙用的代理。你的浏览器配置了代理地址所有请求先发给代理代理替你去访问 Google再把结果拿回来。目标服务器只知道代理的 IP不知道你的真实 IP。特点代理的是客户端客户端知道自己在用代理服务器不知道。反向代理Reverse Proxy反向代理站在服务器这一边代表服务器接收客户端请求。客户端 → 反向代理 → 后端服务器一台或多台典型例子你访问 www.example.com实际上这个域名解析到的是 Nginx 的 IP。Nginx 收到请求后根据配置把请求转发给背后的 Tomcat、Node.js、Python 等应用服务器拿到响应后再返回给客户端。特点代理的是服务器客户端以为 Nginx 就是真正的服务器不知道背后还有别的机器核心区别反向的核心在于代理的方向对客户端是隐藏的。正向代理客户端主动配置代理代理为客户端服务。反向代理客户端完全无感知代理为服务器服务客户端以为自己在直接访问目标服务器。Nginx 作为反向代理能做什么负载均衡把请求分发给多台后端服务器避免单台压力过大。隐藏后端客户端只能看到 Nginx后端服务器的 IP、端口、架构都被保护起来。统一入口多个不同服务如 /api 走后端 A/static 走后端 B通过一个域名和端口对外提供。SSL 终止HTTPS 加密在 Nginx 层解密后端服务器只需处理明文 HTTP减轻负担。缓存、限流、压缩在代理层统一处理后端无需关心。原理其核心原理可以概括为事件驱动、异步非阻塞、多进程架构。一、整体架构多进程模型Nginx 启动后通常以一个 master 进程 多个 worker 进程 的形式运行master 进程负责读取配置、管理 worker 进程启动、停止、重启、处理信号不直接处理请求。worker 进程实际处理网络请求的进程数量一般配置为 CPU 核心数每个 worker 独立运行互不干扰。cache manager / cache loader用于管理磁盘缓存如代理缓存。这种设计的好处是worker 之间相互隔离一个 worker 崩溃不会影响其他 worker同时每个 worker 绑定一个 CPU 核心减少上下文切换提升性能。二、核心机制事件驱动 异步非阻塞这是 Nginx 高性能的根本原因。传统服务器如早期 Apache 的 prefork 模式 采用一个连接一个进程/线程的模型。当连接数上升时进程/线程数暴涨内存和上下文切换开销巨大而且很多线程在等待 I/O 时处于阻塞状态浪费资源。Nginx 的做法是每个 worker 进程用 一个线程 通过 事件循环event loop 同时管理成千上万个连接。工作流程大致是worker 进程通过 epollLinux、kqueueFreeBSD、select/poll 等 I/O 多路复用机制监听所有连接的 socket。某个 socket 有数据可读/可写时epoll 通知 worker。worker 处理该事件如果遇到需要等待的操作如读磁盘、读上游服务器响应不会阻塞等待而是注册一个回调继续处理其他事件。等操作完成后事件再次触发继续执行后续逻辑。这就是 异步非阻塞任何一步都不干等CPU 始终在处理有进展的任务。因此 Nginx 能用很少的资源支撑极高的并发连接数。三、请求处理流程一个 HTTP 请求进入 Nginx 后大致经历建立连接worker 通过 epoll 接受新连接。解析请求解析 HTTP 请求行、头部、body。匹配 location根据 server_name 和 location 规则找到对应的处理配置。执行处理阶段Nginx 把请求处理划分为多个阶段phase如 post_read、server_rewrite、find_config、rewrite、access、content 等。各模块按阶段依次处理。内容生成静态文件直接读磁盘返回用 sendfile、aio 等零拷贝技术。反向代理把请求转发给上游如 Tomcat、Node.js收到响应后再返回给客户端。返回响应写入 socket关闭或保持连接keepalive。nginx 的原理本质是用少量 worker 进程每个进程通过事件循环 异步非阻塞 I/O 同时处理海量连接配合多进程隔离、零拷贝、内存池等优化从而实现高并发、低资源消耗。nginx部署流程 VS docker部署nginx部署npm run build → 得到 dist/一堆 html/js/css拷到服务器/usr/share/nginx/html/ 或某个站点目录服务器上已经装着 Nginx改服务器上的 nginx.confnginx -s reload这时Nginx 装在这台 Linux 上的软件前端包 磁盘上的文件配置 这台机器上的 conf 文件浏览器访问 80 端口 → 本机 Nginx 读 conf → 要么读本地文件要么转发到别的服务。nginx作用静态文件服务器托管前端浏览器要 /portal/index.html、js、css → Nginx 从磁盘读出来返回。root/usr/share/nginx/html;location/portal{try_files $uri $uri//portal/index.html;}意思是访问 /portal/… 时优先找真实文件没有就回 portal/index.htmlVue 路由刷新不 404。这和「把前端包放在 nginx 下面」是同一件事。反向代理浏览器访问的是「当前网站」的路径例如https://域名/mojo-gateway/xxx浏览器以为接口也在当前这个域名下。Nginx 收到后在后面偷偷转给真正的后端location /mojo-gateway/ { proxy_pass http://svc-udop-gateway:8080/; }浏览器 ──► Nginx(80) 用户只看到这一个域名 ──► 真正的 gateway:8080为什么要这样配避免跨域页面在 https://udop.xxx接口也走同域 /mojo-gateway浏览器不拦。前端不用写死后端 IP前端只写 /mojo-gateway后端挂哪由 Nginx/proxy_pass 决定。统一入口静态页、接口、甚至 FineBI/监控都可以从一个 80/443 出去。location 「什么路径」proxy_pass 「转到哪台机器」。换环境只改 proxy_pass 后面的地址前端包可以不动docker部署 本质还是Nginx 前端文件只是装进了箱子dockerfile文件FROM172.16.40.143:5000/nginx:latest//先拿一个已经装好 Nginx 的系统镜像基础镜像当底板再在上面copy我们的前端和nginx.confCOPYdist//usr/share/nginx/html//把打好的前端文件放进镜像里 Nginx 的网站目录COPYnginx.conf/etc/nginx/nginx.conf//把仓库根目录这份 conf 盖掉镜像默认配置CMD[nginx,-g,daemon off;]//容器启动时就运行 NginxDocker 部署 ≠ 换了一种 Web 技术只是以前「服务器 手动拷文件」现在「把 Nginx文件配置打成一个可移动的包到哪 run 都行」。Dockerfile 是「镜像配方」build.sh 是「按环境把前端全打好再按配方做出镜像并导出 tar」的流水线脚本。本地/CInpm build → dist/docker build取 nginx 基础镜像塞进 dist塞进 nginx.conf→ 镜像文件tar/仓库里的 imagedocker images 命令能查看到镜像此时镜像存在这台机器 Docker 管理的本地存储里服务器docker load / pull 镜像docker run -p 80:80 镜像- 容器内 Nginx 启动- 读 conf- 监听 80- → 用户能打开页面、调接口差别对比项nginxDockerNginx 装在哪物理机/虚拟机系统里容器镜像里FROM nginx前端文件放哪拷到服务器目录COPY 进镜像的 html 目录改配置改服务器上的 confreload改仓库 nginx.conf 再重新 build 镜像或运行时挂载覆盖换机器再装 Nginx、再拷文件、再改 conf带上镜像 docker run 即可和统一构建的关系同一份 dist各环境改各服务器 conf同一份镜像各环境挂不同 conf推荐前端打成的dist包是否能直接访问能访问但有前提条件。 直接把 dist 包放到浏览器里双击 index.html 打开通常不能正常访问但把 dist 放到一个 Web 服务器如 Nginx下就能正常访问。一、先明确 dist 是什么前端项目Vue/React 等执行 npm run build 后会生成一个 dist 目录里面通常是dist/├── index.html ├── assets/│ ├── index-abc123.js │ ├── index-def456.css │ └── logo.png └── favicon.ico它本质就是一组静态文件一个 HTML、若干 JS/CSS/图片。理论上只要有办法通过 HTTP 把这些文件发给浏览器就能访问。二、情况一直接双击 index.htmlfile:// 协议——通常不行如果你把 dist 拷到本地直接双击 index.html浏览器地址栏是file:///C:/Users/xxx/dist/index.html这种情况下大概率页面白屏或报错原因有以下几个浏览器同源策略 / CORS 限制file:// 协议下浏览器对本地文件的加载有严格安全限制。JS 模块ES Module通过 file:// 加载时会被浏览器拦截报类似错误Access to script atfile:///.../index-abc123.jsfrom originnullhas been blocked byCORSpolicy因为现代打包工具Vite、Webpack产出的 JS 多为 ES Module而 ES Module 不允许在 file:// 下跨源加载。绝对路径问题打包时 publicPath / base 常配置成 /于是 index.html 里引用的是script src/assets/index-abc123.js/script/assets/… 在 file:// 下会被解析成磁盘根目录如 C:/assets/…而不是 dist 目录下导致找不到文件。前端路由History 模式失效如果用了 Vue Router / React Router 的 history 模式访问 /user/123 这类路径时需要服务器把请求回退到 index.html。file:// 下没有这种回退机制刷新页面就会 404。接口请求失败dist 里的 JS 会去请求后端 API如 /api/user。file:// 下没有同源的后端请求要么跨域被拦要么地址错误。结论直接双击打开 dist/index.html基本不可用。三、情况二放到 Web 服务器Nginx 等——可以正常访问把 dist 目录内容放到 Nginx 的静态目录下配置好就能正常访问server{listen80;server_name example.com;root/usr/share/nginx/html;# dist 内容放这里 index index.html;# history 路由回退 location/{try_files $uri $uri//index.html;}}这个 Nginx 进程在容器里只开放 80 端口80 端口在习惯上就是 HTTP 用的端口443 才是 HTTPS浏览器用 HTTP 连到这个 Nginx它就把 dist 里的文件读出来返回去dist 里的文件仓库里的货 Nginx仓库管理员 listen80管理员只在「80号窗口」发货这个窗口是HTTP窗口这时浏览器通过 http://example.com 访问一切正常。原因走的是 http:// 协议没有 file:// 的安全限制。/assets/… 会被解析成 http://example.com/assets/…Nginx 能正确返回文件。try_files 解决了前端路由刷新 404 的问题。接口请求可以通过 Nginx 反向代理转发到后端解决跨域。package-lock.json 需不需要提交到代码仓库结论需要提交而且强烈建议提交一、package-lock.json 是什么package-lock.json是 npm 在安装依赖时自动生成的文件它记录了项目依赖树的精确版本信息——不仅包括package.json里声明的直接依赖版本还包括这些依赖所依赖的所有传递依赖间接依赖的精确版本、下载地址和校验值。简单说package.json只写「我要什么」package-lock.json写「具体装的是哪一份」。二、为什么不提交会出问题如果不提交package-lock.json每个开发者或 CI 服务器执行npm install时npm 会根据package.json里的版本范围重新解析依赖。而package.json里通常写的是^1.2.3这种范围版本意味着「允许安装 1.x.x 的最新版」。问题就出在这里今天^1.2.3解析到1.2.3明天某个依赖发布了1.2.4后天又发了1.3.0。不同时间、不同机器上执行npm install装出来的依赖版本可能不一样。依赖版本不一致 → 行为不一致 → 「我本地好好的怎么到你那就报错了」「昨天还能跑今天 CI 就挂了」。这类问题极难排查因为代码没变变的只是依赖的隐性版本漂移。三、提交了有什么好处可复现构建任何人、任何时间npm install装出来的依赖树完全一致杜绝「在我机器上能跑」。CI/CD 稳定流水线每次构建用的依赖都一样不会因为依赖更新而突然失败。快速定位问题线上出问题时能精确知道当前跑的是哪个版本的依赖方便回滚或对比。安装更快有 lock 文件时npm 可以直接按锁定版本安装省去解析版本范围的时间。四、常见疑问Qlock 文件会不会很大、很乱A会比较大动辄几千行但它是自动生成的不需要手改也不需要 review 内容直接提交即可。Q什么时候需要更新它A当你主动执行npm install 新包或npm update时npm 会自动更新 lock 文件把这次变更一起提交即可。Q那package.json还需要提交吗A需要。package.json声明依赖范围和项目元信息package-lock.json锁定精确版本两者配合使用缺一不可。Q有没有例外情况A极少数场景如库/插件开发可能选择不提交但绝大多数应用项目都应该提交。如果你不确定提交就对了。五、一句话总结package-lock.json必须提交到代码仓库。它是保证「团队协作、CI 构建、线上部署」三端依赖一致的关键文件提交它成本极低不提交的代价却可能是一次难以排查的线上事故。.npmrc 是做什么的.npmrc是 npm 的配置文件全称 npm runtime configuration。它用来集中管理 npm 的行为比如从哪里下载依赖、用什么镜像源、是否走代理、缓存目录在哪等。它和package.json、package-lock.json一起构成了一个前端项目依赖管理的三件套。一、.npmrc 放在哪.npmrc可以出现在多个层级越靠近项目的配置优先级越高项目级项目根目录下的.npmrc只对当前项目生效通常提交到代码仓库。用户级~/.npmrc用户主目录下对当前用户的所有项目生效。全局级npm 安装目录下的.npmrc对所有用户生效。内置级npm 自带的默认配置优先级最低。二、.npmrc 里通常写什么最常见的用途是配置镜像源比如国内开发常用的淘宝镜像registryhttps://registry.npmmirror.com其他常见配置项registry指定 npm 下载包的源地址。proxy/https-proxy配置代理公司内网环境常用。cache指定 npm 缓存目录。save-exacttrue安装依赖时精确锁定版本不写^范围。ignore-scriptstrue禁止依赖包执行安装脚本安全考虑。三、.npmrc 要不要提交到代码仓库项目级的.npmrc建议提交原因和package-lock.json类似团队统一镜像源避免「你用的是官方源我用的淘宝源装出来的东西不一样」。统一代理、缓存等配置保证 CI 和本地行为一致。新人 clone 下来就能直接npm install不用手动配环境。四、一句话总结.npmrc是 npm 的配置文件用来统一「从哪下载、怎么下载」。项目级的.npmrc建议提交到代码仓库和package-lock.json一起保证团队依赖安装行为一致。如何在同时启动的不同项目中使用不同的 Node 版本不同项目可能依赖不同的 Node 版本比如 A 项目要 Node 16B 项目要 Node 20如果全局只装一个 Node切换项目时就会很痛苦。解决办法是用版本管理工具让每个项目自己声明用哪个 Node并在启动时自动切换。一、先装一个 Node 版本管理器不要直接去官网装 Node而是先装一个版本管理工具常见的有nvmNode Version Manager最常用支持 Linux / macOS / WindowsWindows 用 nvm-windows。fnmFast Node ManagerRust 写的更快跨平台。volta不仅能管 Node 版本还能管 npm / yarn / pnpm 的版本按项目自动切换。以 nvm 为例安装后可以随时安装、切换多个 Node 版本nvminstall16.20.2 nvminstall20.11.0 nvm use20.11.0# 当前终端切到 20二、让每个项目声明自己需要的 Node 版本光有 nvm 还不够因为「同时启动两个项目」时每个终端要手动nvm use很麻烦。更好的做法是在项目里写清楚要哪个版本工具自动切换。在项目根目录创建.nvmrc文件里面只写版本号# 项目 A 的 .nvmrc16.20.2# 项目 B 的 .nvmrc20.11.0然后配合 shell 钩子nvm 官方提供进入哪个项目目录就自动切到对应的 Node 版本。这样两个终端分别cd进 A、B 项目各自用的 Node 版本就不同了。三、同时启动两个项目的实操假设要同时跑 ANode 16和 BNode 20打开终端 1cd进 A 项目确认版本cd~/projects/A nvm use# 自动读 .nvmrc切到 16.20.2node-v# v16.20.2npmrun dev# A 项目跑起来打开终端 2cd进 B 项目cd~/projects/B nvm use# 自动读 .nvmrc切到 20.11.0node-v# v20.11.0npmrun dev# B 项目跑起来两个终端互不干扰各自用各自的 Node 版本。四、用 Volta 更省心按项目自动切换如果觉得 nvm 还要手动nvm use麻烦可以用volta。它会在你cd进项目时自动读取package.json里的engines字段或volta字段自动切换 Node / npm 版本不需要手动执行任何命令{engines:{node:20.11.0}}首次在项目里执行volta pin node20后volta 会把版本写进package.json之后任何人 clone 这个项目volta install一下就能用对的版本跑起来。五、一句话总结用nvm / fnm / volta管理多个 Node 版本再配合.nvmrc或package.json的engines让每个项目声明自己的版本就能在同时启动的不同项目里各用各的 Node互不干扰。20261020问题描述本地 npm run dev正常部署后用某个地址打开报错 crypto.randomUUID is not a function点开堆栈进到 vendor-spark-xxxxx.js不是自己写的业务代码排查思路看完整报错堆栈是谁调用的业务 / 依赖 / vendor 包对比环境差异本地 vs 部署HTTP vs HTTPS域名 vs IP vs localhost在报错页 Console 测三件套location.hrefwindow.isSecureContexttypeof crypto?.randomUUID对照结论若 isSecureContextfalse 且 randomUUID 是 undefined不是「依赖写坏了」是「当前页面环境不提供这个 API」spark 打包进 vendor-spark 的代码需要 crypto.randomUUID当前用非安全上下文常见是非 localhost 的 HTTP访问时浏览器不提供这个方法于是报错。核心概念不是浏览器「自动调用」浏览器只是提供 crypto 这个 Web API业务或依赖库在运行时主动调用 crypto.randomUUID()搜 src 搜不到是因为调用在依赖里构建后进了 vendor-spark-*.jsrandomUUID 是干什么的生成几乎不会重复的随机 IDUUID常用于会话 id、消息 id、表单行key、请求追踪、临时文件名等很多框架/SDK 会默认用它所以即使你业务没写依赖也可能写了。Secure Context安全上下文部分强大/敏感的 API浏览器规定只在「安全」的页面里开放。大致规则页面 一般是否安全上下文https://…是http://localhost / 127.0.0.1是开发特例http://公司域名 / http://内网IP 否crypto.randomUUID 就属于这类 APIMDNSecure context。本地 localhost → 有 API → 不报错部署 HTTP 内网域名 → 没 API → 报错改 HTTPS → 有 API → 又能进和本次项目的对应关系package.json 依赖 spark/*↓vite buildmanualChunks↓dist/assets/vendor-spark-哈希.js↓部署后浏览器加载这份 JS↓内部调用 crypto.randomUUID()↓当前页 isSecureContext ?├─ true → 正常└─ false → TypeError: randomUUID is not a functionvendor-spark 不是源码目录里的文件是构建产物线上 Sources 里看到的就是它。知识扩展为什么 localhost 要特殊放行方便开发本地常用 HTTP若一律禁止几乎没法调试这些 API。不要把「localhost 能跑」理解成「任何 HTTP 都能跑」。构建产物 / vendor chunk 是什么源码node_modules/spark/…构建Vite 把多个包捆成 vendor-spark-xxx.js并 minify所以在依赖「未压缩源码」里搜不到字面量很常见以浏览器堆栈为准解决方案怎么选业务上优先生产/UAT 用 HTTPS。polyfill 是内网暂时只能 HTTP 时的权宜之计。部署后为什么既能用http也能用https由什么决定的同一套前端静态资源能不能用 HTTP、能不能用 HTTPS由「外面怎么暴露端口和证书」决定不是 Vue 业务代码里开关。为什么两个都能进常见情况是基础设施同时开了两条入口http://域名 → 网关/服务器:80→ 前端静态资源https://域名 → 网关/服务器:443证书→ 同一套前端静态资源由什么决定

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询