
一台服务器上运行 60 个容器
一个裸金属服务器运行数十到数百个 Hoody 容器。KSM 和 BTRFS 去重使边际成本接近零。
Stripe 用 ?n=12 把事件主体 POST 到一个 pipe 路径。十二个订阅者用 ?n=12 GET 同一个路径。pipe 在所有人连接好之前一直保留消息,然后一次性向全部十二个推流。没有 broker,没有 consumer group,没有 DLQ。
Hoody Pipe 是带多接收方模式的 HTTP 流。在发送方和接收方 URL 上都追加 ?n=N,pipe 会等到 N 个读者都接入后,再把主体同时镜像给所有人。慢的那些只压自己的连接——快的那些继续流。
你的 Stripe 端点把事件主体直接转发到 /api/v1/pipe/billing?n=12。连接在 pipe 等待十二个接收方集合期间被阻塞。
每个订阅者都是容器中的一个 curl 循环,用 ?n=12 GET 同一个路径。pipe 把主体保留在内存中,直到第十二个读者连接——没有磁盘上的队列,没有要刷盘的内容。
所有人连接好后,主体一次性流向全部十二个。慢读者把背压施加在自己的套接字上;其他人继续走。最后一个读者断开时,pipe 忘掉这条消息。
# Sender side — your Stripe webhook handler.
# Pipe holds the body until 12 readers are attached.
curl -X POST "https://api.hoody.com/api/v1/pipe/billing?n=12" \
-H "Authorization: Bearer $HOODY_TOKEN" \
-H "Content-Type: application/json" \
--data-binary "@stripe-event.json"
# Receiver side — one of twelve subscriber containers.
# Same path, same n. Streams the event body when fan-out is ready.
curl -N "https://api.hoody.com/api/v1/pipe/billing?n=12" \
-H "Authorization: Bearer $HOODY_TOKEN" | ./billing-handler.sh
# n must match on both ends — mismatch returns 400.
# Default n=1 is point-to-point. n=12 is fan-out for twelve.两端用同一个 URL、同一个查询字符串。发送方的连接就是 broker;读者的连接就是 consumer group。背压是按套接字计的,而不是按主题——因为根本没有主题,只有一份在传输的主体和十二个套接字在拉它。
增加一个订阅者就是再起一个容器里多跑一条 curl。移除一个就是杀掉那条 curl。没有 broker 配置,没有 consumer-group 再均衡,没有 DLQ 要清空——集群拓扑就是此刻碰巧在跑的那些进程。
pipe 不追踪谁订阅了。它只是为每个事件等 n 个连接。两端把 n 调高,下个事件就等新的人头数。没有要被破坏的成员状态,因为根本没有成员关系。
下面每一行都是当协议本身就是 broker 时消失的一类工作。无需配置基础设施,无需学抽象——只有 curl 和 ?n。
没有 SQS,没有 Kafka 集群,没有 RabbitMQ exchange。pipe 就是队列,而它一次只为一条消息存活。
没有 offset、没有 commit、没有再均衡。pipe 把主体保留到 n 个套接字接入——这就是全部协调模型。
如果发送方在 n 个读者就绪前断开,pipe 会超时(5 分钟 TTL),Stripe 会重试。没有要照看的毒消息桶。
订阅者是 curl 循环。发送方是 curl。协议是 HTTP。任何能命中 URL 的东西都能加入这个集群。
慢读者只压自己的连接。其他十一个继续以全速推流。整个机群没有头部阻塞。
最后一个读者断开的瞬间,主体就消失了。无需设保留策略,无需排日志压缩,无需写 GDPR 删除请求作业。
十二个订阅者,一个 URL,没有 broker。
下面这些 broker,是在 HTTP 扇出还不是一个查询参数之前你必须装的东西。右侧是替代它们的——一个路径、一个 n、十二个互不知道彼此组成集群的 curl 进程。
十二个接收方用同一个 n GET 同一个路径。pipe 就是 broker——并在最后一个读者挂断的瞬间忘掉这条消息。
如果你为了把一个 webhook 广播给 N 个消费者去抓下面任何一个,pipe 模型用两次 curl 调用就能干同样的活——一边是发送方,另一边是接收方。
你以前要花一个 sprint 才能写出的扇出,现在是一个查询参数。追加 ?n=12 然后发布。