17.3 数据库会话状态

把会话数据作为已提交记录存入数据库,以持久性和共享访问换取数据库负担。

学习目标

  • 能解释数据库会话状态如何把会话记录交给共享数据库,并说清持久性、扩展性与数据库负担之间的取舍
  • 能编写带版本条件、过期检查和事务边界的 TypeScript 读写草图,拒绝旧版本覆盖新状态
  • 能根据会话大小、节点数量、恢复目标和热点指标,判断订单向导何时采用或拒绝数据库会话状态

为什么 17.3 数据库会话状态值得单独学习

订单向导会跨越多个请求,可能经历节点重启、发布切换和重复提交。把会话放在进程内很快,却无法让下一台节点或重启后的服务看到同一份状态;把全部状态放进客户端,又会把隐私、大小和撤销责任推给不可信输入。数据库会话状态把记录交给共享数据库,换来持久性和节点独立性,同时新增 SQL、连接池、清理任务与热点治理。

的关键不在于“用了 ORM 还是 SQL”,而在于确定谁拥有状态、何时提交、如何过期,以及数据库故障时请求应暂停、回退还是拒绝。数据库保存的是会话上下文,不是让会话记录替代订单、库存或权限等业务权威。

本章只覆盖 2024 年中文版公开目录中的 17.3 数据库会话状态,依据作者图书页和公开模式目录独立重写。案例、实验、代码、练习和答案均为本课程原创,不复现原书正文、插图或代码。

先建立直觉:共享存储换来恢复能力

一次订单向导请求可以拆成“读键、校验版本、修改草稿、事务提交、设置过期”五个观察点。只负责找到记录;服务器仍要验证当前用户、租户和流程权限。若把会话键当成授权凭证,数据库再可靠也无法修复边界错误。

数据库记录通常包含小型上下文、与到期时间。一次更新必须把读取、权限判断和写入的责任说清楚;发现版本冲突时,应返回可重试或重新读取的结果,而不是静默覆盖另一台节点刚提交的状态。

持久化也会引入新的观测项:连接池等待、热点会话、慢查询、清理滞后和数据库不可用率。不是“数据库方案必然错误”,却是提醒团队缩小会话、调整索引、分片或更换存储边界的证据。

目录单元到教学证据

17.3 数据库会话状态

本章精确对应 manifest 单元 poeaa24-pattern-40-database-session-state。学习者要能用同一条订单向导责任链回答:哪个节点可以读取、哪个事务可以写入、旧版本如何拒绝、过期记录如何清理、数据库不可用时业务是否允许继续。

完成本单元要交出三样证据:一张标出客户端、应用节点和数据库责任的图;一段带版本与过期检查的读写代码;一个故障样本,证明冲突、热点或数据库不可用时系统会在明确边界停止,而不是把错误状态继续传播。

专属案例:跨节点的订单向导

假设访客先保存配送地址,再在另一台应用节点选择付款方式。会话记录保存 cartDraftId、当前步骤、租户键和短期草稿;订单金额、库存、优惠资格与付款结果仍从权威服务读取。两台节点应该看到同一份已提交记录,但不能因为共享数据库就绕过当前用户的授权。

评审卡记录如下:单元键为 poeaa24-pattern-40-database-session-state;模式族为 session;采用条件是“需要跨节点共享、可恢复且记录大小可控的会话”;观测项包括读写延迟、版本冲突率、连接池等待、热点键和过期清理滞后;拒绝条件是“会话成为大对象、数据库无法承受峰值,或故障时没有安全回退”。

专属可视化实验:一条记录怎样跨节点生效

先预测:节点 A 读到版本 7 后,节点 B 先提交版本 8,节点 A 再写入时应该发生什么?点击主图观察会话键如何定位记录、版本条件如何阻止旧写入,以及过期清理为什么是容量责任的一部分。再把数据库改成不可用,判断请求应返回可重试错误还是继续执行付款。

Database Session State:状态持久化到数据库客户端sid=abc123任意应用节点无本地状态,按需读写 DBSQLsessions 表sid | data | expiresabc | {...} | 30m事务化读写 · 过期清理步骤 1:会话键只定位记录客户端只带 sid;应用节点读取数据库中的权威会话状态持久化到数据库,任意节点可读写,代价是 DB 开销
数据库会话状态将数据持久化到数据库表中,任意应用节点均可读写; 三个步骤分别核对定位、版本写入与过期清理。

三个阶段快照

下面的 Stepper 把主图拆成三个确定性阶段。先写下每一步的责任和失败结果,再对照图中的数据流,避免把“能读到记录”误认为“已经获得业务授权”。

分步1 / 3

1. 会话键定位权威记录

节点收到 sid 后只用它定位记录,再校验租户、用户和流程权限。会话键不是价格、角色或付款状态的来源。

Database Session State:状态持久化到数据库客户端sid=abc123任意应用节点无本地状态,按需读写 DBSQLsessions 表sid | data | expiresabc | {...} | 30m事务化读写 · 过期清理步骤 1:会话键只定位记录客户端只带 sid;应用节点读取数据库中的权威会话状态持久化到数据库,任意节点可读写,代价是 DB 开销
数据库会话状态将数据持久化到数据库表中,任意应用节点均可读写; 三个步骤分别核对定位、版本写入与过期清理。

代码实践:带版本和过期边界的读写

下面的 TypeScript 草图把数据库适配器收窄成可测试接口。真实项目应使用参数化查询、事务、连接池和成熟的重试策略;这里的重点是权限、版本和过期检查的顺序。

type SessionRow = {
  id: string;
  ownerId: string;
  tenantId: string;
  step: 1 | 2 | 3;
  cartDraftId: string;
  version: number;
  expiresAt: number;
};
 
type SessionStore = {
  get(id: string): Promise<SessionRow | null>;
  updateIfVersion(
    id: string,
    expectedVersion: number,
    patch: Pick<SessionRow, "step" | "cartDraftId" | "expiresAt">,
  ): Promise<"updated" | "conflict">>;
};
 
async function advanceWizard(
  store: SessionStore,
  input: { sessionId: string; ownerId: string; tenantId: string; now: number },
): Promise<"ok" | "not-found" | "forbidden" | "expired" | "conflict"> {
  const row = await store.get(input.sessionId);
  if (!row) return "not-found";
  if (row.ownerId !== input.ownerId || row.tenantId !== input.tenantId) {
    return "forbidden";
  }
  if (row.expiresAt <= input.now) return "expired";
 
  const result = await store.updateIfVersion(
    row.id,
    row.version,
    { step: 2, cartDraftId: row.cartDraftId, expiresAt: input.now + 15 * 60_000 },
  );
  return result === "updated" ? "ok" : "conflict";
}

四个代码边界可直接测试:不存在的键不能创建隐含授权;用户和租户必须匹配;过期记录不能进入业务动作;版本冲突不能被重试逻辑伪装成成功。若把 updateIfVersion 改成无条件 UPDATE sessions SET ...,就应把它标成会覆盖并发写入的拒绝样本。

选择与拒绝矩阵

评审问题采用数据库会话状态的证据应拒绝或改用其他方案的信号
共享任意节点都能读取同一份已提交记录只能依赖粘性路由或进程内缓存
完整性权限、版本与事务边界可测试会话键直接充当授权凭证
容量记录小、索引明确、清理可观测大对象把数据库变成文件存储
故障数据库不可用时有安全回退和告警连接池耗尽仍继续付款或发货
生命周期expiresAt、清理任务和恢复路径明确过期数据永久增长且没有责任人

常见误区

本章小结

  • 数据库会话状态以共享、持久化和节点独立性换取 SQL、连接池、热点与清理责任。
  • 会话键只定位记录;权限、租户、版本和过期检查必须在业务动作前完成。
  • 乐观版本让并发冲突可见,事务和参数化查询让状态变化可审计、可测试。
  • 当会话很大、数据库无法承受峰值或故障没有安全回退时,应拒绝当前方案并重新划分状态边界。

可验证练习

练习

问题 1:找出错误边界。 advanceWizard 只接收 sessionId,查到记录后就把 step 改成 3。请指出至少两个缺失检查,并说明它们防止什么问题。

问题 2:解释冲突结果。 节点 A 和 B 都读到版本 7,B 先提交版本 8,A 的更新返回 conflict。为什么不能直接重试同一个 patch?

问题 3:选择替代方案。 会话包含 2MB 的草稿、强隐私字段,并要求数据库故障时付款仍继续。请给出三个拒绝信号和一个拆分方向。

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

数据库会话状态

把跨请求会话记录保存到共享数据库,由应用节点按键读写并承担事务、清理与故障责任。

会话键

定位一条会话记录的标识;它不等于身份、租户或业务授权。

乐观版本

随记录递增的版本标记,用条件更新发现旧请求并拒绝覆盖。

数据库热点

同一记录或分片被频繁访问,造成锁等待、缓存失效或集中压力的现象。

过期清理

依据到期时间删除或归档不再有效的会话记录,并以指标监控清理滞后。

前后导航

参考资料

资料与写作方式声明

本章以Martin Fowler《企业应用架构模式》与公开模式目录权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…