C#
Azure
docker
.NETCore
kestrel

アーキテクトの目から見た最近の.NET Core 2とC#

More than 1 year has passed since last update.

最近になって言語やフレームワーク選定、マイクロサービスでの活用、Dockerなどのコンテナでの活用、大量のREST APIアクセスなどの観点で、お客様などから相談を受ける機会が増えたので自分の為にも整理してみる事にした。

OSS

未だにプロプライエタリソフトウェアと思われる事も残念ながら多いのだが、.net coreは、MITライセンス、Apacheライセンスで公開されているOSS、Microsoftだけでなく、RedhatやJetBrainsも開発に協力している。
気に入らないことがあれば修正する事もできるし、貢献する事もできる。

https://github.com/dotnet/core/blob/master/roadmap.md

言語

一度チームの仲間が言語を習得した後、今後に生かせるかどうか、プロジェクト離脱や転職後でもその言語を生かせるかなど様々な事を考えている。
開発後のソフトウェア資産を維持する事から考えれば、ECMA Internationalや国際標準化機構 (ISO) などの標準化も大事な要素になる。
サーバサイド、Unity、Unreal、Xamarinによるモバイルアプリ、Windows向けデスクトップアプリ(UWP)、Consoleアプリなど多少の素性の違いはあるが、C#ではあるので、様々なシーンでも利用ができる。

開発

快適な開発環境は、開発者の生産性向上の為に不可欠だし、そもそも開発環境はこうじゃなきゃダメってルールを作るのは面倒な事が多い。
Windowsだけで開発ができると思いこまれている節があるが、SDK一つでMacやLinuxでも開発ができる。

https://www.microsoft.com/net/core#windowscmd

重厚長大なVisual StudioのようなIDEを使うこともできるし、VS Codeのような軽量IDEでも良い、JetBrainsのツールも使える、EmacsやViなどでも良い。Visual Studio for Macなんてのもある。
好みに合わせて、好きなエディタを用いる事ができる。

アプリケーションポータビリティ、クロスプラットフォーム

折角プログラムを書いたのに、プラットフォーム毎に書き直していたのでは、面倒極まりない事になる。
どこでも動くのは大事な事だ、プログラムをコンパイルしたら、以下のように、各プラットフォーム向け実行ファイルを吐き出せばいい。

$ dotnet publish -c Release -r win10-x64
$ dotnet publish -c Release -r linux-x64
$ dotnet publish -c Release -r osx-x64
$ dotnet publish -c Release -r linux-arm

あとは、各プラットフォーム向けに生成されたフォルダをコピーして、実行権限を付与して、実行すれば動く。
継続的インテグレーション(CI)/継続的デリバリー(CD)/継続的モニタリング(CM)などのプロセスを実現する上でテスト環境などを準備する事になるが、デリバリが楽になるのは良い。

コンテナ

最近はコンテナを積極的に活用と仕様という話題を耳にすることも増えた、当然ながら、コンテナへのデプロイも楽である。
以下のようなDockerファイルを用意して。

FROM microsoft/dotnet:2.0-sdk AS build-env
WORKDIR /app

# copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore

# copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out

# build runtime image
FROM microsoft/dotnet:2.0-runtime 
WORKDIR /app
COPY --from=build-env /app/out ./
ENTRYPOINT ["dotnet", "dotnetapp.dll"]

あとは、イメージを作るだけでいい。

$ docker build -t dotnetapp-prod .
$ docker run --rm dotnetapp-prod
Hello .NET Core from Docker

Webサーバ

何かと重いといわれ続ける事が多かったASP.NETだが、フロントのウェブサーバであるIISがモジュラー型でありフル機能のWebサーバであったからでもある。
今はNode.jsなどと同じように、軽量TCPサーバライブラリ(libuvやRIOなど)を用いた、ノンブロッキングIO実装WebサーバKestrelを持っている。

Kestrelは高速な反面、高機能Webサーバのような仕組みは無い、もちろんMicroservices向けのREST APIなど、そのままで動かしても良いし、Nginxのバックエンドとして稼働させたりするのが良い、IISでももちろん動く。

以下には、TechEmpowerテストなどを用いた、IIS/NodeJS/Scala/Nettyなどとの比較ベンチマークのソースコードがある。
環境や実装によって当然スループットは変わるのだが、多少なりとも参考になるだろう。
短絡的にRPSだけのリクエスト処理数勝負的な話では無いとしても、Scala、Netty、NodeJSと同じような立ち位置でKestrelが使えそうな感じである事くらいは参考になる。

https://github.com/aspnet/benchmarks

実験のベースライン

これらは、さまざまなテクノロジスタックとアプローチのHTTP以外のオーバーロードを測定するためのサーバー実験です。
これらは一般的に、実際のHTTPサーバではなく、固定HTTPレスポンスを持つHTTPの要求に特別に応答するTCPサーバです。

Stack Server Req/sec Load Params Impl Observations
Hammer (raw HTTP.SYS) perfsvr ~280,000 32 threads, 512 connections C++ directly on HTTP.SYS CPU is 100%
Hammer (raw HTTP.SYS) perfsvr ~460,000 32 threads, 256 connections, pipelining 16 deep C++ directly on HTTP.SYS CPU is 100%
libuv C# perfsvr 300,507 12 threads, 1024 connections Simple TCP server, load spread across 12 ports (port/thread/CPU) CPU is 54%, mostly in kernel mode
libuv C# perfsvr 2,379,267 36 threads, 288 connections, pipelining 16 deep Simple TCP server, load spread across 12 ports (port/thread/CPU) CPU is 100%, mostly in user mode
RIO C# perfsvr ~5,905,000 32 threads, 512 connections, pipelining 16 deep Simple TCP server using Windows Registered IO (RIO) via P/Invoke from C# CPU is 100%, 95% in user mode

Plain Text

TechEmpowerテストのプレーンテキストベンチマークと同様です。
サーバーとスタックのHTTP効率を強調することを目的としています。
実装は、レスポンス本体を積極的にキャッシュし、パフォーマンスを最大化するために必要でないコンポーネントを削除/無効化することは自由です。

Stack Server Req/sec Load Params Impl Observations
ASP.NET 4.6 perfsvr 57,843 32 threads, 256 connections Generic reusable handler, unused IIS modules removed CPU is 100%, almost exclusively in user mode
IIS Static File (kernel cached) perfsvr 276,727 32 threads, 512 connections hello.html containing "HelloWorld" CPU is 36%, almost exclusively in kernel mode
IIS Static File (non-kernel cached) perfsvr 231,609 32 threads, 512 connections hello.html containing "HelloWorld" CPU is 100%, almost exclusively in user mode
NodeJS perfsvr 106,479 32 threads, 256 connections The actual TechEmpower NodeJS app CPU is 100%, almost exclusively in user mode
NodeJS perfsvr2 (Linux) 127,017 32 threads, 512 connections The actual TechEmpower NodeJS app CPU is 100%, almost exclusively in user mode
ASP.NET Core on Kestrel perfsvr 313,001 32 threads, 256 connections Middleware class, multi IO thread CPU is 100%
Scala - Plain perfsvr 176,509 32 threads, 1024 connections The actual TechEmpower Scala Plain plaintext app CPU is 68%, mostly in kernel mode
Netty perfsvr 447,993 32 threads, 256 connections The actual TechEmpower Netty app CPU is 100%

Plain Text with HTTP Pipelining

Like the Plain Text scenario above but with HTTP pipelining enabled at a depth of 16. Only stacks/servers that show an improvement with pipelining are included.
上記のプレーンテキストのシナリオと同様ですが、HTTPパイプラインが16段で有効になっています。
パイプライン処理による改善を示すスタック/サーバーのみが含まれています。

Stack Server Req/sec Load Params Impl Observations
NodeJS perfsvr 147,554 32 threads, 256 connections The actual TechEmpower NodeJS app CPU is 100%, almost exclusively in user mode
NodeJS perfsvr2 (Linux) 173,641 32 threads, 512 connections The actual TechEmpower NodeJS app CPU is 100%
ASP.NET Core on Kestrel perfsvr 1,174,881 32 threads, 256 connections Middleware class, multi IO thread CPU is 100%
ASP.NET Core on Kestrel perfsvr2 (Linux) 928,023 32 threads, 256 connections Middleware class, single IO thread
Scala perfsvr 1,514,942 32 threads, 1024 connections The actual TechEmpower Scala plaintext app CPU is 100%, 70% in user mode
Netty perfsvr 2,808,515 32 threads, 256 connections The actual TechEmpower Netty app CPU is 100%

まとめ

以前に比べて、C#という言語の活用範囲を考えれば、最近は案外悪くないものだなと思うようになった。
OSSとなってから、まだ数年であるが、十分な実用域に入ってきているのだと感じる。