从 Python 库到全球存储平台:Habitat 如何支撑 10 亿 ChatGPT 用户

2026-09-11 30 预计阅读时间: 1 分钟
来源: openai.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

当一个存储组件只服务少量应用时,Python API、单区域部署和简单的对象读写通常已经足够。但随着 ChatGPT 用户规模扩大到 10 亿、请求峰值达到每秒 2200 万次,存储系统面对的就不再只是“能不能保存数据”,而是全球流量、容量增长、故障隔离和开发效率的综合考验。

OpenAI 分享的 Habitat 演进路径,核心变化是:它从一个 Python 库逐步发展为全球分布式存储平台。这个过程值得关注的地方,不只是最终的吞吐数字,更是如何让一个早期开发工具逐步承担平台级职责。

从库到平台,变化在哪里

一个 Python 库通常关注调用体验:开发者如何创建对象、写入数据、读取数据,以及如何处理错误。平台则必须额外回答一组运营问题:

  • 数据应该落在哪个区域,是否需要跨区域复制?
  • 单个依赖或存储节点发生故障时,业务能否继续运行?
  • 请求量快速上升时,如何扩展而不让应用团队重复改造?
  • 如何观察延迟、错误率、容量和热点键?
  • 不同类型的数据是否需要不同的一致性、保留周期和成本策略?

因此,Habitat 的演进可以理解为抽象层次的变化。早期抽象可能是“调用一个 Python 函数保存数据”,平台化之后,抽象变成了“通过统一接口申请并使用一种具备位置、可靠性和运维能力的存储服务”。

这里的重点不是把所有能力塞进一个更大的 SDK,而是把跨应用反复出现的能力集中起来:路由、扩展、故障处理、监控和容量管理由平台负责,业务代码只表达数据模型和访问意图。

22M 请求每秒带来的工程约束

每秒 2200 万次请求并不意味着每次请求都传输同样大小的数据,也不意味着所有数据都需要相同的可靠性。实际设计时,至少要把几个维度拆开:

请求吞吐与数据吞吐

小对象、高频元数据请求和大对象上传的瓶颈不同。前者更容易受连接数、序列化和索引访问影响,后者则更容易受网络带宽、分片和后台复制影响。平台需要分别观察请求数、字节数、P50/P95/P99 延迟,以及读写比例。

控制面与数据面

创建存储命名空间、配置策略、分配区域属于控制面;真正的数据读写属于数据面。将两者混在同步请求中,会让配置变更和高峰期数据流量互相影响。可以让控制面负责生成版本化配置,数据面根据本地缓存执行低延迟路由。

热点与均匀扩展

即使总容量和平均流量都能扩展,单个热门键、租户或分区仍可能成为瓶颈。实践中可以采用带版本或哈希分片的键设计,并限制单个租户的并发和速率。热点保护不是单纯增加机器,而是把流量分散到更多可独立扩展的分区。

故障不是例外路径

全球分布式系统必须预设区域不可用、网络分区、依赖超时和副本落后等情况。调用方需要知道哪些错误可以重试,哪些错误必须立即返回;平台则要避免重试风暴,通过超时、指数退避、熔断和幂等请求保护后端。

一个可运行的本地存储接口示例

下面的示例只使用 Python 标准库,模拟一个最小的对象存储网关。它不是 Habitat 的实现,也不能代表生产级分布式存储;它用来展示一个平台接口应如何明确对象键、幂等写入和健康检查。将代码保存为 storage_demo.py 后即可运行。

from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlparse
import hashlib
import json

OBJECTS = {}


class StorageHandler(BaseHTTPRequestHandler):
    def send_json(self, status, payload):
        body = json.dumps(payload).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self):
        path = urlparse(self.path).path
        if path == "/healthz":
            self.send_json(200, {"status": "ok"})
            return

        if path.startswith("/objects/"):
            key = path.removeprefix("/objects/")
            value = OBJECTS.get(key)
            if value is None:
                self.send_json(404, {"error": "not_found"})
                return
            self.send_response(200)
            self.send_header("Content-Type", "application/octet-stream")
            self.send_header("Content-Length", str(len(value)))
            self.end_headers()
            self.wfile.write(value)
            return

        self.send_json(404, {"error": "unknown_path"})

    def do_PUT(self):
        path = urlparse(self.path).path
        if not path.startswith("/objects/"):
            self.send_json(404, {"error": "unknown_path"})
            return

        key = path.removeprefix("/objects/")
        size = int(self.headers.get("Content-Length", "0"))
        value = self.rfile.read(size)
        digest = hashlib.sha256(value).hexdigest()

        # A repeated PUT for the same key replaces the value deterministically.
        OBJECTS[key] = value
        self.send_json(200, {"key": key, "bytes": len(value), "sha256": digest})


if __name__ == "__main__":
    server = ThreadingHTTPServer(("127.0.0.1", 8080), StorageHandler)
    print("storage demo listening on http://127.0.0.1:8080")
    server.serve_forever()

运行和验证:

python3 storage_demo.py

curl -fsS http://127.0.0.1:8080/healthz
printf 'hello habitat\n' | curl -fsS -X PUT \
  --data-binary @- http://127.0.0.1:8080/objects/greeting
curl -fsS http://127.0.0.1:8080/objects/greeting

要把这个示例推进到生产环境,至少还需要持久化介质、认证授权、请求限流、校验和校验、版本控制、超时策略、指标采集以及多副本或跨区域复制。尤其不能把进程内字典当作可靠存储:进程重启会丢数据,多实例之间也不会共享状态。

平台化时应优先稳定的边界

统一客户端,不暴露底层拓扑

应用不应该依赖某个具体节点、机架或区域的地址。客户端或网关应根据租户、对象键和策略完成路由。底层拓扑变化时,业务只需要继续使用稳定的逻辑命名空间。

显式声明数据语义

“写入成功”需要有清晰定义:是本地落盘、一个副本确认,还是跨区域副本确认?“读取最新数据”也需要对应的一致性等级。把这些语义写进 API 和配置,比让每个应用自行猜测更容易长期维护。

让可观测性成为接口的一部分

平台至少应提供按租户、区域、操作类型和状态码拆分的指标。请求追踪中还应包含逻辑存储空间、分区和副本状态等信息。没有这些维度,22M 请求每秒只会变成一个无法定位问题的总数字。

将容量和成本纳入设计

高可靠性通常意味着更多副本、更大的跨区域流量和更复杂的后台任务。平台需要提供生命周期、压缩、冷热分层或保留策略等机制,并让业务团队能看见自己的容量和成本,而不是把所有开销隐藏在一个模糊的“存储服务”价格里。

采用时的检查清单

在把类似 Habitat 的平台能力接入新的工作负载前,可以检查:

  • 是否定义了对象键、租户边界和最大对象大小?
  • 是否明确读写一致性、持久性和跨区域恢复目标?
  • 重试是否幂等,超时后是否可能造成重复写入?
  • 是否有按租户和区域拆分的延迟、错误率、容量指标?
  • 热点键、突发流量和单租户失控时会发生什么?
  • 区域故障或副本落后时,业务是降级、阻塞还是切换?
  • 是否有数据删除、保留周期和成本归属规则?

Habitat 从 Python 库走向全球分布式平台,说明大规模基础设施的难点不在于单次 API 调用,而在于把可靠性、扩展性和运维复杂度收敛到可复用的系统边界中。对其他团队而言,最现实的借鉴路径是先统一数据语义和观测指标,再逐步抽象路由、故障处理与容量管理;不要仅仅因为流量数字很大,就提前堆叠所有复杂组件。


相关推荐