Web 应用架构
一、架构设计
(一) 分层架构
Web 应用主要采用层级架构的设计,整体可以分为三层:
- 表现层(Presentation Layer):处理用户交互,使用 HTML/CSS/JavaScript 构建界面。
- 业务逻辑层(Business Layer):执行核心业务规则,如数据验证和事务处理。
- 数据访问层(Data Layer):负责数据库操作,常用 ORM 框架实现。
层次架构的优点为:
- 分离关注点:每一层都有其特定的职责,这有助于开发人员专注于各自的任务。
- 可维护性:由于层与层之间的耦合度低,修改一层不会影响其他层,这使得维护和升级变得更加容易。
- 可扩展性:可以根据需要独立扩展每一层,以应对不同的性能和功能需求。
- 可测试性:每一层可以独立进行单元测试,这有助于提高代码质量和发现潜在问题。
层次架构的局限为:
- 性能损耗:跨层调用可能产生性能损耗(约 5%-10% 请求延迟)。
- 污水池反模式:请求简单的穿过几个层,每层里面基本没有或很少做业务逻辑。这种层次隔离失效的情况,容易导致业务修改时出现遗漏和不一致的问题。解决办法是层次开发,即允许部分数据的处理直达核心层,而不用层层穿越。
(二) 前后端分离
在分层架构中,表现层属于前端处理,业务逻辑层和数据访问层属于后端处理。通过前后端分离的模式,解耦前端与后端开发,从而提升开发效率、系统可维护性和用户体验。
- 前端:主要由 HTML、CSS 和 JavaScript 构成。通常使用前端框架(React、Vue.js、Angular)进行开发。
- 后端:可以选择不同的编程语言和框架来实现,如 Node.js、Django(Python)、Spring Boot(Java)等。
- 通信:
- 数据交互:多数场景使用 JSON,高性能场景使用 Protobuf
- 接口风格:Restful 风格
- 跨域请求:CORS 跨域访问
二、前端技术
前端表现层通常有 MVC/MVP/MVVM 三种架构:
模式 | MVC | MVP | MVVP |
---|---|---|---|
核心组件 | - 模型(Model):数据存储与业务逻辑 - 视图(View):用户界面展示 - 控制器(Controller):处理用户输入,协调模型与视图 |
- 模型(Model):数据存储与业务逻辑 - 视图(View):用户界面交互接口 - 呈现器(Presenter):处理逻辑,控制视图更新 |
- 模型(Model):数据存储与业务逻辑 - 视图(View):绑定数据的 UI - 视图模型(ViewModel):数据转换与双向绑定 |
数据流 | 单向为主(存在双向例外) | 双向(通过 Presenter 中介) | 双向(自动绑定) |
数据交互 | 视图可直接调用控制器,也可能直接访问模型 | 视图通过接口与呈现器通信,不直接访问模型 | 视图通过数据绑定与视图模型交互,无直接逻辑调用 |
耦合度 | 高(View 与 Controller 耦合) | 低(通过接口解耦) | 最低(数据绑定解耦) |
MVC 数据流
- 理论上是 单向数据流(用户→View→Controller→Model→View),实际上常出现 Controller 直接操作 View 的反模式
graph LR A[用户] -->| 操作 | B(View) B -->| 事件通知 | C(Controller) C -->| 更新 | D(Model) D -->| 推送新数据 | B C -.->| 可能直接操作 | B
- 理论上是 单向数据流(用户→View→Controller→Model→View),实际上常出现 Controller 直接操作 View 的反模式
MVP 数据流
- 双向数据流,呈现器是视图与模型之间的唯一中介
graph LR A[用户] -->| 操作 | B(View 接口) B -->| 委托事件 | C(Presenter) C -->| 调用 | D(Model) D -->| 返回数据 | C C -->| 通过接口 | B B -.->| 查询数据 | C
- 双向数据流,呈现器是视图与模型之间的唯一中介
MVVM 数据流
- 双向数据流,通过绑定视图和视图模型实现自动同步,无需显式调用接口完成视图更新
graph LR A[用户] -->| 操作 | B(View) B -->| 自动绑定 | C(ViewModel) C -->| 更新数据 | D(Model) D -->| 数据变更 | C C -->| 数据驱动 | B
- 双向数据流,通过绑定视图和视图模型实现自动同步,无需显式调用接口完成视图更新
三、后端技术
1. RESTful
REST 全称是 Representational State Transfer,中文意思是 表述性状态转移。如果一个架构符合 REST 的约束条件和原则(如下表所示),我们就称之为 RESTful 风格。
概念 | 解释 |
---|---|
资源标识 | 资源是任何有被引用必要的事物,包括实体和抽象概念。使用 URI 作为资源唯一标识,可看作资源地址或名称。 |
操作接口 | 使用标准 HTTP 方法访问资源。GET 表示获取资源;POST 表示创建资源;PUT 表示全局更新资源;DELETE 表示删除资源;PATCH 表示局部更新资源。 |
资源表述 | 客户端获取资源表述,通常采用 json、xml 等格式描述资源。 |
资源链接 | 利用超媒体概念,在表述格式中添加相关操作的链接,驱动客户端的状态转移。 |
无状态 | 服务端不保存客户端状态,客户端的每次请求需要包含完整的上下文信息。 |
2. CORS 跨域访问
为了保护用户的安全和隐私,浏览器默认使用 同源策略,即要求协议、域名、端口三者完全相同,才能正常访问。
解决跨域问题,常见的方案有:
- CORS(跨域资源共享):在服务器端设置响应头部,允许指定的域名访问资源。
- JSONP(JSON with Padding):通过在页面中动态添加 \
- 代理服务器:在服务器端设置一个代理服务器,将请求代理转发到目标服务器,绕过浏览器的同源策略。
对比项 | CORS | JSONP | 代理服务器 |
---|---|---|---|
支持方法 | 所有方法 | GET 方法 | 所有方法 |
安全性 | 高 | 低 | 中 |
兼容性 | 现代浏览器 | 老旧浏览器 | 所有浏览器 |
适用场景 | API 接口、单点登录 | 第三方数据加载 | 无法修改服务端时 |
CORS 通过在 HTTP(s)请求和响应中使用特定的头部字段来实现跨域资源共享,具体来说,CORS 分为两种类型的请求处理方式:简单请求和预检请求。
- 简单请求:对于某些简单的 HTTP 请求(如 GET、POST 请求,且不包含自定义头部),浏览器会直接发送请求,并在响应中检查 CORS 头部。
- 预检请求:对于复杂请求(如使用 PUT、DELETE 方法,或包含自定义头部),浏览器会首先发送一个 OPTIONS 请求,称为预检请求(Preflight Request),以确定服务器是否允许实际请求。如果预检请求通过,浏览器会继续发送实际请求。
服务器可以通过设置响应头部来细粒度配置 CORS:
响应头字段 | 作用 | 示例 |
---|---|---|
Access-Control-Allow-Origin | 允许的源(* 表示允许所有) | * |
Access-Control-Allow-Methods | 允许的 HTTP 方法 | GET, POST, PUT |
Access-Control-Allow-Headers | 允许的自定义请求头 | Authorization, Content-Type |
Access-Control-Max-Age | 预检请求缓存时间(秒) | 3600 |
Access-Control-Allow-Credentials | 是否允许携带 Cookie(需配合 withCredentials) | true |
四、性能
1. 负载均衡
负载均衡(Load Balance)是将网络流量或计算任务动态分配到多台服务器 / 计算节点的技术,旨在优化资源利用、提升系统吞吐量并保障服务高可用性。其优势在于:
- 消除单点故障,提升系统容错能力
- 实现水平扩展,突破单机性能瓶颈
- 降低响应延迟,改善用户体验
负载均衡的实现方案可以分软硬件两大类:
- 硬件负载均衡:使用专用硬件设备(如 F5 BIG-IP),性能卓越但成本高昂
- 软件负载均衡:通过软件实现,灵活经济,是市场主流技术
对比维度 | F5 BIG-IP | DNS 负载均衡 | LVS | Nginx | HAProxy | Istio 服务网格 |
---|---|---|---|---|---|---|
原理 | 专用硬件处理全协议流量,集成 SSL 卸载与安全防护 | 域名解析返回多个 IP 地址,客户端自动轮询访问 | Linux 内核 IPVS 模块实现四层转发 | 反向代理拦截 HTTP 请求,通过 upstream 模块分配流量 | 基于状态机的连接处理,精细化 TCP 会话管理 | Envoy 代理 Sidecar 模式拦截流量,控制平面动态下发路由规则 |
性能 | 百万级 QPS,支持全协议 | 无额外延迟(受 TTL 缓存限制) | 四层转发性能接近硬件 | 万级 QPS | 高并发 TCP 吞吐 | 依赖 Envoy 代理性能 |
稳定性 | 冗余设计,金融级可靠性 | 中(依赖 DNS 冗余设计) | 热备方案支持高可用 | 高可用需配合 Keepalived | 动态健康检查 | 基于 K8s 自动容错 |
配置难度 | 低(GUI 界面) | 低(多 A 记录配置) | 高(需内核调优) | 低(配置文件) | 中(策略复杂) | 高(YAML 声明式配置) |
成本 | 高昂(20 万 +) | 低(仅 DNS 服务费用) | 免费(开源) | 免费(开源) | 免费(开源) | 中等(运维复杂度高) |
支持层级 | 四层 / 七层 | 四层 | 四层 | 七层为主 | 四层 / 七层 | 七层 |
典型场景 | 金融核心交易系统 | 全局流量分发(如 CDN) | 大规模 TCP 流量分发 | Web 服务器 /API 网关 | 数据库集群负载 | 微服务流量治理 |
负载均衡算法 可分为静态算法和动态算法:
- 静态分配策略
- 轮询算法:依次分配请求,适合服务器性能均等场景
- 加权轮询:根据服务器处理能力设置权重比例(如 4 核服务器权重为 2 核的 2 倍)
- 源地址哈希:基于客户端 IP 固定分配服务器,保持会话连续性
- 动态调整策略
- 最少连接数:优先选择当前连接数最少的节点,适用于长连接服务(如 FTP)
- 响应时间优先:通过 ICMP 探测选择延迟最低节点,改善实时交互体验
- 自适应混合算法:结合 CPU 负载、内存使用率等指标动态计算权重
graph TD A[新请求] --> B{算法选择} B -->| 常规请求 | C[轮询 / 加权轮询] B -->| 会话保持需求 | D[源地址哈希] B -->| 实时性要求高 | E[响应时间优先] B -->| 复杂业务场景 | F[自适应混合算法]
2. 读写分离
读写分离的实现策略:
- 数据库层:MySQL 主从复制(延迟 <100ms)
- 原理:MySQL 主从复制通过二进制日志(binlog)实现数据同步。主库将事务写入 binlog,从库通过 IO 线程拉取日志,并由 SQL 线程重放日志以应用数据变更。
- 中间件:MyCAT/ShardingSphere 实现自动路由
- MyCAT:按哈希、范围等规则将数据分散到多个库表,通过 schema.xml 定义逻辑表与物理节点映射。配置 balance=1 实现读请求随机分发到从库,写请求强制路由至主库。
- ShardingSphere:通过 Sharding-JDBC 在 JDBC 层拦截 SQL,自动改写为分片语句。
- 缓存层:Redis 集群 + 读写分离(读性能提升 10 倍)
- 原理:采用一致性哈希将 Key 分配到 16384 个槽位,每个节点管理部分槽位。通过 READONLY 命令标记从节点为只读,代理层将 80% 读请求分发至从节点。
读写分离的数据一致性保障:
- 半同步复制:主库提交事务前需至少一个从库确认接收 binlog 并写入中继日志,主库收到从库 ACK 后再正式提交事务。
- 数据库中间件:所有读写走中间件,记录写操作的 key,主从同步时间窗口内,读请求路由到主库,窗口期过了再路由到从库。
- 缓存记录:写操作时记录 key 到 cache,并设置超时时间,再修改主库。读操作时先查 cache,命中则路由到主库,未命中则路由到从库。
五、安全性
1. JWT 认证
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间以 JSON 对象形式安全传递信息。其核心特性是 无状态性,即服务器无需存储用户会话信息,仅通过令牌本身的签名和内容验证用户身份。
JWT 由三部分构成,用点号(.)分隔:Header.Payload.Signature
- Header:包含令牌类型和签名算法。
- Payload:存储用户身份信息和声明。通过 Base64 编码,但未加密,不可存放密码等敏感信息。
- Signature:通过加密算法对 Base64(Header).Base64(Payload)和密钥(secret_key)计算生成,用于防篡改。
服务端验证步骤为:
- 解析 JWT,提取 Header 和 Payload。
- 用相同算法和密钥重新生成签名,比对客户端传来的 Signature,若一致则合法。
- 对合法令牌验证声明(如检查 exp 是否过期、iss 是否合法)是否有效,若有效则通过。
2. CSRF 攻击
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种利用用户已认证身份发起非授权操作的攻击方式。其核心在于浏览器自动携带用户凭证(如 Cookie),而服务器无法区分请求是否由用户主动发起。
CSRF 攻击流程:
- 用户登录受信任网站(如网银),服务器返回身份凭证(Cookie) 。
- 用户访问恶意网站 B,该网站嵌入指向网站 A 的恶意请求(如转账 API)。
- 浏览器自动携带网站 A 的 Cookie,向网站 A 发送请求,服务器误认为是合法操作并执行。
CSRF 防御措施:
- 验证 CSRF Token:服务器生成随机 Token 嵌入表单或请求头,验证请求是否携带有效 Token。
- 设置 SameSite Cookie 属性:Strict 模式完全禁止跨域携带 Cookie,Lax 模式仅允许安全方法(如 GET)且导航类请求携带。
- 检查 Referer/Origin 头部:验证请求来源域名,拦截非信任域名的请求。
- 双重认证机制:敏感操作(如转账)需二次验证,如短信验证码、人脸识别。