
系列定位本篇是「阿明餐厅」系列的番外三。在前传中阿明完成了从单机到云原生的架构演进。当系统足够成熟后一个新的商业机会出现了 ——把这套系统卖给其他餐厅老板。但一套系统服务多个客户远比想象中复杂。引言一套系统三个世界阿明的系统做得太好了。消息传出去隔壁城市的三家餐厅老板找上门来明哥你那套系统能不能也给我们用我们付钱。阿明很高兴“一套系统卖给三家成本分摊利润翻倍”他让老陈快速开了三个账号心想无非就是多三个登录用户。第一周就炸了锅。A 餐厅的老板打电话来“为什么你们的菜单上显示’酸菜鱼 38 元’我明明设置的是 48 元” —— 他看到了 B 餐厅的菜单和价格。紧接着C 餐厅午高峰搞了一次大促瞬间涌入 2000 单把整个系统拖慢了。A 和 B 的顾客纷纷投诉点餐页面转圈圈。还没完A 餐厅是高端日料要求界面用深色主题、展示清酒列表B 餐厅是快餐连锁要求 bright 风格、突出套餐优惠。改了一套另一套就乱。阿明终于意识到多租户不是加个登录页面那么简单。每个租户都觉得自己是唯一的住户但实际他们共享着同一栋楼。第一章多租户的三种隔离模型阿明把问题甩给老陈“到底怎么才能让三家餐厅各过各的”老陈拿了三个方案出来用餐厅的布局做比喻方案一独立数据库VIP 包厢每家餐厅一个完全独立的数据库就像给每个客户一间专属包厢 —— 门一关互不干扰。A 餐厅的数据泄露了B 和 C 完全不受影响。方案二共享数据库独立 Schema隔断包间大家共用一个数据库实例但每个租户有自己的 Schema数据表集合。就像用隔断把大厅分成几个包间 —— 视线隔开了但隔音不好隔壁吵闹还是能听到。方案三共享表 tenant_id大厅拼桌所有租户的数据存在同一张表里靠tenant_id字段区分。就像大厅里拼桌 —— 成本最低但一个人大声说话全桌都听到。三种隔离模型对比 ┌─────────────────┬──────────────┬──────────────┬──────────────┐ │ │ 独立数据库 │ 独立 Schema │ 共享表 │ │ │ (VIP 包厢) │ (隔断包间) │ (大厅拼桌) │ ├─────────────────┼──────────────┼──────────────┼──────────────┤ │ 隔离度 │ ★★★★★ │ ★★★☆☆ │ ★★☆☆☆ │ │ 成本 │ 高每户一套│ 中共享实例│ 低共享一切│ │ 运维复杂度 │ 高N 套库 │ 中 │ 低 │ │ 数据迁移难度 │ 低独立操作│ 中 │ 高混在一起│ │ 性能隔离 │ 完全隔离 │ 部分隔离 │ 几乎无隔离 │ │ 定制灵活度 │ 高 │ 中 │ 低 │ │ 适用客户 │ 大客户/VIP │ 中型客户 │ 小客户/免费 │ └─────────────────┴──────────────┴──────────────┴──────────────┘老陈建议三种模型混合使用。A 餐厅是大客户年付 50 万给他独立数据库B 餐厅中等规模用独立 SchemaC 餐厅是小店用共享表。阿明问“为什么不全部用独立数据库最安全啊。”老陈回答“因为成本。100 个小客户每个配一个独立数据库光运维就能把人累死。隔离是有价格的要和客户价值匹配。”在 SaaS 领域这叫**Silos vs Pools模型** —— Silos独立部署隔离好但成本高Pools共享部署成本低但隔离弱。成熟的 SaaS 平台会按客户等级分层使用。多租户隔离的核心是隔离是有价格的要和客户价值匹配。第二章租户路由与数据隔离确定了隔离模型下一个问题请求进来怎么知道是哪个租户的老陈设计了四种租户识别方式识别方式示例优点缺点餐厅类比子域名a.aming.com用户友好、SEO 友好需要通配符证书每家店有自己的门牌号独立域名arestaurant.com品牌独立运维复杂每家店有自己的独栋HTTP HeaderX-Tenant-ID: a灵活不直观进门报暗号Token 中携带JWT 的tenant_id字段安全、无状态Token 变大工牌上写着部门阿明选了子域名 JWT Token的组合 —— 用户通过a.aming.com访问登录后 JWT 中携带tenant_id后续请求靠 Token 识别身份。但在数据层面光识别租户还不够。老陈最怕的事情是代码里忘了加tenant_id过滤条件一个查询就把所有租户的数据全拉出来了。他引入了三层防线# 第一层中间件自动注入 tenant_idclassTenantMiddleware:defprocess_request(self,request):tenant_idextract_tenant_id(request)# 从 JWT 或域名解析request.tenant_idtenant_id# 设置数据库会话的默认过滤条件db_session.set_tenant_filter(tenant_id)# 第二层数据库行级安全Row-Level Security, RLS# PostgreSQL 的 RLS 策略 —— 即使应用层忘了过滤数据库也会自动加上# CREATE POLICY tenant_isolation ON orders# USING (tenant_id current_setting(app.current_tenant)::int);# 第三层ORM 层的全局 ScopeclassOrderRepository:deffind_all(self,tenant_id):# ORM 自动追加 WHERE tenant_id ?returnself.db.query(Order).filter(Order.tenant_idtenant_id).all()对于独立 Schema 的租户B 餐厅路由逻辑稍有不同 —— 中间件根据tenant_id动态切换 Schemadefget_db_connection(tenant_id):iftenant_idvip_a:returnget_dedicated_db(tenant_a)# 独立数据库eliftenant_idin[medium_b,medium_d]:connget_shared_db()conn.execute(fSET search_path TO tenant_{tenant_id})# 切换 Schemareturnconnelse:connget_shared_db()conn.set_tenant_filter(tenant_id)# 共享表 tenant_id 过滤returnconn老陈特别强调了一个原则租户隔离不能依赖开发者的自觉。人会犯错代码会漏写但数据库级别的 RLS 策略和 ORM 全局 Scope 不会因为疏忽而失效。租户路由的核心是让隔离成为基础设施的能力而不是每个开发者都要记住的事。第三章资源隔离与限流C 餐厅大促拖垮全系统的事情阿明记忆犹新。老陈分析原因“共享表模式下所有租户共用一个数据库连接池、同一个 CPU、同一块内存。C 餐厅瞬间涌入 2000 个查询把连接池占满了A 和 B 的请求只能排队等。”这就是经典的**吵闹邻居Noisy Neighbor问题** —— 一个租户把资源吃光其他租户遭殃。老陈设计了一套按租户限流的方案限流策略按租户 A 餐厅VIP 令牌桶容量500 req/s 突发上限800 req/s 优先级队列P0最高 B 餐厅中型 令牌桶容量200 req/s 突发上限400 req/s 优先级队列P1 C 餐厅小型 令牌桶容量50 req/s 突发上限100 req/s 优先级队列P2 全局保底 预留 30% 资源给 VIP 租户任何情况下不被抢占# 基于 Redis 的按租户限流classTenantRateLimiter:def__init__(self,redis_client):self.redisredis_clientdefallow_request(self,tenant_id):quotaself.get_tenant_quota(tenant_id)keyfrate_limit:{tenant_id}# 使用 Pipeline 保证 INCR EXPIRE 的原子性pipeself.redis.pipeline()pipe.incr(key)pipe.expire(key,1)# 1 秒窗口current,_pipe.execute()ifcurrentquota[max_rps]:returnFalse,{retry_after:1,tenant:tenant_id}returnTrue,{}defget_tenant_quota(self,tenant_id):quotas{vip:{max_rps:500,burst:800},medium:{max_rps:200,burst:400},small:{max_rps:50,burst:100},}tierget_tenant_tier(tenant_id)returnquotas[tier]除了限流老陈还做了资源配额资源维度VIP 租户中型租户小型租户餐厅类比CPU 配额40%30%10%灶台分配内存配额4 GB2 GB512 MB冷库空间存储空间500 GB100 GB10 GB仓库面积连接池50205传菜窗口数并发任务1005010同时接的单阿明问“如果 C 餐厅愿意多付钱能给他更多资源吗”老陈笑了“当然。限流不是惩罚是商业模型的技术实现。付多少钱用多少资源天经地义。”详见《高峰保卫战》中的限流策略 —— 全局限流保护系统按租户限流保护邻居。资源隔离的核心是一个租户的错误应该只影响他自己不应该让所有人买单。第四章定制化 vs 标准化A 餐厅的老板又提需求了“我的日料店需要一个清酒搭配推荐功能你的系统没有。”阿明第一反应是“做大客户的要求必须满足。”老陈拦住了他“你给 A 做了清酒推荐B 要不要做火锅配菜推荐C 要不要做奶茶加料推荐做了三个定制以后每次升级都要分别测试三套代码升级一次要一周。”阿明陷入了两难不定制丢客户过度定制丢效率。老陈提出了一个黄金比例80% 标准化 20% 可配置。SaaS 定制化的四层模型 第一层主题/皮肤零代码 → 每个租户自定义 Logo、颜色、字体 → 存储在 tenant_config 表前端动态加载 第二层功能开关Feature Toggle → 每个租户可以开启/关闭特定功能模块 → 例如A 开启清酒推荐B 关闭 第三层字段级扩展自定义字段 → 租户可以在标准模型上增加自定义字段 → 例如A 的菜品表多出sake_pairing字段 第四层插件化扩展点高级定制 → 提供标准 API 和 Webhook租户自己开发插件 → 平台不碰定制代码只提供扩展接口老陈特别设计了一个 Feature Toggle 系统控制租户级别的功能开关{tenant_id:vip_a,features:{menu_management:{enabled:true},order_tracking:{enabled:true},sake_recommendation:{enabled:true,tier:premium},hotpot_pairing:{enabled:false},analytics_basic:{enabled:true},analytics_advanced:{enabled:true,tier:premium}}}# 功能开关中间件classFeatureToggleMiddleware:defcheck_feature(self,tenant_id,feature_name):configself.get_tenant_features(tenant_id)featureconfig.get(feature_name,{})ifnotfeature.get(enabled,False):raiseFeatureDisabledException(f功能 {feature_name} 未开通请联系客服升级套餐)# 检查功能是否在当前套餐层级内required_tierfeature.get(tier,basic)ifnotself.tenant_has_tier(tenant_id,required_tier):raiseTierUpgradeRequiredException(f功能 {feature_name} 需要{required_tier}套餐)但阿明还是踩了一个坑。他为 A 餐厅做了一个VIP 预约管理的深度定制模块代码直接写在了主分支里。结果下次系统升级时这个定制模块和新版本冲突了A 餐厅的预约功能挂了两天。老陈总结教训“定制代码永远不要和主产品代码混在一起。用插件、用扩展点、用 Webhook但不要动核心代码。”详见《从接单到出餐》中的 Feature Toggle —— 在 SaaS 场景下特性开关不仅是灰度发布的工具更是多租户定制化的基石。定制化的核心是80% 标准化保效率20% 可配置保灵活0% 硬编码保平安。第五章计费模型与计量系统卖给三家餐厅后阿明开始琢磨收费。“收多少钱合适按年收按月收按什么维度计费”老陈梳理了三种常见的 SaaS 计费模型计费模型计费维度优点缺点餐厅类比按座位Per Seat使用系统的员工数收入可预测不反映真实用量按人头收费的自助餐厅按用量Usage-Based订单数 / API 调用数 / 存储量用多少付多少公平收入不可预测按克称重的麻辣烫按功能模块Tiered基础版/专业版/企业版客户自选层级功能切分困难套餐制小份/中份/大份老陈建议阿明用混合模型基础平台费按功能层级 超额用量费按订单数。阿明的 SaaS 定价方案 基础版小店 月费999 元 包含500 单/月、1 个门店、基础报表 超额2 元/单 专业版中型 月费4999 元 包含5000 单/月、5 个门店、高级报表、API 接入 超额1 元/单 企业版大客户 月费面议通常 2-5 万/月 包含无限单量、无限门店、独立部署、专属客服但计费的前提是计量—— 你得准确知道每个租户用了多少。老陈搭建了一套计量系统# 计量数据采集 —— 每个关键操作都记录用量事件classMeteringService:defrecord_usage(self,tenant_id,metric_name,quantity):event{tenant_id:tenant_id,metric:metric_name,# 如 orders, api_calls, storage_mbquantity:quantity,timestamp:datetime.utcnow().isoformat(),}# 写入 Kafka由下游消费者聚合self.kafka_producer.send(metering_events,event)# 用量聚合 —— 每小时运行一次defaggregate_hourly_usage():# 从 Kafka 消费过去 1 小时的用量事件eventsconsume(metering_events,window1h)fortenant_id,metric,totalingroup_and_sum(events):save_to_metering_db(tenant_id,metric,total,periodhourly)计费系统有一个容易被忽略的环节 ——Showback展示成本。让租户自己看到用了多少既能减少账单争议也能推动升级。租户 A 的月度用量报告 本月用量摘要2026-05 订单处理4,832 单包含 5,000 单内未超额 API 调用128,456 次 存储空间23.4 GB / 100 GB 活跃用户47 人 费用明细 基础平台费专业版 4,999 元 超额订单费 0 元 附加存储费 0 元 ────────────────────────── 本月合计 4,999 元详见《阿明的省钱经》中的 Showback/Chargeback —— 给内部团队看成本是 Showback给外部客户看账单就是 SaaS 计费。理念完全一致让花钱的人看到花了多少。计费的核心是计量准确是信任的基础Showback 是减少争议的武器。第六章租户迁移与运维系统运行半年后B 餐厅业务爆发从中型客户升级为 VIP 客户。问题来了B 餐厅的数据目前在共享 Schema 里需要迁移到独立数据库。而且迁移过程中不能停服—— B 餐厅一天 3000 单停一小时就损失十几万。老陈设计了一套租户迁移方案租户迁移四步法以 B 餐厅从共享 Schema → 独立数据库为例 第一步双写Dual Write → 新订单同时写入旧 Schema 和新数据库 → 持续 24-48 小时确保新库数据追平 第二步历史数据迁移 → 后台批量迁移历史数据到新库 → 增量同步CDC补齐双写期间的变更 第三步切换读流量 → 先将 10% 读请求路由到新库验证数据一致性 → 逐步提升到 100% 第四步停止双写 → 确认新库数据完整后停止旧库写入 → 保留旧库 30 天作为回退备份除了迁移日常运维也有多租户特有的挑战运维场景挑战解决方案餐厅类比版本升级升级可能影响所有租户灰度升级先升小租户验证无误再升大租户新菜品先在小店试卖数据备份要能按租户恢复逻辑备份按 tenant_id 拆分支持单租户恢复每家店的账本分开保管合规隔离GDPR 要求某些数据不出境按地域部署欧洲租户数据存在欧洲机房本地食材本地采购租户下线删除数据不能影响其他租户共享表模式下软删除 定期物理清理退租后打扫干净老陈特别重视灰度升级策略。他的原则是灰度升级顺序从低风险到高风险 第一波内部测试租户自己的餐厅 ↓ 验证 24 小时无异常 第二波小型免费租户C 餐厅们 ↓ 验证 48 小时无异常 第三波中型付费租户 ↓ 验证 72 小时无异常 第四波VIP 大客户A 餐厅 ↓ 全程人工值守关于合规隔离阿明遇到了一个真实场景一个日本客户要求数据必须存储在日本境内符合日本《个人信息保护法》。阿明不得不在日本区域部署了一套独立环境专门为这个客户服务。老陈感叹“多租户的终极挑战不是技术而是合规。法律说你不行技术再好也没用。”详见《差评危机》中的灰度发布思路 —— 灰度不仅用于代码发布也用于 SaaS 的版本升级。先小后大先低风险后高风险。租户运维的核心是升级要灰度备份要按户合规要分区下线要干净。核心总结多租户与 SaaS 架构新租户入驻隔离模型VIP包厢/隔断包间/大厅拼桌租户路由域名识别 JWT 鉴权数据隔离RLS 动态 Schema tenant_id资源隔离按租户限流 配额管理定制化80%标准化 20%可配置计费计量Usage-Based Showback租户运维灰度升级 合规隔离章节核心问题餐厅类比技术实现隔离模型租户之间隔多开包厢/隔断/拼桌独立 DB / 独立 Schema / 共享表租户路由请求怎么找到正确的租户门牌号 工牌子域名 JWT RLS资源隔离怎么防止吵闹邻居按桌限菜按租户令牌桶 资源配额定制化标准化还是个性化套餐 vs 单点Feature Toggle 插件化计费计量收多少钱怎么算按人头/按克/按套餐计量系统 Showback租户运维怎么安全地升级和维护新店试菜灰度升级 合规分区一句心法多租户的本质是用一套系统服务多个独立世界。隔离做得好客户感觉不到邻居隔离做不好一次事故全军覆没。延伸阅读当餐厅长出大脑 —— AI Agent 的全景拆解SaaS 平台未来也可以嵌入 AI 能力为租户提供智能推荐架构是长出来的 —— 从单机到云原生的演进多租户是架构成熟后的商业化延伸给产品经理的重构说明书 —— SaaS 化的本质就是一次大规模重构需要产品经理理解技术债的代价高峰保卫战 —— 全局限流保护系统按租户限流保护邻居限流是多租户的基石厨房装监控 —— 多租户场景下需要按租户维度的监控和告警否则不知道哪个租户出了问题食安大检查 —— 多租户的安全挑战更复杂一个租户的数据泄露可能影响所有租户的信任从厨师到 CEO —— SaaS 产品的组织管理从服务自己到服务客户的思维转变厨房质检员 —— 多租户的测试策略需要覆盖租户隔离、跨租户数据泄露等特殊场景从接单到出餐 —— SaaS 的 CI/CD 需要支持灰度发布、按租户回滚等特殊能力菜单设计学 —— SaaS 平台的 API 设计需要支持多租户上下文传递和版本兼容学徒的困境 —— AI 时代的人机协作与学习之道当 AI 越来越强人还要不要练基本功数据厨房 —— 多租户的数据治理更复杂每个租户的数据质量、数据血缘都要独立管理前厅翻修记 —— 多租户的前端需要支持主题定制、功能开关驱动的动态 UI阿明的省钱经 —— SaaS 的成本优化直接决定利润率多租户的 FinOps 是核心能力差评危机 —— 多租户的故障影响面更大一次事故可能流失所有客户外卖大战 —— 多租户的性能优化需要按租户隔离不能让一个租户拖垮所有人传菜窗口的智慧 —— 消息队列在多租户场景下需要按租户隔离 Topic 或 Queue十家店的烦恼 —— 分布式系统的经典难题在多租户场景下更加复杂厨房实况直播 —— 实时系统在多租户场景下需要按租户隔离 WebSocket 连接和事件流一个厨房四个门面 —— 多平台架构与多租户有天然的结合点一个 SaaS 平台多个前端门面懂你的菜单 —— SaaS 平台为每个租户提供个性化推荐搜索和推荐需要按租户隔离模型菜谱标准化之路 —— SaaS 平台的知识管理和文档标准化帮助租户自助解决问题仓库搬家不停业 —— 租户数据迁移是数据库迁移的特殊场景需要零停机完成预制菜还是现炒 —— 低代码平台天然适合 SaaS 场景让租户自己配置而不是开发定制阿明出海记 —— SaaS 出海面临多租户合规、数据驻留、多语言等叠加挑战厨房大换岗 —— AI 组织转型在多租户场景的应用每个租户的 AI 配置独立管理阿明的二次创业 —— AI 原生创业的 SaaS 化路径AI 能力如何融入 SaaS 产品会自我进化的厨房 —— Agent Loop 的多租户隔离不同租户的 Agent 循环独立运行AI 的黑暗料理 —— AI 幻觉在多租户场景的治理不同租户的 AI 护栏策略差异化结语阿明的加盟帝国故事揭示了每一个 SaaS 创业者都会遇到的真相把系统卖给别人和自己用完全是两回事。自己用的时候出了问题改就行卖给别人用的时候一个租户的问题可能变成所有租户的灾难。答案是六层防线选对隔离模型匹配客户价值做好租户路由让请求找到正确的归属数据隔离保证信息不串门资源隔离防止吵闹邻居拖垮所有人定制化在标准化和个性化之间找到黄金比例计费计量让每一分钱都有据可查。下次当你考虑把内部系统变成 SaaS 产品时不妨问自己你的隔离模型能匹配不同等级的客户吗还是只有一种方案租户路由是基础设施级的还是靠每个开发者手动加tenant_id一个租户的大促活动会不会拖垮其他租户你做过按租户的压测吗你有没有为大客户做过硬编码的定制那些定制代码现在还能和新版本兼容吗你的计量系统能准确到每个租户的每一笔用量吗客户能看到自己的账单吗好的多租户架构是让每个客户都觉得这个系统是为我一个人做的。← 返回系列导读