← 返回信息流
AI 资讯Hacker News·3 小时前

纯Scheme实现Erlang风格Web服务器

原标题:A Erlang style pure Scheme Webserver and further

速览

该项目用纯Scheme语言实现了类似Erlang的actor并发风格的Web服务器。它展示了函数式编程在系统编程中的新可能性,并探索了不同语言范式的融合。这种尝试对于语言设计和并发编程研究具有实验意义。

AI 深度解读

背景

在 Web 服务器领域,传统架构通常面临两个核心挑战:容错性和在线更新。大多数服务器采用进程或线程隔离,但崩溃恢复往往依赖外部编排(如 Kubernetes),而热更新则需要复杂的负载均衡策略。Erlang/OTP 的 Actor 模型提供了一种优雅的解决方案——通过轻量级进程、监督树和消息传递实现自愈和热交换,但这一范式长期局限于 Erlang 生态系统。Igropyr 项目的出现,试图将 Erlang 风格的核心机制移植到 Scheme 语言中,结合 libuv 事件循环,打造一个纯 Scheme 的 Web 服务器,兼具函数式简洁性与工业级可靠性。

核心内容

Igropyr 是一个基于 Chez Scheme 的纯 Scheme Web 服务器,采用 Erlang 风格的 Actor 模型,底层使用 libuv 事件循环,遵循 MIT 协议开源。它的核心设计围绕四个支柱展开:容错性热交换远程重试环以及基于续延的 Web 编程

1. 容错性(Fault Tolerance)

每个请求都在一个被监督的工作池中运行。处理器不主动防御错误——它们可以崩溃,系统会自动恢复。具体机制:

  • 崩溃的工作进程被立即替换;任务会在新进程上重试最多 3 次,之后客户端才收到错误。
  • 工作进程如果卡住超过 30 秒(即使是 CPU 密集循环),会被强制杀死并替换:抢占式调度确保没有任何循环能冻结整个服务器。
  • 发送了一半的请求只会阻塞其自身的读取进程,该进程会因超时被回收,其他连接不受影响。

开发者只需编写“快乐路径”,监督者负责处理异常。

2. 热交换(Live Systems)

可以在运行中的服务器上替换处理器或单个路由。监听器、已建立的连接和工作池保持不变;正在进行中的请求继续使用旧代码执行。路由存储在一个可变的注册表中,位于工作池之后。重新注册某个路径会原子性地替换它(对下一个请求生效);http-swap! 则可以用相同方式替换整个处理器。

结合优雅关闭(http-shutdown! 会排空正在处理的任务)和 SO_REUSEPORT 多进程监听,零停机操作成为默认行为,而非额外的工程任务。

3. 远程重试环(The Remote Retry Ring)

当重试耗尽或工作进程被杀死时,Igropyr 不只返回 500 错误——它可以通过保持打开的连接,精确告知客户端发生了什么。on-failure 钩子会在卡住的工作进程被杀死后(确保无残留执行)返回结构化的故障信息。故障状态是确定的:

  • crash:重试用尽;客户端可以修改参数重新提交,或执行补偿操作。
  • stuck:在中途被杀死;客户端可携带状态重新提交,或回滚。

故障发生时 keep-alive 连接保持存活,客户端可以在同一连接上重新提交,获得全新的重试轮次。通过缩短 stuck-ms,原本用户要盯着转圈 30 秒的场景,现在可以在相同时间内完成几次有信息的重试——故障在 UI 层面变得透明。

4. 基于续延的 Web 编程(Web Programming with Continuations)

多请求的对话流程(例如向导、预定、转账)作为一个绿色进程运行。它的局部绑定就是会话状态,包括会话存储无法持有的东西:例如跨回合的开放数据库事务。

“用户处于确认步骤”意味着进程就停在那行代码上。代码顺序无法表达的状态根本不可能出现——无需状态机,无需防范重放。

gone 保证:进程因任何原因死亡(崩溃、TTL、完成)都会注销自身,后续的恢复请求会返回 gone。死进程 = 断开的连接 = 数据库自动回滚:gone 证明没有提交任何东西。结合上述故障代码,客户端总能知道确定的服务器状态——形成一个完整的远程事务环。

基础架构

  • 每一行代码都是 Scheme(R6RS 库,.sc 文件),没有 C 粘合层。libuv、zlib 和用于 MySQL 认证的加密通过 Chez 的 FFI 直接调用。全程序编译将框架和应用折叠成一个优化过的二进制文件。
  • 绿色进程:通过 spawn/send/receivelinkmonitor 实现,附带进程注册表、gen-server 和 PubSub。一个 OS 线程,抢占式调度,纯消息传递——无共享状态,无锁。
  • 一个事件循环支撑数千个 parked 进程。DNS、文件读取和数据库往返会暂停调用进程,而非线程。包含非阻塞 HTTP/WebSocket 客户端和 Redis/MySQL 驱动。
  • 性能数据:在笔记本电脑上超过 120k req/s(keep-alive),ab -c 500 下 0 个失败请求,卡住进程池完全恢复仅需 ≤35 秒,仅使用 1 个 OS 线程。

包含的一切

核心/框架分离,类似 Node 和 Express:核心只暴露一个入口点 (http-listen port (lambda (req res) ...));附带的 (igropyr express) 层(create-appapp-getsend-json! 等)是可选的,其他框架也可以建立在相同核心上。另外还包括 WebSocket、流式响应与 SSE、OTP 构建块(gen-server、进程注册表、PubSub)、JSON 安全解析器、S表达式 RPC、表单与 Cookie、中间件套件(Cookie 会话、CORS、安全头、访问日志)、分块传输编码等。

关键要点

  • Erlang 风格 Actor 模型:采用轻量级绿色进程、消息传递、监督树,但完全用 Scheme 实现。
  • 自动容错:崩溃自动重试(最多 3 次),卡住超过 30s 自动杀死,无需外部编排。
  • 抢占式调度:单线程内即使有无限循环也不会冻结服务器。
  • 热代码替换:运行时替换路由或整个处理器,不影响现有连接,支持零停机。
  • 结构化故障反馈on-failure 钩子返回 JSON 故障码(crash/stuck),保持 keep-alive 连接,客户端可智能重试。
  • 续延驱动对话:多请求流保持进程状态,包括数据库事务;进程死亡即回滚,杜绝脏数据。
  • 纯 Scheme 栈:无 C 扩展,通过 FFI 调用 libuv 和系统库,全程序编译为单一二进制。
  • 单线程高并发:支持数千个并发连接,120k+ req/s 性能。
  • 内建 OTP 原语:gen-server、进程注册表、PubSub(自动清理死订阅者)。
  • 完整 Web 能力:HTTP/WebSocket/SSE、流式响应、JSON/S-exp RPC、表单解析、Middleware 等。

意义与影响

Igropyr 展示了一种将 Erlang/OTP 的可靠性模式嵌入到几乎最纯粹的 Lisp 方言中的可行路径。它的意义不仅在于技术实现,更在于对 Web 服务器设计哲学的反思:

  1. 容错内建而非附加:传统服务器将容错视为运维责任(K8s、负载均衡器等),而 Igropyr 将其作为语言级特性——崩溃恢复、自动重试、故障协议,这一切都在应用代码中透明地实现。这降低了系统的运营复杂度,尤其对于微服务和小型部署。

  2. 热更新默认而非额外:零停机部署在大多数系统中需要代理层或蓝绿部署,而 Igropyr 的原生热交换机制使得代码升级成为 API 调用,极大简化了持续交付流程。

  3. 续延式 Web 编程的复兴:Web 编程长期受限于无状态请求-响应模型,开发者不得不借助 Session、事务管理等绕开状态问题。Igropyr 的续延方式让开发者可以直接用过程式思维编写多步交互,同时保证事务一致性——这是对 Web 编程模型的激进回归,但可能为复杂对话型应用(电商结账、多步表单)提供更简洁的解决方案。

  4. **纯 Scheme 的实用

查看原文 →igropyr.com