内容中心设计:不可变版本、发布指针、搜索投影与撤销
"搜索命中"只是候选集,不能赋予用户原本没有的读取权限——内容中心把内容本体、工作流、发布可见性与检索索引拆成四种事实。发布守卫与版本思想与审核系统设计同源;投影与墓碑机制与CDC、事件和投影同源;ES 引擎行为由 Elasticsearch 主题权威讲授。
教学模型采用 MySQL 8.0作为事务事实源,Elasticsearch 8.15作为搜索投影。缓存和对象存储只在明确的一致性边界内参与;本文不把假设规模写成实测结果。
需求与事实源
内容中心支持草稿、审核、定时发布、撤销、历史版本和多渠道分发。假设每天新增200万条,峰值写入1000/秒、读取5万/秒;正文平均20KiB,附件平均2MiB但长尾很大。标题、标签与权限需要筛选,全文搜索允许2秒目标延迟;撤销后受控接口不得继续返回内容,而已被外部下载的副本无法靠一次删缓存追回。
必须先分清内容本体、工作流、发布可见性和检索索引。事务库保存身份、版本、权限、审核引用与发布指针;不可变正文与附件放对象存储;搜索与缓存是派生视图。把主库与ES双写后都返回成功,只是把网络故障留给线上碰运气。
数据模型与发布不变量
content 保存 content_id、tenant_id、current_version、published_version、row_version、visibility;content_revision 保存不可变版本、对象摘要和schema_version;publication记录发布动作、审核decision_id、有效期及撤销原因。一个发布指针只能指向已存在且满足当前授权/审核条件的版本。
CREATE TABLE content (
content_id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
current_version INT NOT NULL, -- 草稿推进,不等于可见
published_version INT NULL, -- 发布指针;NULL=从未发布
row_version INT NOT NULL, -- 乐观并发守卫
visibility VARCHAR(16) NOT NULL -- DRAFT/PUBLIC/REVOKED
);
CREATE TABLE content_revision (
content_id BIGINT NOT NULL,
version INT NOT NULL,
object_etag CHAR(64) NOT NULL, -- 对象存储内容摘要,不可变
schema_version INT NOT NULL,
PRIMARY KEY (content_id, version)
);
CREATE TABLE publication (
publication_id BIGINT PRIMARY KEY,
content_id BIGINT NOT NULL,
version INT NOT NULL,
decision_id BIGINT NULL, -- 审核绑定
action VARCHAR(16) NOT NULL, -- PUBLISH/REVOKE
reason VARCHAR(255) NULL,
created_at TIMESTAMP NOT NULL
);UPDATE content
SET published_version=?, visibility='PUBLIC', row_version=row_version+1
WHERE content_id=? AND tenant_id=? AND current_version=?
AND row_version=? AND visibility IN ('DRAFT','PUBLIC');审核资格需在本事务或可证明的协议中核验,不能只靠调用方声称通过。用户保存草稿v9时,可保持已发布v8,也可按业务要求撤销旧版;两种规则需要明确,不能让页面和搜索各自选择。HTTP接口可用ETag/If-Match辅助发现编辑覆盖,标准见RFC 9110条件请求;最终并发裁决仍是服务端版本守卫。
发布链与可靠投影
编辑并保存不可变revision
→ 审核绑定revision与轮次
→ 发布事务:修改指针 + publication + outbox
→ 投影消费者按content_id分区
→ ES/缓存保存投影版本与可见状态
→ 后台核对投影水位和失败记录事件至少包含event_id、content_id、aggregate_version、published_version、visibility。消费者只接受大于已应用版本的事件,并允许重复;撤销事件也带单调版本。若删除ES文档后立即忘记版本墓碑,迟到的发布事件可能复活内容。墓碑保留期需覆盖消息重放、备份恢复和离线消费者的最大时限,不能随手设一天TTL。
事件负载与消费规则的最小完整示例(教学模型实跑见故障与验证):
{"event_id":"e3","content_id":9001,"aggregate_version":3,
"published_version":3,"visibility":"PUBLIC","schema_version":2}apply(event): # 消费者伪代码,按content_id分区串行
if event.event_id in applied: return # 重复投递:幂等,不重复审计
if event.aggregate_version <= applied_version:
return if not tombstoned(content_id) else KEEP_TOMBSTONE
project(index, event); audit.append(event.event_id)发布指针的并发裁决由SQL守卫完成:labs/system-design 的 content_publish_pointer 模型证明同一快照的两个并发写入只有一个成功、草稿保存不移动指针、被拒者重读后可重试。
搜索可见不等于持久化
ES接受索引请求后,搜索查询仍可能暂不可见;refresh使新segment对searcher可见,与完整持久化提交不是同一事件。引擎实现与实验见Elasticsearch 的可见、耐久与段整理,ES 8.15近实时搜索为官方依据。不要在每个内容写入后强制全索引refresh来“保证一致”,它可能显著改变吞吐与尾延迟。
用户刚发布后,可按ID从事实源/一致读路径显示结果,同时提示搜索投影在目标时间内更新。管理员权限与紧急撤销应在受控查询出口重新核验事实状态;搜索命中只是候选集,不能赋予用户原本没有的读取权限。
缓存键可包含内容版本,指针缓存需版本或失效协议。仅“先更新数据库再删缓存”仍有旧读回填窗口,应讨论版本化写入、短租约回填、失效事件与最终核对,而非背口诀。
索引重建与零停机切换
重建建立新索引,记录快照起始水位,批量导入后应用增量,追上既定切换水位;比较文档数之外,还要验证分租户数量、版本、可见性、抽样内容hash与删除墓碑。索引alias切换是发布步骤,但不能自动证明增量追齐。
回填旧版本与实时更新并发时,按aggregate_version防旧覆盖新。重建失败保留旧索引读路径,限制新索引回填资源以免拖垮主库。切换后仍要观察查询相关性、权限过滤、延迟与错误,而不是只确认alias API返回200。
容量与冷热分层
200万条×20KiB约38.1GiB正文/天,30天约1.12TiB;200万×2MiB附件约3.81TiB/天。两类数据差两个数量级,若把附件也塞进事务库会让备份、复制与恢复成为主成本。对象存储也需版本、生命周期、孤儿对象清理及删除审计。
读取5万/秒,假设命中率95%,回源2500/秒;降到90%时回源5000/秒,负载翻倍。缓存击穿时还需考虑热点内容集中在同一键,简单增加总缓存节点不一定降低单热点回源。合并相同加载、限制并发、允许明确范围内的旧数据与降载都要说明业务边界。
搜索分片数由索引大小、查询并行、恢复时间和节点内存共同决定。不能从“每天200万条”直接断言建多少分片;应给出存量、保留期、字段mapping、分词膨胀和每分片恢复吞吐,再压测验证。
安全、撤销与内容生命周期
多租户键贯穿数据库、对象路径、缓存键、ES过滤和后台批任务。字段级与对象级授权在服务入口统一执行,不能指望前端隐藏按钮。审计日志记录动作、主体、版本与结果,内容和凭据不直接进入普通追踪。
撤销顺序为先在事实源拒绝可见,再传播缓存/搜索/CDN失效,最后核对;CDN已缓存副本有传播时延,合同和产品提示需承认这一边界。法律删除涉及备份、日志和第三方导出,需定义限制访问、物理清理周期与证明,不能把ES delete称为全系统删除。
故障与验证
构造v1发布、v2撤销、v3重发,并按v3、v1、v2顺序消费,预期最终投影仍为v3。重复同事件不应重复审计业务动作。删除后重放旧发布,验证墓碑拒绝复活。再在索引重建中途实时修改内容,核对回填不能覆盖较新版本。
以上乱序/重复/墓碑时序已由确定性教学模型机器核对:python3 labs/system-design/model_check.py 的 content_projection_reorder(v3、v1、重复e3、v2、重复e1 的到达序收敛到v3,审计3次非5次,墓碑拒绝复活);2026-10-25本机实跑输出与解释存 evidence/u78-2026-10-25.json。模型不证明Kafka投递或ES refresh行为,真实中间件链路按消息平台设计与Elasticsearch 主题另行验证。
迁移测试保留旧索引、切换和回退指针,验证失败后仍可查询旧读模型。注意:应用回退到旧代码不表示新schema或已发外部事件可逆,要明确数据兼容窗口。
闭卷验收
本页 7 道闭卷题收进了下方自测面板:先回忆,再展开参考答案与评分点,最后给三档自评。
架构师进阶
要求全球读、单地编辑时,可比较主区写入加全球缓存与多区写入;后者必须处理编辑冲突、权限撤销和事件排序。要求全站立即撤销时,需要辨明受控接口、CDN和外部下载三个可控制范围,再给延迟/成本承诺。引入MongoDB需以文档不变量与边界论证,不能因为“内容是文档”就无条件新增一种数据库;复制可见性另按三组关注检查。