返回文章列表

客户端、前端与用户体验系统:客户端也是系统设计的一部分

把客户端纳入系统设计视角,分析多端一致性、BFF、弱网重试、Feature Flag、RUM、Token 管理和用户体验链路。

第三篇:互联网系统的核心路径

互联网系统的核心路径,不是某一段代码,也不是某一个数据库查询,而是一条从用户动作开始的链路:

用户动作

客户端 / 前端

DNS / CDN / WAF / API Gateway

API 契约

同步服务调用

数据库 / 缓存 / 第三方服务

异步消息 / 事件流 / 后台任务

通知、搜索、风控、推荐、审计、数据分析

小系统里,请求路径往往很短:浏览器发请求,后端处理,数据库落盘,返回结果。链路短、依赖少、流量低,即使设计粗糙,也不一定马上暴露问题。

大规模系统则不同。请求会穿过客户端缓存、边缘节点、安全设备、网关、多个服务、缓存、数据库、消息队列、第三方 API、异步消费者和数据平台。任何一个环节的延迟、失败、重试、配置错误、证书过期、缓存污染、接口变更,都可能沿链路放大成用户可见故障。

本篇关注五个问题:

  1. 请求如何发起?
  2. 请求如何进入系统?
  3. 系统之间如何定义契约?
  4. 同步调用如何避免雪崩?
  5. 异步事件如何可靠流转?

第 8 章:客户端、前端与用户体验系统

本章的问题链

先看原始问题:很多系统设计只盯着后端服务,却忘了用户真正接触的是客户端。弱网、版本碎片、缓存、权限过期、首屏加载、埋点丢失和灰度开关,都会让一个后端正常的系统在用户眼里不可用。

为了解决这个问题,本章把客户端和前端放回系统边界内,用 BFF、端上缓存、Feature Flag、RUM、Token 管理、兼容策略和体验指标来治理真实用户体验。

但这不是终点:客户端发出的请求并不会直接进入业务服务。新的问题是:请求到达系统核心之前,还要经过 DNS、CDN、WAF、API Gateway 等入口层,这些层同样会决定体验和可靠性。

所以本章会按“问题 -> 机制 -> 新问题”的顺序展开:先把眼前的工程压力说清楚,再看对应机制解决了什么,最后讨论它留下的边界和下一步。

客户端、前端与用户体验系统:客户端也是系统设计的一部分 flow 1

1. 本章解决什么问题

很多后端工程师第一次做系统设计时,习惯从“服务端架构图”开始:API 服务、数据库、缓存、消息队列、对象存储。这个视角并不完整。真实的用户体验,从用户点击按钮之前就已经开始了:DNS 解析、资源加载、首屏渲染、登录态恢复、本地缓存读取、埋点采集、风控信号生成、弱网重试、失败提示、灰度开关判断,都可能影响最终体验。

客户端不是“展示层”。现代客户端承担了越来越多系统职责:

  • 多端体验一致性。
  • 本地缓存和离线能力。
  • 弱网下的请求调度。
  • Token 生命周期管理。
  • Feature Flag 和 A/B 实验。
  • 风控信号采集。
  • 埋点和真实用户监控。
  • 降级页面和兜底交互。
  • API 聚合与数据预取。

尤其在移动端、小程序和桌面端场景里,客户端发布节奏慢、版本碎片多、网络环境不稳定、用户设备性能差异大。后端一次接口变更,可能在数月内持续影响老版本客户端。一个没有幂等设计的“提交订单”按钮,在弱网环境下可能被用户连点多次;一个没有兼容窗口的响应字段删除,可能让某个旧 App 版本直接崩溃;一个配置错误的 Feature Flag,可能让客户端把流量打到尚未准备好的后端接口。

因此,现代系统设计必须从用户体验路径开始,而不是只从服务端开始。


2. 问题背景:小系统里为什么不明显

小系统通常有几个特点:

单一 Web 端

同一个后端 API

同一个数据库

此时很多问题不明显:

  • 客户端版本少,接口变更可以同步上线。
  • 用户网络环境相对可控,失败后刷新页面即可。
  • 缓存层次少,不容易出现多级缓存不一致。
  • 灰度范围小,即使出错也容易人工干预。
  • 埋点不完整,也不影响基本业务判断。
  • 安全模型简单,Token 管理和风控信号不复杂。

但系统增长后,客户端会变成一个复杂生态:

Web
Mobile iOS
Mobile Android
Mini Program
Desktop App
Partner SDK
Open API Client
Internal Admin Console

不同客户端有不同发布节奏、运行环境、网络条件和安全风险。后端不能假设“所有调用方都按最新协议访问”。客户端也不能假设“每次请求都能成功、每个响应都可信、每个功能都必须完整可用”。


3. 大规模系统中的故障、成本和组织问题

客户端复杂度会以几种方式变成生产问题。

第一,接口兼容性问题会长期存在。Web 可以快速发布,但移动端 App 可能需要经过应用商店审核,企业桌面端可能依赖客户手动升级,小程序也有平台审核和缓存延迟。后端删除字段、修改枚举语义、改变错误码,都可能影响老版本客户端。

第二,弱网会放大后端设计缺陷。用户在地铁、电梯、跨境网络、会议 Wi-Fi 下提交请求时,可能遇到请求超时、响应丢失、重复发送。若服务端缺少幂等键,请求重试可能导致重复下单、重复扣费、重复发券。

第三,客户端缓存会制造一致性问题。商品价格、库存、优惠资格、会员权益、风控状态,很多数据不能只靠客户端缓存判断。客户端可以缓存展示数据,但最终决策必须由服务端确认。

第四,灰度和实验会改变流量形态。一个只对 1% 用户开放的新入口,可能因为客户端配置错误、实验分桶错误或缓存策略错误,瞬间放大成大面积流量。Feature Flag 降低了发布风险,也引入了配置债务、状态组合爆炸和排查难度。

第五,埋点不是事后补充,而是反馈链路的一部分。没有客户端埋点,就很难知道用户是否看到页面、是否点击按钮、是否在弱网中失败、是否因为前端错误而流失。Web Vitals 把 LCP、INP、CLS 作为核心体验指标,并建议用真实用户监控观测用户实际体验;TTFB 和 FCP 也常用于定位服务端响应、渲染阻塞等问题。(web.dev)


4. 核心概念

4.1 多端一致性

多端一致性不是要求所有客户端界面完全一样,而是要求核心业务语义一致。例如:

  • 订单状态含义一致。
  • 价格展示和支付金额一致。
  • 权限判断一致。
  • 风控拦截结果一致。
  • 错误提示可理解。
  • 关键操作可追踪。

常见做法是将业务决策放在服务端,客户端只做展示和交互。客户端可以缓存,但不能独自决定“是否允许支付”“是否扣减库存”“是否发放权益”。

4.2 BFF:Backend for Frontend

BFF 是为特定前端或客户端场景提供后端适配层的架构模式。它通常解决几个问题:

  • 聚合多个后端服务,减少客户端多次请求。
  • 针对移动端裁剪字段,减少流量。
  • 屏蔽内部服务模型变化。
  • 为不同端提供不同数据形态。
  • 下沉部分体验逻辑,例如首屏数据拼装。

但 BFF 也会带来代价:

  • 可能复制业务逻辑。
  • 多端 BFF 容易语义不一致。
  • BFF 可能变成“前端团队的后端单体”。
  • 内部 API 变更仍需治理。
  • 观测链路更长。

BFF 适合“不同客户端体验差异大、后端领域服务较稳定”的场景,不适合把所有业务规则都搬到适配层。

4.3 离线缓存与弱网处理

弱网不是简单的“请求失败”。它可能表现为:

  • DNS 慢。
  • TLS 握手慢。
  • 首包慢。
  • 上行慢。
  • 下行慢。
  • 请求已到达服务端,但响应丢失。
  • 客户端认为失败,服务端已经成功。
  • 用户反复点击按钮。
  • App 切后台导致请求中断。

弱网设计要区分可重试操作不可随意重试操作。查看商品详情可以重试;提交订单、支付确认、提现申请必须配合幂等键、状态查询和用户提示。

4.4 客户端 Feature Flag

客户端 Feature Flag 用于控制功能开关、实验分流、灰度发布和紧急降级。它的价值在于可以把“代码发布”和“功能开放”解耦。

但客户端 Flag 有特殊风险:

  • 配置缓存导致关闭不及时。
  • 老版本客户端不识别新 Flag。
  • 多个 Flag 组合形成未测试状态。
  • Flag 判断依赖用户属性,可能触发隐私和合规问题。
  • 客户端 Flag 容易被逆向,不能作为安全边界。

4.5 客户端埋点与 RUM

客户端埋点至少包括三类:

类型关注点例子
行为埋点用户做了什么点击、曝光、搜索、下单
性能埋点用户体验如何首屏时间、接口耗时、资源加载
错误埋点哪里失败了JS 错误、App 崩溃、API 失败

真实用户监控,也就是 RUM,能帮助团队看到真实设备、真实网络、真实地理位置下的体验,而不是只看实验室环境。

4.6 客户端安全与 Token 管理

客户端是低信任环境。Token、设备 ID、本地缓存、离线数据、埋点数据,都可能被窃取或篡改。

基本原则:

  • 不在客户端保存长期高权限密钥。
  • Access Token 生命周期要短。
  • Refresh Token 要有轮换和吊销机制。
  • 关键操作需要服务端二次校验。
  • 客户端风控信号只能作为辅助,不可作为唯一依据。
  • 本地缓存敏感信息要最小化。
  • 日志和崩溃上报不能携带明文敏感数据。

5. 典型架构方案

5.1 直接 API 模式

Client

API Gateway

Backend Service

适合早期系统。优点是简单、链路短、排查容易。缺点是客户端可能直接感知后端领域模型,后端接口变化会影响多端。

5.2 BFF 模式

Web App ─────→ Web BFF ───┐
Mobile App ──→ Mobile BFF ├──→ Domain Services
Mini App ────→ Mini BFF ──┘

适合多端体验差异明显的系统。BFF 聚合数据、裁剪字段、处理端侧适配。关键是避免 BFF 承载核心业务决策。

5.3 配置中心 + Feature Flag 模式

Client
  ↓ 拉取配置
Feature Flag Service

Client 根据配置展示功能、选择接口、触发实验

适合灰度和实验。但需要:

  • 配置版本。
  • 默认值。
  • 回滚策略。
  • 客户端缓存 TTL。
  • 审计日志。
  • 灰度范围控制。
  • 紧急全局关闭能力。

5.4 离线优先模式

Client UI

Local Store
  ↓ 同步队列
Sync Engine

Backend API

适合笔记、表单、协同办公、现场作业等场景。核心难点是冲突解决、重放、幂等、权限变化和删除同步。


6. 关键权衡

设计问题方案 A方案 B权衡
多端接口共用 API各端 BFF共用简单但难适配;BFF 灵活但治理成本高
缓存策略客户端强缓存每次服务端确认强缓存体验好但一致性弱;实时确认准确但慢
弱网重试客户端自动重试用户手动重试自动体验好但需幂等;手动安全但体验差
Feature Flag客户端判断服务端判断客户端响应快但不安全;服务端安全但多一次请求
埋点采集全量上报采样上报全量精确但成本高;采样便宜但可能漏小流量问题
首屏加载多接口并发BFF 聚合并发灵活但网络开销大;聚合快但后端耦合上升

7. 失败模式

7.1 老版本客户端被后端破坏

后端删除字段或修改枚举语义,老版本客户端未适配,出现崩溃或错误展示。

改进:

  • 字段只增不破坏。
  • 删除字段前观察调用方。
  • 服务端保留兼容窗口。
  • 客户端容忍未知枚举。
  • 用契约测试覆盖老版本请求。

7.2 弱网重复提交

用户点击“提交订单”,请求到达服务端,但响应丢失。客户端认为失败并重试,产生重复订单。

改进:

  • 客户端生成幂等键。
  • 服务端按用户、业务类型、幂等键去重。
  • 客户端失败后优先查询提交状态。
  • 按钮进入 pending 状态。
  • 对支付类操作展示明确状态:“正在确认,请勿重复支付”。

7.3 Feature Flag 配置污染

错误配置让新功能暴露给所有用户,后端容量不足导致雪崩。

改进:

  • Flag 变更需要审批和审计。
  • 灰度比例上限逐步提升。
  • 客户端有默认安全值。
  • 后端仍要校验能力开关。
  • 关键 Flag 纳入发布看板。

7.4 埋点缺失导致无法判断影响

用户反馈“页面打不开”,但团队只有后端 200 成功率,没有客户端失败率、首屏时间、JS 错误、App 崩溃数据。

改进:

  • 客户端埋点和服务端 Trace ID 打通。
  • 关键用户旅程设置漏斗指标。
  • 性能指标按版本、地区、网络类型、设备分组。
  • 发布前定义护栏指标。

8. 案例分析一:移动电商首页加载路径

8.1 用户是谁

  • 普通消费者。
  • 新用户、老用户、会员用户。
  • 不同地区、不同网络、不同设备性能用户。

8.2 核心动作是什么

用户打开 App,希望快速看到首页内容,并能进入商品详情、搜索、购物车和活动页。

8.3 请求路径

App 启动

读取本地配置 / 本地缓存

拉取 Feature Flag / 实验配置

请求 Mobile BFF 首页接口

BFF 聚合:
  ├─ 用户服务:会员状态
  ├─ 商品服务:推荐商品
  ├─ 营销服务:优惠活动
  ├─ 搜索/推荐:个性化排序
  └─ 风控服务:风险标签

返回首页数据

客户端渲染首屏

曝光埋点 / 性能埋点 / 错误埋点

8.4 ASCII 架构图

+-------------------+
|   Mobile App      |
| local cache       |
| feature flag      |
| RUM / analytics   |
+---------+---------+
          |
          v
+-------------------+        +--------------------+
|   Mobile BFF      |------->| Feature Flag Svc   |
| aggregation       |        +--------------------+
+----+-------+------+
     |       |
     |       +-----------------> Recommendation Svc
     |
     +-------------------------> Product Svc
     |
     +-------------------------> Promotion Svc
     |
     +-------------------------> User/Profile Svc
     |
     +-------------------------> Risk Svc

8.5 哪些链路必须强可靠

  • 登录态恢复。
  • 首页基础框架。
  • 商品价格和库存进入交易链路前的服务端确认。
  • 活动资格最终校验。
  • 风控拦截。

8.6 哪些链路可以异步

  • 曝光埋点。
  • 首页性能上报。
  • 推荐点击反馈。
  • 非关键配置更新。
  • 用户画像更新。

8.7 哪些链路可以降级

  • 个性化推荐失败时展示热门商品。
  • 营销服务失败时隐藏活动位。
  • 风控信号采集失败时使用默认保守策略。
  • 图片加载失败时展示占位图。
  • Feature Flag 拉取失败时使用本地缓存配置。

8.8 容易被误判的地方

最容易误判的是把首页当成“一个接口”。首页实际上是用户体验系统:它包含资源加载、配置、缓存、服务聚合、推荐、营销、风控、埋点、降级。首页接口 99.9% 成功,并不等于用户首页体验 99.9% 成功。客户端可能因为 JS 错误、图片阻塞、弱网、设备卡顿而失败,这些问题在后端接口成功率里不可见。


9. 案例分析二:弱网下提交订单

9.1 错误设计

用户点击提交订单

客户端 POST /orders

请求超时

客户端自动重试 POST /orders

服务端创建两笔订单

问题在于服务端把“收到一次请求”直接等价为“创建一次订单”。在不可靠网络下,客户端无法知道第一次请求是否已经成功。

9.2 改进设计

客户端生成 idempotency_key

POST /orders with idempotency_key

服务端检查:
  - 同一用户
  - 同一业务动作
  - 同一幂等键

若已处理,返回原结果
若处理中,返回处理中状态
若未处理,创建订单

客户端超时后查询 /orders/status?idempotency_key=...

9.3 关键实践

  • 幂等键由客户端生成,但服务端定义作用域。
  • 幂等记录需要 TTL,但不能短于业务重试窗口。
  • 请求体摘要应参与校验,避免同一个幂等键对应不同请求。
  • 客户端失败后优先查询状态,而不是立即再次提交。
  • UI 必须清楚表达“处理中”,避免用户反复点击。
  • 支付、发券、扣库存等副作用要有独立幂等保护。

10. 可观测性与运维

客户端可观测性要把用户旅程串起来:

client_trace_id

gateway request_id

backend trace_id

message correlation_id

analytics session_id

至少需要观察:

  • App 版本。
  • 操作系统版本。
  • 设备型号。
  • 网络类型。
  • 地区。
  • 页面加载阶段。
  • API 成功率和耗时。
  • 客户端错误。
  • 崩溃率。
  • 用户点击路径。
  • Feature Flag 状态。
  • 实验分组。
  • Trace ID。

关键不是“采集所有数据”,而是能回答:

  • 影响了哪些用户?
  • 哪个版本开始变差?
  • 是网络、客户端、网关、后端还是第三方问题?
  • 新功能灰度是否导致指标恶化?
  • 降级是否生效?

11. 安全、成本与治理影响

客户端侧设计还会影响安全和成本。

安全方面:

  • 客户端不能保存长期敏感密钥。
  • Token 刷新失败要避免死循环。
  • 本地缓存要最小化敏感数据。
  • 客户端参数必须服务端校验。
  • Feature Flag 不能作为权限控制。
  • 风控信号要防伪造、防重放。

成本方面:

  • 首页接口字段过大,会增加带宽和 CDN 成本。
  • 埋点全量上报会增加数据管道和存储成本。
  • 客户端错误重试会放大后端流量。
  • 图片资源未压缩会增加传输成本。
  • 多端重复实现 BFF 会增加研发成本。

治理方面:

  • 客户端 API 需要版本兼容策略。
  • 埋点字段需要数据字典。
  • Feature Flag 需要生命周期管理。
  • 老版本客户端需要退役策略。
  • 多端体验需要统一业务语义。

12. 设计 Checklist

前后端协作 API Checklist

  • 是否明确支持哪些客户端和最低版本?
  • 是否定义字段兼容策略?
  • 是否允许客户端忽略未知字段?
  • 枚举新增是否会破坏旧版本?
  • 是否有错误码和用户提示映射?
  • 写操作是否支持幂等键?
  • 弱网超时后客户端应该查询还是重试?
  • 是否定义客户端缓存 TTL?
  • 服务端是否仍会校验价格、库存、权限、风控?
  • 是否能通过 Trace ID 串联客户端和服务端?
  • 埋点是否覆盖曝光、点击、失败、性能?
  • Feature Flag 是否有默认值、回滚和审计?
  • 老版本客户端如何退役?
  • 敏感数据是否进入本地缓存、日志、崩溃上报?

13. 本章小结

客户端、前端和用户体验系统,是现代系统设计的第一段核心路径。它们不是服务端之后的附属层,而是系统可靠性、安全性、可观测性和成本模型的一部分。

好的客户端设计,不是把所有逻辑前移,而是明确哪些判断可以在客户端做,哪些必须由服务端最终确认;哪些数据可以缓存,哪些数据只能展示参考;哪些失败可以自动重试,哪些失败必须查询状态;哪些功能可以灰度,哪些开关不能成为安全边界。

从这一章开始,我们把系统设计的视角从“后端组件图”扩展为“用户旅程链路图”。


14. 典型失败模式

  1. 后端破坏老版本客户端兼容性。
  2. 弱网重试导致重复下单或重复扣费。
  3. 客户端缓存展示过期价格、库存或权益。
  4. Feature Flag 配置错误导致流量突增。
  5. 埋点缺失导致无法判断真实用户影响。
  6. Token 刷新死循环放大后端压力。
  7. BFF 复制业务逻辑导致多端语义不一致。
  8. 风控信号被客户端伪造。
  9. 老版本客户端长期访问废弃接口。
  10. 客户端降级只做了页面兜底,没有后端能力兜底。

15. 本章最重要的 5 个判断

  1. 客户端不是展示层,而是用户体验链路、风险链路和观测链路的起点。
  2. 弱网环境下,写操作必须和幂等、状态查询、用户提示一起设计。
  3. 客户端缓存只能优化体验,不能替代服务端最终决策。
  4. Feature Flag 降低发布风险,但必须治理生命周期、默认值、审计和组合复杂度。
  5. 没有客户端可观测性,就无法真正知道用户体验是否正常。