バツイチとインケンのエンジニアブログ
プログラムやプログラムじゃないこと

actix_web は Actorモデルでどのようにwebリクエストを捌いているのか

2018-12-20
書いた人 : バツイチ
カテゴリ : rust | タグ : actix_web, tokio
QiitaのRust Advent Calendar 2018 20日目の記事です。

actix_web は Actor モデルを採用した Rust の web frameworkです。
「Actor モデルでwebリクエストを捌く」仕組みを雑に想像すると、

TCPリスナーがHTTPリクエストをメールボックスに貯め、それをワーカーであるActorが拾ってレスポンスを返す?

この仕組だと複数のActorで1つのメールボックスを共有する機構が必要になってきます。
しかし、Actorシステムを提供する actix にはこの機構はなさそうに見えます。
それに、恐らくこの仕組みでパフォーマンスを出すのは難しいでしょう。

そこで、actix_web がどのようにHTTPリクエストを捌いているのか、ソースコードを追ってみることにしました。
※ 読み間違え等ありましたらコメント頂けると大変嬉しいです… 🙏

actix_web の構成

actix_web の依存関係を図示するとおおよそ下記のようになっています(バージョン0.7.16時点)。

図1

actix エコシステムとしては、

  • actix_web本体: 高次HTTPレイヤーを処理
  • actix: Actorシステムを提供
  • actix_net: TCPコネクションを処理

という役割をそれぞれが担っていますが、 非同期イベントの管理には tokio , mio, futures の 0.1系 を利用しています。


では、actix_web のシンプルなサンプル・コードをエントリポイントとして、ソースコードを読んでいきます。

TCPコネクションのリスニング開始

図2

actix_web で HTTPサーバを開始するには、 actix_web::server::HttpServer の start メソッドを実行します。

ここがTCPコネクション・リスニングのエントリ・ポイントになるのですが、 TCPソケットの生成自体は bind メソッド内で行われます。
よって当然ですが、 bind メソッドの呼び出しを行わずに start してしまうと、 panic します。

start メソッドは下記のようになっています。

図3

actix_web::server::HttpServer.start は 内部的に actix_net::server::Server の start を実行しています。

socket.handler.register (図3の2) では HTTPサービス・ファクトリの生成とトークンによるTCPリスナーとの紐付けを行っているのですが、一旦ここは割愛して、 actix_net::server::Server の start (図3の1)を見ていきます。

ワーカー・スレッドの生成

actix_net::server::Server.start では、actix_web::server::HttpServer.workers に指定された数のワーカー・スレッドを開始します。

図4

ワーカー数として指定された数だけ実行している start_worker (図4の3)で何をやっているかというと、ストリーム用インメモリ・チャネルを開き、 WorkerClient に送信側を、 Worker に受信側を渡しています。

図5

start_worker メソッドは、送信側を持つ WorkerClient を戻り値として返します。
各ワーカーとの通信チャネルの送信側をメインスレッドが保持し、チャネルへのコマンドを通じてワーカーを制御しているわけですね。

start_worker メソッドのもう1つの特筆すべき点として、 Arbiter::new の中で Worker::start を行っていることです。

Arbiter はイベントループ・コントローラで、 new されるとスレッドを生成しイベントループ・コントローラを起動します。
ワーカー 1つにつき、 1つの Arbiter が生成されているので、マルチスレッドでワーカー数分のイベントループが動作することになります。

Arbiter の中身も見ていきたいところなのですが、話が広がりすぎてしまうので端折ります💦
(tokioランタイム上でイベントループを制御しています。)

※ Arbiter については下記の記事で詳しく説明されていました。
Arbiter | Actor model by Rust with Actix

受信側を渡された Worker の内部

Woker::start では、socket.handler.register (図3の2)で生成した HTTPサービス・ファクトリを使って、 HTTPサービスの生成を行っています。

図6

TCPリッスン

続いて、図4の4, self.accept.start を見ていきます。

self.accept.start は、workers を受け取っていますが、これは送信チャネルを持つ WorkerClient です。

self.accept.start は AcceptLoop.start を指していますが、 結果 Accept.start が呼び出されるので、 こちらの方のコードを見ます。

図7

TCPコネクション受付用に、もう1つスレッドを起動しています。
このスレッドはTCP接続リクエスト・ポーリング専用スレッドです。

図7の5, accept.poll の中身を見てみます。

図8

accept.poll の中では、 システム・イベントをポーリングし、TCPリスナーから TCPストリームを受け取ると accept_one メソッドを実行します。

accept_one では、ワーカーのコマンド・チャネルに受け取ったTCPストリームを送信します。

図9

これにより、ワーカーのサービスがTCPストリームを解釈してレスポンスの生成を行います。

ここまでのシーケンスをまとめてみると

こんな感じになります。

図10

TCPリスニングのコア部分においては、ほとんどActorシステムが使われておりませんでした💦
※ 唯一、 Workerを起動する Arbiter が Actor であることくらいでしょうか…

余談ですが・・・OSプロセスから見たactix_web

スレッド観点から見ると、 actix_web はメイン・スレッドの他に (ワーカー数 * ワーカー・スレッド)+1(TCP接続ポーリング・スレッド) 数のスレッドが起動します。
例えば、ワーカー数を 4 とすると、 1(メイン) + 4(ワーカー・スレッド) + 1(TCP) = 6 スレッド起動することになります。

actix_web の簡単なサンプルを実行して確認してみます。

サンプル・ソースコード

公式READMEのexampleに .workers(4) を追加しています。

extern crate actix_web;
use actix_web::{http, server, App, Path, Responder};

fn index(info: Path<(u32, String)>) -> impl Responder {
    format!("Hello {}! id:{}", info.1, info.0)
}

fn main() {
    server::new(
        || App::new()
            .route("/{id}/{name}/index.html", http::Method::GET, index))
        .keep_alive(server::KeepAlive::Timeout(0))
        .workers(4)
        .bind("127.0.0.1:8080").unwrap()
        .run();
}

これを起動すると、 メインを含む6スレッド立つはずです。
ps -L で実行スレッドを確認すると、たしかに6スレッド起動しています。

図11

次に、 lsof でプロセスの詳細を確認します。

図12

名前付きパイプ(FIFO Read & Write)と、eventpoll の組み合わせが6個使われています。
1スレッドにつき、 FIFO(r), FIFO(w), eventpoll の3つが使われると考えて良さそうです。

つまり、 workers に指定するワーカー数は OS のulimit の 1/3 以下でなくではならないということですね。

名前付きパイプと、eventpollがスレッド数分使われるのは、actix_web が tokio ランタイムをベースにしているからです。

tokioコンテキストにおいてスレッドを実行した場合とRustの標準ライブラリのみでスレッドを実行した場合とで、プロセスの比較実験を行ってみました。

tokio

図13

標準ライブラリ

図14

tokio ではスレッド数のFIFO(r), FIFO(w), eventpollが使用されているのに対し、標準ライブラリではスレッド毎のFDはありません。
(こういう違いがありますよって話でした。)

スレッドの実験に使用したソースコードは下記に掲載しています。
x1-/rust_comparison_of_threads

まとめ

冒頭の仮説

TCPリスナーがHTTPリクエストをメールボックスに貯め、それをワーカーであるActorが拾ってレスポンスを返す?

は、全く異なっておりました😅
実際には、TCPリスナー は ストリーム・チャネルを介してTCPストリームをHTTPサービス・チェインに送信し、処理していました。
Actorシステム の出番はもう少し高次のレイヤーになります。
しかし、 futures::sync::mpsc::unbounded の使い方など、個人的にはいろいろ発見があり勉強になりました。

このエントリーをはてなブックマークに追加
Tweet

← 2018年版: Jupyter Notebook で Scala を動かす
レーザー・カッターで自作キーボードのケースをつくった時の失敗談 →

 

最近書いた記事

  • Ryzen7 3800XT でmini ITXオープンフレームPCを作る
  • Pythonで機械学習入門 競馬予測
  • HP ENVY 15 クリエイターモデルレビューとRAID0解除
  • JRA-VAN データラボを使って、競馬データを収集する
  • Surface Pro 3 にubuntu18.04を入れる

カテゴリー

  • Android
  • Apache Flink
  • API
  • AWS
  • bazel
  • BigQuery
  • Cassandra
  • Docker
  • Druid
  • Elasticsearch
  • Git
  • Golang
  • gradle
  • HDFS
  • JavaScript
  • jvm
  • Linux
  • MongoDB
  • MySQL
  • Nginx
  • Nodejs
  • PaaS
  • PHP
  • Python
  • RabbitMQ
  • Raspberry Pi
  • React Native
  • Redis
  • Riak
  • rust
  • scala
  • Scheme
  • SEO
  • solr
  • Spark
  • spray
  • Sublime Text
  • Swift
  • Tableau
  • Unity
  • WebIDE
  • Wordpress
  • Youtube
  • ひとこと
  • カンファレンス
  • スケジューラ
  • マイクロマウス
  • 広告
  • 技術じゃないやつ
  • 株
  • 機械学習
  • 競馬
  • 自作キーボード
  • 自然言語処理

アーカイブ

  • 2021年4月
  • 2021年2月
  • 2021年1月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年10月
  • 2019年9月
  • 2019年8月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年11月
  • 2018年9月
  • 2018年5月
  • 2018年3月
  • 2018年2月
  • 2017年9月
  • 2017年8月
  • 2017年6月
  • 2017年4月
  • 2017年3月
  • 2017年1月
  • 2016年10月
  • 2016年9月
  • 2016年8月
  • 2016年6月
  • 2016年5月
  • 2016年4月
  • 2016年3月
  • 2016年2月
  • 2016年1月
  • 2015年12月
  • 2015年11月
  • 2015年10月
  • 2015年9月
  • 2015年8月
  • 2015年6月
  • 2015年5月
  • 2015年2月
  • 2015年1月
  • 2014年12月
  • 2014年11月
  • 2014年9月
  • 2014年6月
  • 2014年5月
  • 2014年3月
  • 2014年2月
  • 2014年1月
  • 2013年12月
  • 2013年11月
  • 2013年10月
  • 2013年9月
  • 2013年8月

書いた人

  • バツイチちゃん
  • インケンくん

このブログについて

エンジニアとしての考え方が間逆な2人がしょーもないこと書いてます。

バツイチ

アイコン

IT業界で働くエンジニアです。名前の通りバツイチです。
理論や抽象的概念が好きだけど人に説明するのが下手。

インケン

アイコン

バツイチちゃんと同じ業界で働いています。
理論とか開発手法とかは正直どうでもよくて、
生活する上で役に立つことに使いたい

Copyright 2025 バツイチとインケンのエンジニアブログ