2. 多租户模型与隔离策略
多租户是 IoT SaaS 平台的核心命题:同一套服务进程、同一套数据库集群、同一套消息通道,要同时承载成百上千个客户,而每个客户都要求“我的设备、我的数据、我的规则,别人看不见也改不了”。这一节围绕四个问题展开:请求进来后如何确定“这是哪个租户”、数据在存储层隔离到什么程度、哪些资源共享哪些独占、以及一个租户从开通到注销要经历哪些状态。
一、租户识别:把 tenant_id 贯穿整条链路
租户识别不是登录时做一次就结束的动作,而是要在每一次请求、每一条消息、每一次设备上报中都能被还原出来。IoT 场景的识别入口比普通 Web 系统更多,常见有四类:
- 控制台/API 请求:从 JWT 的 claim 中取
tenant_id,或从子域名(如acme.iot.example.com)解析,二者必须一致,否则拒绝。 - 设备接入:设备用
ProductKey + DeviceName + DeviceSecret连接 MQTT Broker,Broker 在鉴权阶段查出设备所属租户,把tenant_id写入会话上下文。 - 消息流转:上行消息的 Topic 中携带租户前缀,例如
/t/{tenant_id}/{product_key}/{device_name}/up,规则引擎、流处理、时序写入都从 Topic 解析租户。 - 异步任务与回调:定时任务、Webhook、离线计算必须显式携带
tenant_id,不能依赖“当前登录用户”这种隐式上下文。
工程上最稳妥的做法是:在网关层完成一次识别,把 tenant_id 注入到请求上下文(如 Java 的 ThreadLocal、Go 的 context.Context),后续所有数据访问都从上下文取,禁止业务代码自行从参数里读租户。这样能避免“参数里传了别的租户 ID 就越权”的经典漏洞。
二、数据隔离级别:从共享表到独立库
数据隔离通常分四档,隔离强度与成本同向上升:
| 级别 | 存储形态 | 隔离强度 | 典型适用 |
|---|---|---|---|
| L1 共享表 | 同表加 tenant_id 列,行级过滤 | 弱,靠代码保证 | 免费版、试用租户 |
| L2 共享库独立 Schema | 同库不同 schema,如 t_acme.device | 中,权限可隔离 | 标准付费租户 |
| L3 独立数据库 | 每租户一个 DB 实例或独立库 | 强,故障与备份独立 | 企业版、合规要求高 |
| L4 独立部署 | 独立集群/独立 VPC | 最强,物理隔离 | 金融、政务、专有云 |
L1 的关键风险是“漏加 where 条件”。工程上不要依赖开发者自觉,而应通过 MyBatis 拦截器、JPA 的 @Filter、或数据库行级安全策略(如 PostgreSQL RLS)强制注入租户条件。以 PostgreSQL 为例:
ALTER TABLE device ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON device
USING (tenant_id = current_setting('app.tenant_id')::bigint);
-- 每次请求开始时执行:
SET LOCAL app.tenant_id = '1024';
这样即使 SQL 忘记写租户条件,数据库也会自动过滤。IoT 的时序数据量极大,通常设备属性、遥测点走 L1 共享表 + 时间分区,而设备档案、规则配置、密钥等敏感数据按租户级别提升到 L2/L3,形成“混合隔离”。
三、共享与独立资源的权衡
隔离级别决定数据怎么放,资源共享策略决定计算和连接怎么分。IoT 平台最容易被单租户拖垮的资源有三类:MQTT 连接数、消息吞吐、时序写入。常见做法是给每个租户分配配额并做限流:
- 连接配额:
max_connections,超出后新连接被拒绝并返回明确错误码。 - 消息速率:
msg_per_sec,用令牌桶在 Broker 侧限流,避免一个租户的异常设备刷爆整个集群。 - 存储配额:
tsdb_retention_days与max_storage_gb,到期自动降采样或清理。
共享资源池适合中小租户,成本低、利用率高;独立资源池适合大客户,但会带来资源碎片。实践中常采用“共享池 + 大客户独占池”的混合模式,并在租户元数据中标记 resource_pool = shared | dedicated,路由层据此选择连接池和消息队列。
四、租户生命周期管理
租户不是一条静态记录,而是有状态的对象。典型状态包括:申请中 → 试用 → 正式 → 欠费冻结 → 注销。每个状态对应不同的资源行为:
- 试用:限制设备数(如 50 台)、保留期 7 天,到期自动转冻结。
- 正式:按套餐开放配额,支持扩容。
- 欠费冻结:禁止新设备接入和写入,但保留数据只读,给 30 天续费窗口。
- 注销:先逻辑删除,进入 15 天冷静期,之后物理清理数据库、对象存储和密钥。
生命周期管理必须与租户识别联动:网关在注入 tenant_id 后要检查租户状态,冻结租户的请求直接返回 403 TENANT_FROZEN,而不是让它继续写入。注销流程要有幂等设计,避免清理任务重复执行导致误删其他租户数据。
tenant_id,OSS/S3 路径要按租户分目录,否则隔离会出现“木桶短板”。把四件事串起来看:租户识别解决“是谁”,隔离级别解决“数据放哪、怎么防越权”,资源共享策略解决“算力和连接怎么分”,生命周期解决“从生到死怎么管”。四者共同构成多租户平台的地基,后续的计费、监控、审计都建立在这套模型之上。