
写了十几年 Web 应用从最初的 ASP、JSP 到后来的 SSM、Spring Boot再到现在的前后端分离和微服务期间换过不少技术栈接手过各种别人留下的“祖传代码”也亲手从零搭过不少项目。但不管技术怎么变有一个底层的东西从来没变过那就是 B/S 架构。哪怕你只是写个简单的个人博客或者参与一个日活千万级的复杂系统骨子里都跑不掉“浏览器发请求、服务器返回结果”这套逻辑。很多人觉得 B/S 架构听起来“太基础了”不就是一个浏览器一个服务器嘛有什么好讲的。恰恰是这种心态让很多初级开发者在真正做项目时栽了跟头——搞不清请求在哪一步出了问题、Session 到底存在哪、为什么前后端一分开就各种跨域报错、为什么并发一高服务器就扛不住。这些问题根子上全是对 B/S 架构理解不到位。这篇文章我打算从原理到实践把 B/S 架构这张“网”彻底揭开不整虚的全是开发时真正用得上的东西。适合刚入门 Web 开发的新人也适合那些会写接口但总觉得底层差点意思的初中级开发者。1. 先搞清楚B/S 架构到底是什么1.1 从一次“上网”说起聊 B/S 架构之前先回想一个极其日常的画面你打开浏览器输入网址按回车然后页面就出来了。这整个过程背后其实就是一次完整的 B/S 架构交互。B 就是 Browser浏览器S 就是 Server服务器浏览器负责把你的请求发出去服务器负责处理请求然后把结果传回来最后浏览器再把结果渲染成你看到的页面。听起来简单但“请求发出去”和“结果传回来”之间远没有字面上那么轻松。你输入的 www.example.com 这串字符浏览器要先把它翻译成服务器的 IP 地址然后建立一条可靠的网络连接再按照约定好的格式把“我想看什么”告诉服务器。服务器那边收到后要根据请求的内容去查数据库、算逻辑、拼数据最后再按照同样约定好的格式把结果返回给浏览器。浏览器拿到结果后又要解析、渲染把一串字符串变成排版精美的页面。这个“约定好的格式”就是 HTTP 协议而浏览器和服务器各自扮演的角色、它们之间如何通信、数据如何流转就是 B/S 架构的全部内涵。可以说弄懂了这趟“数据旅行”你就掌握了 Web 开发的底层逻辑以后不管是调接口、解决跨域、做性能优化还是排查线上故障都会清晰很多。1.2 B/S 架构的两大核心角色浏览器与服务器在 B/S 架构里浏览器和服务器是绝对的主角两者职责边界非常清晰。浏览器这边它做的事情远不止“展示页面”这么简单。首先它要负责用户交互——我们看到的输入框、按钮、页面跳转本质都是浏览器在监听用户操作其次它要负责发请求——把用户的操作转化为 HTTP 请求报文发送给服务器最后它还要负责“翻译”服务器返回的内容——HTML、CSS、JavaScript 这些通过 HTTP 响应的字符串浏览器需要解析成 DOM 树、CSSOM 树再经过布局和绘制变成实际页面。这还只是“传统”浏览器的职责现在的现代浏览器更像是一个小型的操作系统支持本地存储、多线程Web Worker、音视频解码、三维图形渲染等等功能越来越重但核心职责没变作为用户与服务器的中间桥梁。服务器这边的角色就更多了。往大了分它至少包含三部分Web 服务器负责接收 HTTP 请求并返回静态资源如 Nginx、Apache、应用服务器负责运行业务代码、处理动态逻辑如 Tomcat、Node.js 进程、数据库服务器负责存储和查询数据如 MySQL、PostgreSQL、Redis。这三部分可以跑在同一台机器上更多时候是分开部署的。服务器端的职责同样很明确——接收请求、解析请求、执行业务、访问数据、组装响应、返回结果。任何一个环节出了问题前端拿到的结果就可能是 404、500 或者根本超时。1.3 B/S 三层架构表现层、业务层、数据层我们常说的“三层架构”并不是某本教科书凭空想出来的理论而是 B/S 架构在实践中自然演化出来的结果。把浏览器端的表现逻辑、服务器端的业务逻辑、以及后端的存储逻辑拆分清楚各层的职责边界分明整个系统的可维护性才谈得上。表现层Presentation Layer前端的天下。它管的是“跟用户打交道”的事页面长什么样、交互怎么触发、数据如何展示。在早期的 B/S 架构中表现层是服务端渲染的 JSP/ASP 页面HTML 里混着 Java 代码而现在的表现层已经非常独立单页应用SPA把整个页面的渲染都拿到了浏览器端表现层变成了完整的“前端应用层”。业务层Business Layer后端的重头戏。它管的是“业务规则怎么执行”用户登录时校验密码、下单时扣减库存并生成订单、发帖时检查内容和权限。业务层是 B/S 架构中最“值钱”的部分也是不同项目差距最大的地方。同样的框架有人能写出高内聚低耦合的优质业务代码有人写出来就是一团乱麻全看这一层怎么设计。数据层Data Layer负责“把数据留住”。你的注册信息、订单记录、文章内容最终都要落到数据库里。数据层要解决的不只是“存起来”还包括怎么高效查询、怎么保证一致性、怎么抗住并发读写。在实际架构中数据层往往不止一层——Redis 做缓存挡在前面MySQL 做持久化存储垫在后面有时还会有消息队列来做数据的异步流转。这三层各自独立又通过约定的接口相互协作。表现层只管发 HTTP 请求拿数据业务层只处理逻辑不关心页面长什么样数据层只负责读写数据不对业务做判断。这样拆分的好处是——任何一层都能独立替换、独立扩展、独立测试。你换了前端框架后端接口不用动你换了数据库只要接口返回的数据结构不变前后端都不受影响。2. 为什么 Web 开发离不开 B/S 架构2.1 B/S 与 C/S 的对比看得见的差别和看不见的差别要理解 B/S 架构的价值最好的方式是和它的前辈 C/SClient/Server客户端/服务器架构做个对比。C/S 架构的核心思路是专门写一个客户端程序安装到用户电脑上由这个客户端和服务器通信。银行早期用的柜面系统、某些老牌桌面版 ERP、游戏里的客户端都是典型的 C/S 架构。B/S 架构把 C/S 里的“客户端”给替换成了浏览器这个替换带来的差别是变革性的。看得见的差别在“部署”上——C/S 架构每台机器都要装客户端版本升级要逐个更新操作系统不兼容就抓瞎B/S 架构只要浏览器能开应用就能用升级只发生在服务器端用户无感。看不见的差别在“成本”和“数据管控”上——C/S 客户端往往会在本地缓存数据数据安全难以保证B/S 架构所有数据都集中在服务器端本地只是临时渲染控制力完全掌握在自己手里。当然 C/S 架构并未完全退出历史舞台。在某些特殊场景下比如需要大量本地计算、离线办公、高度定制化交互的场景C/S 架构依然有它的优势。但在绝大多数面向大众用户的业务场景中B/S 架构的“零安装、易升级、强管控”优势是压倒性的。对比维度C/S 架构B/S 架构客户端需要安装专用软件只需通用浏览器升级维护每台客户端逐一更新只更新服务器端跨平台能力受操作系统限制浏览器可用即可数据管控本地易有数据残留数据集中在服务器性能表现本地计算能力强依赖网络与服务器性能开发成本前后端都要写独立客户端前端适配浏览器标准即可2.2 B/S 架构到底解决了什么问题往大处看B/S 架构解决的第一个大问题是“规模化分发”。做一个应用要服务十万用户C/S 架构你得让十万人各自安装客户端B/S 架构只需要部署一台服务器十万个用户打开同一个网址就能访问。互联网能在过去二十多年里爆发式增长B/S 架构的零门槛客户端功不可没——它让“任何人用任何设备打开浏览器就能用你的服务”成为了现实。第二个大问题是“统一标准”。HTTP 协议、HTML 标准、JavaScript 引擎这些都是开放的公共标准所有浏览器厂商都在尽量遵守。对开发者来说这意味着学一套技术栈写一套代码理论上就能在几乎所有设备上跑起来。对比之下早期的 C/S 开发每个平台都要写一套界面和逻辑工作量成倍增长。第三个大问题是“运维集中化”。B/S 架构让开发团队的运维重心从“管用户的机器”转移到了“管自己的服务器”。出了问题你不需要挨个联系用户“麻烦你把日志发给我”只需要登上自己的服务器查日志、看监控。这是从根上改变了软件交付和运维的模式。2.3 现代 B/S 架构的演进从多页到单页再到前后端分离B/S 架构诞生至今内部结构已经经历了好几轮演进很多老开发亲身经历过这些阶段。早期阶段是“服务端渲染的多页应用”。JSP、ASP、PHP 这类技术服务器直接根据请求动态生成整个 HTML 页面返回给浏览器。那时候“前端”的角色很弱大部分页面逻辑都写在服务端脚本里JavaScript 只是用来做点表单校验和动态效果的配角。这个阶段的问题在于前列腺服务器压力极大每次页面跳转都要重新加载整个页面用户体验也比较粗糙。后来进入了“前后端分离 单页应用”阶段。Ajax 技术的普及是个分水岭页面不再需要整体刷新JavaScript 可以悄悄在后台发请求拿数据再局部更新页面。接着 Vue、React、Angular 这些现代框架崛起前端彻底脱离服务端变成一个完全独立的“前端应用”后端职责退化成只“提供数据接口”——也就是我们常说的 RESTful API 或现在的 GraphQL。浏览器端负责路由、状态管理、组件渲染服务器端只专注业务逻辑和数据访问两者通过 JSON 交换数据。现在则是“云端原生”阶段。B/S 架构的外延进一步扩大浏览器端可以是 PC 网页、手机 H5、甚至小程序容器服务器端从单体应用拆分成了微服务部署在容器里跑在云上。但剥开这些外壳往里看依然是浏览器发 HTTP 请求、服务器处理返回这套 B/S 架构的底子。所以我说搞懂 B/S 架构就等于拿到了理解一切 Web 系统的“元技能”。3. 核心原理拆解一次 HTTP 请求的完整旅程3.1 从地址栏输入到页面渲染中间发生了什么原理这东西说一千道一万不如跟着一次真实请求走一遍。假设你在浏览器输入了https://www.example.com/login然后按下回车接下来发生了什么第一步是 DNS 解析。浏览器需要知道www.example.com这个域名背后的服务器 IP 地址。它会先查本地缓存、再查系统 hosts 文件、再向上游 DNS 服务器发起递归查询最终拿到 IP。这一步看似不起眼但很多“网络慢”的故障源头就在这里——DNS 解析超时、被劫持指向错误 IP都是实操中的常见问题。第二步是建立 TCP 连接。拿到 IP 之后浏览器和服务器要通过 TCP 协议“握手”建立可靠连接具体来说就是三次握手浏览器发 SYN服务器回 SYNACK浏览器再回 ACK。这里有一点值得注意——HTTPS 的请求在 TCP 之上还要多一层 TLS 握手要去验证证书、协商加密密钥所以 HTTPS 建立连接会比 HTTP 慢一点点但换来的是传输内容无法被窃听和篡改。第三步是发送 HTTP 请求。连接建立后浏览器会把请求信息打包成 HTTP 报文——包括请求行方法、URL、协议版本、请求头User-Agent、Cookie、Accept 等、请求体POST 时的表单数据或 JSON。这包东西通过 TCP 连接发送给服务器。第四步是服务器处理请求并返回响应。Web 服务器如 Nginx先把 HTTP 报文解析出来根据路径决定把请求交给谁——如果是静态资源如 CSS、JS、图片直接返回如果是动态接口则转发给后端的应用服务器如 Tomcat、Node.js。应用服务器执行业务逻辑可能需要查数据库、调第三方接口然后把结果打包成 HTTP 响应报文返回状态行如 200 OK、响应头Content-Type、Cache-Control 等、响应体HTML 或 JSON。第五步是浏览器解析渲染。浏览器拿到响应后如果是 HTML就开始解析构建 DOM 树同时发现 CSS 和 JS 资源会再发请求去取CSS 构建 CSSOM 树二者合并成渲染树经过布局和绘制最终显示在你眼前。如果是 JSON 数据接口就会交给前端的 JavaScript 去动态更新页面——这就是 SpA 模式下的典型渲染路径。3.2 HTTP 无状态与 Session/Cookie 的补救B/S 架构里有一个最让新手困惑的“Bug 级设定”——HTTP 协议天生是无状态的。什么叫无状态就是服务器默认不记得“你是谁”。你第一次请求登录接口输入了正确的账号密码第二次请求查个人资料接口时服务器并不知道你刚刚登录过了。无状态是 HTTP 协议刻意设计的结果它让协议本身足够简单也让服务器不需要为每个请求保持上下文大大提升了对高并发的适应能力。但业务上我们又需要“记住登录状态”这个矛盾催生了 Session 和 Cookie 这对经典组合。Cookie 是浏览器端的小存储空间由服务器通过Set-Cookie响应头下发浏览器会自动保存并在后续请求中自动带上。Session 是服务器端存储的用户会话数据服务器给每个会话生成一个唯一标识Session ID把它放在 Cookie 里发给浏览器。下一次浏览器带着这个 Cookie 来请求服务器看到 Session ID就能在内存里或 Redis 里找到对应的会话数据于是“认出了”这个用户。这套方案在单体应用中非常顺滑但一旦系统演进到前后端分离、分布式部署阶段问题就来了。多个后端实例共享同一个分布式 Session 就需要额外方案比如把 Session 集中存储在 Redis 里而原生 App、小程序这种“没有 Cookie”的环境又催生了 Token 机制——登录后服务器签发一个 Token 给客户端客户端每次请求在请求头里带着 Token服务器验签确认身份。本质上 Token 的定位是替换 Session/Cookie 组合解决“多端、跨域、分布式”环境下的状态管理问题。3.3 数据怎么去、怎么回POST 请求体与 JSON很多人每天写接口调接口但对“数据协议”这件事本身没太上心。B/S 架构里浏览器与服务器交换数据最常用的格式就是 JSON——这也算是 Web 世界里的“普通话”。以一次典型的登录请求为例。前端收集到用户名和密码后JavaScript 通过fetch或axios把它们转成 JSON 字符串放进 POST 请求体请求头里声明Content-Type: application/json。服务器端接收到请求体后先按 UTF-8 解码字节流再用 JSON 解析器把字符串变成对象然后取出username和password字段去校验业务逻辑。处理结束后服务器返回响应时同样要把结果结构化成 JSON——比如{code: 0, msg: success, data: {userId: 123, token: xxx}}前端拿到 response 后再把它转换成 JavaScript 对象更新页面状态。这里有个容易踩的坑JSON 是个字符串协议它本身不认 JavaScript 对象。很多前端新手把对象直接塞进fetch的 body 里导致请求体格式不对后端解析不出数据。正确的姿势是先JSON.stringify(obj)序列化成字符串再发送。同理后端返回的数据也是字符串前端要用res.json()反序列化拿到对象。搞清楚“序列化”和“反序列化”这两个词你在联调接口时会少掉一半的头发。4. B/S 架构的工程实践从零搭一个可运行的应用4.1 技术选型思路别盲目追新适合就好纸上谈兵聊完原理下面进入“真刀真枪”的实践环节。B/S 架构落到工程上第一步就是技术选型。我这里不想盲目推荐“啥火选啥”更想给出一个我在实际项目中反复验证过的稳妥组合。后端我推荐 Spring Boot原因不是它最酷而是它最成熟、生态最全、网上能查到的问题解决方案最多。你踩过的坑十有八九有人踩过这就是选它的底气。Java 这门语言虽然啰嗦但在企业级开发里稳定性和可维护性确实是第一位的。前端我推荐 Vue 3因为它的学习曲线相对平缓、生态完整Vue Router、Pinia中文文档质量也高对国内开发者友好。数据库选 MySQL缓存选 Redis这俩是行业事实标准不解释。很多新人在选型时会陷入“选择恐惧症”用 Node.js 还是 Java用 Vue 还是 React用 PostgreSQL 还是 MySQL我的建议是——在你有足够判断力之前选最主流的、你身边人用最多的。技术栈的热度本身就是一个信息优势意味着社区资源丰富、招聘需求量大、坑都有前人填过。等你真正做过几个项目再根据自己的喜好和场景去换完全来得及。4.2 最小可运行示例登录接口的前后端打通下面我带你手写一个最简 B/S 应用——一个登录接口目的是让你对“前后端如何配合”有个直观体感。后端先建一个 Spring Boot 工程核心就是一个 ControllerRestController RequestMapping(/api) public class AuthController { PostMapping(/login) public Result login(RequestBody LoginRequest request) { // 模拟校验用户名密码 if (admin.equals(request.getUsername()) 123456.equals(request.getPassword())) { return Result.ok(登录成功); } return Result.error(用户名或密码错误); } } // 数据模型 class LoginRequest { private String username; private String password; // getter/setter }这段代码虽然简单但它是 B/S 架构中“业务层”的缩影——接收请求、解析参数、执行业务校验、返回标准化结果。在生产项目中你还要在此基础上加 Service 层做业务编排、Mapper 层访问数据库、异常处理、参数校验、日志记录等等。前端部分一个最小可用的登录页大概是这个流程async function login(username, password) { const res await fetch(/api/login, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ username, password }) }); const data await res.json(); if (data.code 0) { alert(登录成功); } else { alert(data.msg); } }注意到这里前端请求的路径是/api/login这就引出了一个 B/S 架构工程化的关键问题——前后端分离时的“跨域”。因为前端和后端经常跑在不同的端口前端 5173后端 8080浏览器同源策略会阻止它们直接通信。解决跨域的方式有好几种最省事的是在后端加个CrossOrigin注解或用 CorsFilter 配置允许来源更正规的是以后通过 Nginx 反向代理把请求转发给后端让浏览器看起来请求的是同一个域名从根上规避跨域。4.3 部署上线从本地开发到服务器发布本地跑通是第一步把应用真正部署到服务器才是 B/S 架构“S 端”价值的体现。我以前带新人时经常遇到一种情况——本地一切正常一上服务器全是问题。这里我把部署的完整链路梳理给你。后端部署的常规姿势是用 Maven 打包成 jar 包通过 SSH 上传到服务器然后用java -jar app.jar启动。既然用了 Spring Boot 内置的 Tomcat就不需要额外装 Tomcat 了。但更规范的做法是让 Nginx 站在前面做反向代理监听 80/443 端口把/api/开头的请求转发给 127.0.0.1:8080 上的 Spring Boot把静态文件前端构建产物直接返回给浏览器。一个最简 Nginx 配置文件长这样server { listen 80; server_name www.example.com; root /usr/share/nginx/html; # 前端构建后的静态文件目录 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里值得多说一句proxy_pass后面的路径问题——如果加斜杠比如http://127.0.0.1:8080/那么请求路径开头的/api会被去掉再转发如果不加斜杠原路径会被完整转发。这个细节经常导致后端路由对不上 404排错时你第一个要排查的就是这里。前端部署相对简单构建工具会生成一堆静态文件HTML、CSS、JS你把这堆文件放到 Nginx 的 html 目录就行。但有个大坑是前端路由——如果你的 Vue 应用是 history 路由模式在浏览器里直接刷新/login页面时 Nginx 会去查/login.html查不到就返回 404。解决方案是在 Nginx 里加一个 try_files 配置把所有请求都重定向到 index.html让前端的路由接管location / { try_files $uri $uri/ /index.html; }这个坑我见过无数次包括一些老项目线上跑得好好的一旦有人直接刷新某个子页面就 404一片哀嚎——十有八九就是忘了配 try_files。4.4 架构演进从单体到集群的必经之路一个 B/S 系统刚上线规模不大时服务器就是一台但业务量起来之后阻塞点会一个个冒出来。首先是数据库压力——所有读写都压在一台 MySQL 上连接数开始不够用慢查询变多。解决方案是从加 Redis 缓存开始热点数据先查缓存再去看代码里那些 N1 查询。接着是后端服务压力——单实例 Tomcat 撑不住并发。这时你会引入负载均衡让 Nginx 把请求分发到多个后端实例上。实例多了原来放在单机 Session 里的登录状态就“串台”了你被负载到 B 实例但当时的登录状态存在 A 实例。解决方案就是前面提到的把 Session 集中到 Redis 共享存储或者干脆改造为 JWT Token 方案让服务器变成“无状态”每个请求都自包含认证信息。再往后单体应用本身也膨胀到难以维护就牵涉到微服务拆分。这步要谨慎微服务的分布式事务、服务发现、链路追踪都是大工程小团队轻易别碰。我的经验是——架构永远朝着解决问题的方向演进而不是朝着“看起来高级”的方向演进。你加一个组件就要多承担一份运维成本。能用单体和集群解决的问题不要着急上微服务。5. 实战中必须注意的坑与优化点5.1 安全三件套XSS、CSRF、SQL 注入B/S 架构天然是一个“开放系统”任何人任何地方都能通过浏览器访问你的服务这意味着安全问题比传统 C/S 架构更严峻。开发时至少要把这三个基础安全问题刻在脑子里。XSS跨站脚本攻击的思路是攻击者在你的页面里注入恶意 JavaScript。比如你在评论区输入scriptalert(偷你Cookie)/script如果后端不处理直接存储并在其他用户页面渲染出来这段脚本就会在其他用户的浏览器里执行。防范的核心是“永远不要相信用户输入”——前端展示时对内容做转义后端对输入做校验和过滤。开发时养成一个好习惯写模板或组件时凡是要渲染用户提供的内容默认当作不可信文本处理不要直接用v-html或dangerouslySetInnerHTML这类危险接口。CSRF跨站请求伪造的思路更“阴”攻击者诱导用户在自己的浏览器里携带合法 Cookie 去请求你的接口。比如用户登录了你的银行系统某天他打开一个恶意网页里面有个隐藏表单自动提交POST /api/transfer由于浏览器会自动带上银行站点的 Cookie服务器就会认为这个请求是用户本人发起的。CSRF 的防范思路是——不在 Cookie 里做身份验证而是在请求里带上自定义 Token 头或者后端校验请求的来源 Referer 是否可信。现在的开发实践中前后端分离后多用 Token 鉴权CSRF 风险已经显著降低但老的 CookieSession 模式下一定得防。SQL 注入则是把攻击者的输入直接拼接进 SQL 语句导致的。最经典的示例是登录框里输入 OR 11直接绕过密码校验。防范核心就一句话——永远使用参数化查询PreparedStatement不要手拼 SQL 字符串。好的 ORM 框架MyBatis-Plus、Hibernate天然帮你规避了这个问题但 MyBatis 里写${}这种直接拼接的语法仍然要非常小心。5.2 性能优化从缓存到 CDN再到大查询治理B/S 架构的性能瓶颈常出现在三个位置——网络链路、服务器计算、数据库读写。对应有不同的优化手段。网络链路层面最有效的招是上 CDN。浏览器访问服务器时物理距离决定了延迟跨省的访问总比同城的慢。CDN 的原理是“就近分发”把静态资源图片、CSS、JS缓存到离用户最近的边缘节点让用户从几百公里外的节点下载资源。这个优化对前端的静态资源加载效果立竿见影。部署时注意给 Nginx 配好缓存响应头让浏览器也充分利用本地缓存location ~* \.(js|css|png|jpg)$ { expires 7d; add_header Cache-Control public; }服务器计算层面先看后端代码里有没有明显浪费。比如循环里查数据库N1 问题、重复创建对象、未加索引的大表查询。很多人一上来就堆 Redis、加机器忽视了业务代码本身的问题。我见过一个案例一个报表接口耗时 5 秒排查后发现问题出在循环里做了 200 次数据库查询加了一个批量查询接口后耗时直接降到 200 毫秒——有时候性能优化靠的不是“加资源”而是“减浪费”。数据库层面最核心的武器是索引和缓存。Redis 缓存的热点数据我们要重点盯住缓存穿透、缓存击穿、缓存雪崩这三个“高并发三兄弟”——穿透指查一个不存在的数据每次都打到数据库可以用布隆过滤器挡住击穿指一个热点 key 过期瞬间大量请求打到数据库可以加互斥锁重建缓存雪崩指大量 key 同时过期或 Redis 宕机导致大规模请求打到数据库解决方式是过期时间加随机值、做 Redis 集群高可用。这三个名词面试常考实际开发里也真的会碰到。5.3 会话管理分布式 Session 与 Token 的取舍前面已经提到过 Session 在分布式环境下的痛点这里再深入一点展开。当你有两台后端实例时最简单粗暴的解决办法是 Nginx 的 ip_hash 策略——让同一个 IP 的请求固定打到同一台实例这样 Session 天然不会串。但它的缺点是用户切换网络 IP 就被重新分配而且某个实例宕机时挂在这上面的用户会话全部丢失容灾性很差。更好的方案是 Session 集中存储。把 Session 从每台 Tomcat 的内存里挪到 Redis 里所有实例都从公用的 Redis 读写 Session。这样任何一个实例挂了用户的登录状态还在 Redis 里请求被转发到其他实例照样能识别身份。Spring Session 这个框架就是干这件事的引入依赖改两行配置就能完成切换。TokenJWT方案则走的是另一条路线——服务器彻底不存会话状态。登录成功后服务器把用户信息签名进一个字符串JWT客户端保存这个 Token每次请求带着它服务器验签通过就认为身份合法。这个方案天然支持分布式和前后端多端复用扩展性最好。但要注意 JWT 有不可忽视的局限性它不能主动失效除非你加黑名单机制Token 一旦泄露在有效期内任何人都能冒充用户。所以用 JWT 时尽量设置较短的过期时间配合 Refresh Token 刷新机制并且必须用 HTTPS 传输防止中间人截获。6. 常见问题与排查心得6.1 404、500、超时背后的排查思路开发调试时最常见的三类报错——404、500、超时很多人一看到就开始慌这里给你一套系统的排查顺序。404 说的是“资源找不到”先分清是后端接口 404 还是前端资源 404。如果调用后端接口报 404依次检查请求路径拼写是否正确、Nginx 转发规则是否配置正确重点看 proxy_pass 带不带斜杠、后端 Controller 的 RequestMapping 路径是否匹配、服务是否真的启动成功。如果是前端刷新子页面报 404就回到我们前面说的 try_files 配置问题。500 是“服务器内部错误”说明请求到达了后端但处理过程抛异常了。排查思路是立刻看后端日志——标准姿势是把日志输出到独立的文件里Logback 配置然后tail -f跟随查看。常见的 500 原因有空指针异常、数据库连接失败、参数类型转换错误、业务代码抛了未捕获异常。这里我要强调一个习惯不要只盯着控制台报错信息要看完整的堆栈跟踪stack trace从最底层那个Caused by开始找根因。超时要分连接阶段超时和读取阶段超时。连不上多半是网络问题、防火墙挡了端口、服务没启动、负载过高连上了但读取超时多半是后端处理太慢——可能是有慢查询、第三方接口响应慢、业务逻辑死循环。排查时可以用curl -v带上--connect-timeout和--max-time参数精准定位超时发生阶段。6.2 跨域问题同源策略与实战解法跨域错误是前后端分离开发里最“高频暴击”的问题之一。浏览器报错信息是Access-Control-Allow-Origin相关的新手看到就蒙了——明明接口能访问为什么浏览器拦截数据这里先要理解“同源策略”是浏览器的一种保护机制它规定页面的 JavaScript 只能访问“同协议 同域名 同端口”的资源。只要有一项不同就算“跨域”请求能不能发出去不一定但响应一定被浏览器拦截。实战中解决跨域有几种方案我按推荐程度介绍。最推荐Nginx 反向代理统一域名——开发时前端把请求发到/api让 Nginx 转发到后端由于浏览器看到的是同一个域名没有跨域问题。生产环境本来就该这样搞也算是最“正经”的解法。第二种后端开启 CORS——后端在响应头里加上Access-Control-Allow-Origin: 你的前端域名明确告诉浏览器“这个来源可以访问”。注意这里要配置成允许具体的前端地址而不是无脑*否则 Cookie 和 Token 的跨域携带会有问题。第三种前端 Jsonp 方案它只支持 GET 请求且安全性较差属于古董技术除非你在维护老系统否则不建议用。6.3 响应慢的定位方法一张清单走天下接口响应慢几乎每个开发者都会遇到。我有一套自己的排查“清单”按顺序执行大多数问题十五分钟内能定位到范围。第一步先确认是全部接口慢还是个别接口慢。用浏览器开发者工具F12Network 面板跑一遍看耗时分布——Waiting for server response时间长说明后端处理慢Content Download时间长说明响应体太大或网络带宽紧张。第二步针对单个慢接口后端加打印耗时日志。最简单的方式是对核心方法做耗时统计或者用StopWatch精确测量。查问题先从大的耗时块找起校验参数了多久、查数据库了多久、调第三方接口了多久、组装结果了多久。第三步如果耗在数据库。把慢接口里执行的 SQL 拿出来用EXPLAIN分析执行计划看是否命中索引、是否全表扫描、扫描行数是多少。优化索引后如果还是慢再看是否要从 Redis 加缓存或改成分页、异步、批量处理。第四步如果耗在第三方接口。先看是否能缓存第三方响应结果、是否能调连接超时时间避免一直等死、是否能异步化发完请求立刻返回后台慢慢调。第三方依赖永远是性能黑洞能绕开就绕开。这套清单用熟之后你会发现响应慢的问题基本不是“玄学”而是“有迹可循的技术债”。可能问题就是少建了一个索引或是一次不该做的同步调用只要按清单耐心查总能找到根因。最后再分享一个这几年带团队的经验心得。新人问过我最多的一个问题是“老师B/S 架构我到底要学多深”我的回答是——不要只满足于会写接口、会调 jQuery而是要能闭上眼在脑子里画出那条完整的数据链路用户点下按钮的那一瞬间请求怎么从浏览器出发经过 DNS、经过 TCP、经过 Nginx、打到后端、查到数据库、再原路返回最后渲染在屏幕上。画得出这条链路很多问题你根本不用查资料就能推出来画不出这条链路你永远只能靠试错来解决问题。B/S 架构这东西说简单是真简单一个浏览器一个服务器而已说深也是真深链路里每一环都够你钻研很长时间。希望这篇长文能帮你把这条链路真正打通少走些我当年走过的弯路。