跳转到内容
use-cases / cron-per-customer / hero
多租户 SaaS / 按客户调度

每个客户自动获得独立的 crontab

你的 SaaS 让每个客户自定义自己的报表生成。朴素做法是一个共享调度器,客户 ID 塞在任务负载里,然后祈祷谁也别饿死谁。Hoody 的做法给每个租户独立的容器和独立的 hoody-cron 服务。

阅读文档

Three lifecycle states, one HTTP API. PROVISION adds entries, cron ticks run them, DELETE suspends. Each tenant's cron lives in its own container — no shared queue, no noisy-neighbor risk.

use-cases / cron-per-customer / mechanism

一次 PUT 调用就能为整个租户的 crontab 配齐

每个客户容器都暴露 hoody-cron 的 HTTP API。要设置他们的调度,只需用一次 PUT 替换整张 crontab。无共享队列,无优先级车道,无需重新部署调度器配置。

POST a managed entry — creates a cron job with UUID, enabled state, and human-readable schedule_human fieldPOST /provision
request
# POST managed entry for acme-corp tenant
POST acme-cron.hoody.com/users/root/entries
Content-Type: application/json

{
  "schedule": "0 9 * * *",
  "command": "/usr/local/bin/digest.sh",
  "comment": "daily digest",
  "enabled": true
}
response
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": "7d3f2a1b-8c4e-4f9a-b2d5",
  "schedule": "0 9 * * *",
  "schedule_human": "At 09:00",
  "enabled": true,
  "user": "root"
}
201 Created. Entry ID is returned for future PATCH or DELETE. schedule_human confirms expression was parsed correctly.

该端点原子地替换整张 crontab。被移除的条目会回报给你,以便控制平面审计漂移。每个租户的 cron 都活在自己的容器里;acme-corp 的调度对 globex-saas 完全不可见,一个容器里跑飞的任务也无法饿死另一个容器里的调度器。

use-cases / cron-per-customer / powers

容器绑定的 crontab 解锁了什么

三项特性是从这种设计里白送的——因为隔离是底质,而不是你写出来的功能。

Fleet billing breakdownper-tenant cost = server divided by tenants
Your tenants
acme-corpSM
globex-saasMD
initech-incLG
+ 57 more
Flat-rate server / mo$29One bare-metal node. 60 containers. Bill stays flat.
÷tenants
Per-tenant cost<$0.49Drops as you add more tenants

Noisy-neighbor incidents disappear

When initech-inc's scrape.js hangs, acme-corp's 9am digest still fires. Different crontabs, different process trees, different filesystems.

Schedule changes propagate instantly

POST a new entry and the tenant's hoody-cron service picks it up immediately. No central scheduler to reload, no broadcast to send.

Per-tenant logs, one container

When globex-saas asks why their 6pm rollup ran twice, you read one container's log — not a shared scheduler grep across nine machines.

use-cases / cron-per-customer / compare

共享调度器 vs 容器绑定的 crontab

三个轴线上,旧设计向你的团队收税,而 Hoody 的设计根本不收。

维度共享调度器容器绑定
隔离
tenant_id in job payload一行坏数据,所有租户的队列都阻塞
Separate /etc/crontab per container卡死永远是局部的
配给
INSERT INTO scheduled_jobs迁移耦合,schema 锁
PUT /users/root/crontab一次 HTTP 调用,原子替换
审计
grep tenant_id=42 logs/*9 台机器,每台一个日志文件
GET ctr_8a3f1c/cron/log一个容器,一份日志,一个真相

旧栏是每个团队第一次做多租户调度时都会写的东西。新栏是当平台默认给每个租户独立容器时,你顺手就发出来的东西。

use-cases / cron-per-customer / capacity

边界处的容量

当每个客户都有自己的 crontab 时,一台裸金属 Hoody 服务器能做到什么。

  1. 每台机器的租户数60

    一台裸金属节点上跑六十个客户容器,每个都有自己的 hoody-cron 服务。无共享调度器形成瓶颈。

  2. 调度传播<1s

    从 PUT 请求到新调度的第一次 tick,在典型 64 核节点的 60 个容器上观察到。

  3. 跨租户队列0

    字面意义上没有共享队列、优先级车道或调度器线程让两个租户去抢。隔离就是底质。

容量数字为 64 核 / 256GB 裸金属节点上、采用标准 Hoody 容器密度时的典型观测值。实际容量取决于每租户的 CPU / 内存预算以及每个 cron 任务的工作量。跨租户队列的零是结构性的,不是基准测试。

use-cases / cron-per-customer / punchline

一个客户的 cron 饿不死另一个,因为它们根本不在同一份 crontab 上。

之前 / 共享调度器之后 / 容器绑定
共享scheduled_jobs WHERE tenant_id = 42一张人人都读的表里的一行
按租户PUT acme-cron.hoody.com/users/root/crontab一次 HTTP 调用,一个容器,一份 crontab
阅读 cron API
use-cases / cron-per-customer / replaces

它替代了什么

团队为了在多个租户间共享一份 crontab 而搭建的种种架构。Hoody 把每个租户放进自己的 crontab——没有路由,没有公平队列,没有吵闹邻居。

  • 共享多租户 crontab一条糟糕的正则,饿死 400 个客户
  • 自定义租户隔离每行都带 tenant_id 的调度器
  • Postgres pg_cron数据库绑定;一次升级所有人崩
  • 带过滤器的 Quartz scheduler每个区域一个 JVM 加一个分片队列
  • Sidekiq 租户队列12 个队列,12 份配置文件
  • Kubernetes 按租户 CronJob一个 namespace、一个 RBAC role、一份 YAML、一个传呼
use-cases / cron-per-customer / cta

别再到处写 tenant_id 了。给每个客户自己的容器,让 cron 在隔离里做它一直在做的事。

阅读文档
use-cases / cron-per-customer / related

阅读其他内容