Web開発
Erlangスタイルの純粋なScheme製Webサーバーとその他
A Erlang style pure Scheme Webserver and further (igropyr.com)
要約
Igropyrは、ErlangのようなアクターモデルとScheme言語を組み合わせた、フォールトトレラントでホットスワップ可能なWebサーバーフレームワークです。クラッシュしたワーカーを自動的に再起動・リトライし、実行中のコードを再起動なしで更新できる機能を提供します。また、対話的な処理をプロセスとして扱い、トランザクションの整合性を保証する「会話」の概念も導入しています。
全文翻訳
01 · 耐障害性 Let It Crash
すべてのリクエストは、監視されたワーカープール内で実行されます。ハンドラーは防御せず、クラッシュし、システムが回復します。
クラッシュは自己修復
クラッシュしたワーカーは即座に置き換えられます。タスクは、クライアントがエラーを見る前に最大3回、新しいワーカーでリトライされます。30秒以上ハングしたワーカー(CPUを消費するループでさえ)はキルされ、置き換えられます。プリエンプティブスケジューリングにより、サーバーがフリーズすることはありません。半分送信されたリクエストは、自身のリーダープロセスのみを駐車し、タイムアウトによって回収されます。他の接続は決して影響を受けません。
ハッピーパスを記述してください。スーパーバイザーが悲しいパスを所有します。
(app-get app "/crash" (lambda (req res) ;; ワーカーはクラッシュし、スーパーバイザーは ;; 新しいワーカーでリトライします -- プールは自己補充されます (raise 'handler-crashed)))
(app-get app "/stuck" (lambda (req res) ;; ホットループはシステムをフリーズできません。プリエンプション ;; はサービスを継続し、タイマーはこのワーカーをキルします (let loop ((n 0)) (loop (+ n 1)))))
02 · ライブシステム
ホットコードスワッピング
実行中のサーバー上でハンドラー、または単一のルートを置き換えます。リスナー、オープン接続、ワーカープールは稼働したままです。インフライトリクエストは古いコードで完了します。
再起動なしでのデプロイ
ルートは、プールの背後にある変更可能なレジストリに格納されています。パスを再登録すると、次のリクエストのためにアトミックに置き換えられます。http-swap!は、ハンドラー全体を同じ方法で置き換えます。グレースフルシャットダウン(http-shutdown!はインフライトワークをドレインします)およびSO_REUSEPORTマルチプロセスリスニングと組み合わせることで、ゼロダウンタイム運用はプロジェクトではなく、デフォルトです。
(app-get app "/version" (lambda (req res) (send-text! res "v1")))
;; LIVEサーバーで /upgrade をヒットしてください:
(app-get app "/upgrade" (lambda (req res) (app-get app "/version" ;; 再登録 = (lambda (req res) ;; ホットリプレース (send-text! res "v2 (hot swapped)"))) (send-text! res "upgraded")))
03 · リモートリトライリング
障害はプロトコルを話します
リトライが枯渇したとき、またはスタックしたワーカーがキルされたとき、Igropyrは単に500を返すだけでなく、接続を開いたままクライアントに何が起こったかを正確に伝えることができます。
最初にキルされ、後に通知される
on-failureフックは、スタックしたワーカーが死んだ後に構造化された障害に応答します。そのため、クライアントがスタックしたと聞くとき、インフライトの実行は残っていません。状態は確定しています。
crash — リトライが枯渇しました。変更されたパラメータで再送信するか、補償してください。
stuck — インフライト中にキルされました。状態を運んで再送信するか、ロールバックしてください。
キープアライブは障害を生き延びるため、クライアントは同じ接続で再送信し、新しいリトライラウンドを取得します。stuck-msを短縮すると、かつて30秒間スピナーを見つめていたユーザーが、同じ時間内に複数の情報提供付きリトライを通過できるようになります。UIでは障害が見えなくなります。
(app-listen app 8080 `((stuck-ms . 3000) ;; ファストフェイル (check-ms . 1000) (on-failure . ,(make-fault-handler))))
;; クライアントは受信し、接続は維持されます:
;; {"fault":"crash","attempts":4,"retryable":true}
;; {"fault":"stuck","elapsed-ms":3012,...}
;; 設定解除?プレーンな500はそのまま残ります。ゼロブレーク。
04 · コンティニュエーションによるWebプログラミング
対話はプロセスです
マルチリクエストの対話(ウィザード、予約、転送)は、1つのグリーンプロセスとして実行されます。そのローカルバインディングは、セッションストアが決して保持できないものを含む会話の状態です。開いているデータベーストランザクション、ラウンドをまたぐもの。
制御フローはプログラムテキストです
「ユーザーは確認ステップにいます」とは、プロセスがその行で駐車していることを意味します。コードが表現できないステップ順序は発生しません。間違える状態マシンはなく、防御するリプレイもありません。
gone保証:クラッシュ、TTL、完了のいずれかの理由による死はプロセスを登録解除し、後続の再開はgoneを返します。デッドプロセス=切断された接続=データベース自体がロールバックされました。goneは何もコミットされなかったことを証明します。上記の障害コードと組み合わせることで、クライアントは常に確定的なサーバー状態を知ることができます。完全なリモートトランザクションリングです。
(conversation-start! (lambda (req suspend!) (let ((tx (begin-tx!))) ;; リクエストをまたいでライブ (guard (e (#t (rollback! tx) (raise e))) (let ((req2 (suspend! confirm-page))) (commit! tx) done)))) req)
(conversation-resume! id req) ;; => 返信 | 'gone
;; 'goneは、ロールバックされたことを意味します。保証されています。
基盤
それが依存するもの
λ 純粋なChez Scheme
すべての行はSchemeです。R6RSライブラリは.scで、Cシムはありません。libuv、zlib、MySQL認証用の暗号はChezのFFIを介して直接アクセスされます。全プログラムコンパイルは、フレームワークとアプリケーションを1つの最適化されたバイナリに折りたたみます。
✉ Erlangスタイルのアクター
spawn / send / receive、linkおよびmonitor、プロセスレジストリ、gen-serverおよびPubSubを備えたグリーンプロセス。1つのOSスレッド、プリエンプティブスケジューリング、純粋なメッセージパッシング。共有状態なし、ロックなし。
⚡ libuv上の非同期
1つのイベントループが数千の駐車されたプロセスに電力を供給します。DNS、ファイル読み取り、データベースラウンドトリップは、呼び出し元プロセスを駐車し、スレッドを駐車しません。非ブロックHTTP/WebSocketクライアントおよびRedis/MySQLドライバーが含まれています。
120k+req/s、キープアライブ、ラップトップ
0失敗リクエスト (ab -c 500 ≤35s)
1つのOSスレッドからの完全な回復
すべてが含まれています
NodeとExpressのように、コア/フレームワークの分割。コアは1つのエントリポイント(http-listen port (lambda (req res) ...)))を公開します。バンドルされた(igropyr express)レイヤー(create-app, app-get, send-json!, ...)はオプションであり、同じコア上に代替フレームワークを構築できます。
グリーンプロセス
1つのOSスレッドでスケジュールされる数千の軽量プロセス。コンティニュエーションベースのコンテキストスイッチングとプリエンプションにより、CPUを消費するハンドラーでさえシステムをフリーズさせることはできません。
純粋なメッセージパッシング
spawn / send / receive / link / monitor。プロセス間の共有状態はありません。
デフォルトでフォールトトレラント
スーパーバイザーの背後にある固定ワーカープール。クラッシュしたワーカーは置き換えられ、タスクはリトライされます(最大3回、その後クライアントは500を受け取ります)。30秒以上スタックしたワーカーはキルされ、置き換えられます。遅い、または半分送信されたリクエストは、常に自身のリーダープロセスのみをブロックします。
障害フック(リモートリトライリング)
リトライが枯渇したとき、またはスタックしたワーカーがキルされたとき(最初にキルされるため、実行中の実行はありません)、オプションのon-failureハンドラーは、同じキープアライブ接続上で、プレーンな500の代わりに構造化されたJSON障害に応答します。クライアントは再送信します(変更されたパラメータ、運ばれた状態)そして新しいリトライラウンドを取得します。設定されていない場合、プレーンな500はそのまま残ります。
会話(プロセスごとに対話)
マルチリクエストの対話は、ラウンドをまたいでライブ状態(開いているデータベーストランザクションさえも)を保持する1つのグリーンプロセスとして実行されます。suspend!は応答して駐車し、conversation-resume!は続行し、クラッシュ、TTLのいずれかの理由による死は保証されたロールバックを意味します。後続の再開はgoneを取得します。
ホットコードスワッピング
ライブサーバー上でハンドラー(または個々のルート)を置き換えます。リスナー、オープン接続、ワーカープールは稼働したまま、インフライトリクエストは古いコードで完了します。
WebSocket RFC 6455
同じポートでアップグレードします。各ソケットは独自のグリーンプロセスであるため、サーバープッシュは単なるメッセージ送信です。
ストリーミング応答とSSE
res-begin!/res-write!/res-end!を介したチャンク化された応答ボディ。サーバーセントイベントヘルパーが上にあります。
OTPビルディングブロック
gen-server(call/cast/info)、プロセスレジストリ(register/whereis)、および自動クリーンアップ機能付きトピックPubSub。
JSON
安全な再帰下降パーサー(readなし。完全なエスケープとサロゲート処理)とライター。
S式RPC
ピアもSchemeの場合、コーデックはありません。(igropyr sexpr)は安全なホワイトリスト化されたパーサー(readなし、深さ制限あり)であり、app-rpc / send-sexpr! / ws-send-sexpr! / sse-send-sexpr!はメッセージあたり1つのデータムを運びます。正確な比率とbignumはそのまま転送されます。
フォームとクッキー
req-formはURLエンコードおよびマルチパートボディ(ファイルアップロードを含む)を解析します。req-cookie / set-cookie!
ミドルウェアスイート
Cookieセッション(gen-serverストア、CSPRNG sid)、CORS(プリフライト付き)、セキュリティヘッダー、アクセスロガー。
チャンク転送エンコーディング
Transfer-Encoding: chunked リクエストボディは透過的にデコードされます。
非ブロックRedisおよびMySQLクライアント
純粋なScheme、同じイベントループ。呼び出し元はグリーンプロセスを駐車しますが、OSスレッドはサービスを継続します。MySQLには自己修復接続プールが付属しています。
非ブロックHTTP & WebSocketクライアント
outbound http-get / http-pos