Gradleのユーザーガイドの12章まで読んでいたログを投下
たまにはWeb日記っぽく、 http://gradle.monochromeroad.com/docs/userguide/userguide.html をしばらく読んでいたのでそのログを投下。
2018/02/12
- タスクのグラフ構造
- マルチプロジェクトのビルド
- サブプロジェクトごとにビルドしなおすかが決められるのは良いだろう
- 既存の資産の再利用
- Gradleラッパー
- Gradleラッパーを使うと、Gradleをインストールしていないマシンでビルドを実行できます。たとえば、CIサーバー上でビルドしたり、オープンソースプロジェクトを簡単にビルドできるようにしたりするのに便利
- クライアントマシンの管理維持作業を省く(zero administration)というアプローチ
- GroovyはJavaに優しいスクリプト言語
- それでJavaシステム向けのビルドシステムGradleを作る
2018/02/13
- JavaプラグインがRailsみたいにディレクトリなどを規約でしばってる
- buildコマンドを実行するとその依存タスクがたくさん実行される
uploadArchives
とかタスク? っぽいものの名前を知りたい。6章?- Test型とflatDirの意味を調べる
2018/02/14
- マルチプロジェクト, settings.gradle, includeに渡すプロジェクトのルートディレクトリでディレクトリ階層は
:
で表す - dependsOn, into, from の書き方について
// 例7.14 マルチプロジェクトビルド - 配布ファイル task dist(type: Zip) { dependsOn apiJar from 'src/dist' into('libs') { from apiJar.archivePath from configurations.runtime } } artifacts { archives dist }
2018/02/15
taskName << { }
でtaskNameの最後に処理を継ぎ足し- taskName(dependsOn: dependedTaskName) でtaskNameの前にdependedTaskNameが行われる、taskNameはdependedTaskNameに依存している、の指定
- Gradleの定義後の依存関係の指定などオーバーライドの窓口が限定されているRubyぽさ
2018/02/16
- すでに定義されたタスクに対しては
.property
みたいな感じでプロパティへアクセスできる。そのタスクのプロパティはタスク定義時にext.propName
で定義 - Javaみたいな書き方(返り値の型とかの定義)でメソッドが定義できて、似たようなビルドタスクもスッキリ
- デフォルトタスク(
defaultTasks
task1,
task2, ...
)になるとgradle
と入れただけで実行される- 他のタスクを実行するときにも実行されるか気になった
- タスクの依存関係グラフで特定のタスクがいまから実行しようとしてるタスクと一緒に実行されるか(あるいはそのものか)が実行前に
gradle.taskGraph.whenReady
でわかる
2018/02/17
- 依存関係の管理は二つの部分に分けられる
- プロジェクトのビルド実行に必要なファイルを探す(入力ファイルがどこにあるのか?の設定 => プロジェクトの 依存関係 )
- ビルドして生成したものをアップロードする(アップロードした出力ファイル => プロジェクトの 公開物 (アーティファクトとも呼ばれるっぽい))
- リポジトリにはローカルもリモートもあるし、IvyやMavenのリポジトリも使える
- Gradleでビルドした生成物をアップロードする => アップロードしたファイル「(プロジェクトの)(公開)アーティファクト」
- runtimeってAndroidアプリケーションプラグインで使えるのか?
2018/02/18
- Aプラグインを適用すると、Bプラグインも適用される、という話 Android-JavaとAndroid-Kotlinの話を思い出した
- Gradleを使う場合、ソースコードのディレクトリはGradleプラグインが決める
- ジョイントコンパイル
2018/02/19
gradle やってほしいタスクたち、やってほしい順
- 依存や一緒に実行されるタスクで重複が出ても一度しか実行されない
-x
で依存したタスクや一緒に指定したタスクのうち、指定したものと指定したものだけが依存していたタスクを除外--continue
でビルドに失敗した部分があっても継続できる部分は継続してなるべくエラーを多く披露
2018/02/20
「DataBindingのコードを読む」のスライドが面白かったのでDataBindingについて少しまとめました
上のスライドが面白かったため、DataBindingについてスライドを読んでわかったことや追加で調べたことをメモしました。
時間が足りず、書いて動かしたわけじゃないし雑なのですが、自分用のメモとしてはないよりましでしょう*1。
Androidアプリケーション用Gradle Plugin(Gradle 3.0.0)にてData Binding関連で含まれているもの
- Data Bindingライブラリの依存を自動で追加するルール*2
- Data Binding用の処理で使うと思しき
DataBindigInfo
クラスとそのクラスにアノテーションを利用してデータを突っ込むdataBindingExportBuildInfoタスクの追加 - ビルドプロセスの中で、リソースファイルマージのタイミングでレイアウトリソースから関連パラメータを読み取ってDataBinding(のアノテーションプロセッサ?)で利用しやすいように加工したxmlファイルを用意*3
依存ライブラリの追加について(追加で調べたこと)
- build.gradleに対応するプロパティを設定しておくと、DataBindingに関連するライブラリの依存を自動的に追加するルールがAndroid用Gradleに定義されそう
- Androidプロジェクトのビルドなどのタスクを実行する前後*4に実行されるものとして定義されているみたい
BaseComponentModelPlugin.java
というファイルの中にAndroidアプリケーションプロジェクトで使うbuild.gradle
で使うようなタスクやAndroid アプリケーションプラグインに入っている標準的なConfiguration*5やデフォルトタスクやルールが定義されている*6@Mutate
はbuild.gradleで設定されたビルド対象(のなにか)を引数に取り、ビルド対象に変化を与えるためのRule*7っぽい- build.gradleの
android.dataBinding.enabled
を見て実際ルールの中身が動くかどうか決めていそう*8 - 動く順序として、
@Mutate
で設定されたルールはデフォルトルールの後に動く
- build.gradleの
// https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/BaseComponentModelPlugin.java#619 @Mutate public void addDataBindingDependenciesIfNecessary( TaskManager taskManager, @Path("android.dataBinding") DataBindingOptions dataBindingOptions) { taskManager.addDataBindingDependenciesIfNecessary( new DataBindingOptionsAdapter(dataBindingOptions)); }
// https://developer.android.com/topic/libraries/data-binding/index.html // build.gradle android { .... dataBinding { enabled = true } }
DataBindingに関係しているクラスについて
ViewDataBindingクラス
layout_xxx
から始まるレイアウトリソースから自動生成されるっぽい- ViewModelからの変更通知を受け取って、それをViewBindingAdapterに通知(on executeBindings() , executeBindings()が呼び出されるきっかけはChreographer.postFrameCallback())
- クラスとしてはViewDataBindingのサブクラス。レイアウトリソースと対応して自動生成される
- 他のViewBindingAdapterクラスと関わる部分(イベントリスナやexecuteBindings()の中身?)もViewBindingAdapterクラスのアノテーションから生成っぽい
- 対応するViewModelと自動生成されたIDを(でかい何かに)登録する
ViewBindingAdapterクラス*10
- EventListenerを書いたり、Viewの更新コード*11を書いたり(Viewへの反映を呼び出すのは ViewDataBinding.executeBindings())
- 自分が管理してるViewが持ってる値を取得することもでき*13、それをViewModelに渡したり*14
ViewModelクラス
- BaseObservableのサブクラス。
- ViewDataBindingにセットして使う。ViewDataBindingでどのViewModelを使うか、といったことは開発者側で定義するし、自動生成されないのでアノテーションつけながら自分で書く
- DataBindingで使いたい属性のgetterには@Bindableをつけておくと、その属性のIDがBRクラスに生えるっぽい
- ModelはViewModelを知ってて、Repositoryとかの方からModelに変更通知 => ModelがViewModel持ってたらViewModelに通知の流れ?
DataBindingComponentクラス
- getXxxBindingAdapter()メソッドで対応するViewDataBindingクラスの初期化を定義
- DataBindingComponentのインスタンスを
DataBindingUtil.setContentView()
に渡すことで、たぶん getXxxBindingAdapter()メソッドが呼び出されてViewDataBindingのインスタンスが生成 - mDirtyFlagsで工夫してそうですが、DataBindingの実装見てたら非同期処理がバリバリ入るので、中途半端な状態のViewDataBindingのインスタンスの参照をDataBinding関連の他のコンポーネントに公開したくない。ので、完全にインスタンスの生成が済んだオブジェクトをreturnするメソッドを定義させることを関連クラスの定義としておくことで、その辺のお作法を強制してるように見えましたが、どうなんでしょうね。
*1:ツッコミお待ちしております。。
*2:http://mike-neck.hatenadiary.com/entry/2015/05/18/220834 こちらの記事によると、Gradle2.4からルールという概念が登場したそうですが、4.5.1でもincubatingです
*3:リリース用にビルドをしていく際も公開するapkなどのファイルに設定ファイルは含まれないはずですが、アノテーションを使ってリリース時の最新コードを生成するためにこの設定ファイルが必要だから、このパースはmergeReleaseResourceでも実行されそうな気がしましたが確認してません
*4:前後ってなんだよって感じですが、はい
*5:compileやprovidedなど
*6:https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/BaseComponentModelPlugin.java#619
*7:Ruleというものを使ってGradleのドメインモデルを使った新しい書き方をやろう、的な話があり、 @Mutate はそのRuleの一種を示すアノテーション。DefaultRuleの後に動くものらしいのですが、GradleのAPI ドキュメントが4.5.1しかなくてバージョン違いすぎでよくわからず。。 https://docs.gradle.org/current/javadoc/org/gradle/model/Mutate.html
*8:https://developer.android.com/topic/libraries/data-binding/index.html
*10:BindingAdapter見て、いつかRecyclerViewのAPIと並べて眺めると面白いのでは、と思ったのでメモ
*11:こういう値の時はViewを変更せず無視する、とか、一緒のViewBindingAdapterで管理してるバリデーションエラーを表示するViewを可視にする、とかそういう感じ?
*12:なんとなく、View本体とコールバックで使う変化した値を受け取るあたりの形がiOSのUIKitの各クラスに対応して定義されているDelegate書くときの形に似てる
*13:更新時のコールバックが呼ばれる前の値?
*14:多分取得はViewModelに渡すときのViewの属性値のgetterのラッパー。ここは単なる取得だけで、取得した値の加工はViewModelでやる。たとえば、SpinnerでRadioボタンを作って選択するとユーザーの入力は0, 1, 2 ...な整数値だけど、これをドメイン層のModelに渡す前にViewModelでDomain層に使うenum値に直すとか? こうするとCustomViewはドメイン層のModelのことを知らなくていいし、ModelもCustomViewの実装を知らなくて良い。取得メソッドは単なるラッパーであり、ラッパーから値を受け取るようにしているだけだが、この一皮のおかげでViewModelも直接Viewの参照を持たなくてよいため、テストが書きやすい? という気がした
マーケティングオートメーションツールとアプリエンジニア ~プッシュ通知・お知らせダイアローグの運用自動化のためのお仕事~という題で発表しました
ふだんやっているお仕事の中から印象的だった事柄をオムニバス方式でお話しさせていただく体の発表をさせていただきました。
技術的にはGradle入門とGCM, FCMを使うとプッシュが届くけど何が起こっているのか、みたいなお話がありますがお仕事紹介の側面が強いです。
反省
- 30分あるからLT5本(マーケティングオートメーションツール5分/Push10分/InApp5分/クロスプラットフォーム5分/テスト5分)いくぞ、という内容出したけど、失敗でした
- 大規模カンファレンスに沿った品質の内容を、と思うとどんどんそれぞれのトピックが膨らんでいって削れないところをどう選ぶか、みたいな感じでして
- また、それぞれを一定以上の質にする、というところで時間がかかってしまい、細かいブラッシュアップが登壇3時間前までかかってしまった
- 自分が1日に生産できる資料の量はLT5分/日っぽいです
- 削られた内容について
- Chromebook > Chromebookはマルチウィンドウ、ウィンドウリサイズが前提の機種だからそのあたり不具合出やすくなるよ、という話の予定でしたがこちらのセッションでそれを専門に扱っていただくようだったためカット
- Appium VS Espresso, XCUITest > こちらも時間が足りず、DroidKaigiの前にテスト自動化カンファレンスの方でLTさせていただいたこともあり泣く泣くカットしました :bow: http://woshidan.hatenablog.com/entry/2017/12/12/083000
- マーケティングオートメーションツールについて > 5分くらい話せるかな、と思ったのですが30秒くらい触れただけですね。。また機会がありましたらぜひー
同時間帯はGraph QLやDropBoxの話の裏番組だったため3人くらい来るかなーという感じだったので小さい方の部屋とはいえほぼ満杯だったときはびっくりしました。
こちらから話を振ったときも挙手してくださる方がいたり実況してくださる方がいたり、部屋を出るとき面白かったーとつぶやいてくださる方がいたり、ありがたかったです。
時間内に収めるためにら行を削るべき、とクロスプラットフォームの章の用語を見直していたら直前に図に誤りが見つかって訂正したのもいい思い出ですが、もし次回以降LTのオムニバス方式で話す機会があるとしてもCFPでは制限時間の半分くらいに収まるようにします。体力的に、死ぬ。
運営スタッフの方々もありがとうございました。
他にも書いた方がいいことがある感じはしますが、書かずじまいになりそうなので書くことがあったら追記します。。
Media Streaming on Androidを見て面白かったので細々調べたメモ(動画のストリーミング配信プロトコル周り)
このメモはセッションで話された内容の書き起こしではなく、上記のスライドの内容が面白かったので自分で勝手に調べたメモです。
調べた結果とセッション中と用語のずれがある場合は、Abema TVのCode IQのプレス記事*1を優先させていただきました。
目次
- Androidで動画を再生するためのクラス
- Streamingで動画をダウンロードする、そのためにそれ用のコーデックで圧縮する
- Apple製のストリーミング動画配信プロトコル HLS(HTTP Live Streaming)とその実装に必要なファイル
- Abemaで採用しているストリーミング動画配信プロトコル MPEG-DASHの説明
Androidで動画を再生するためのクラス
- MediaPlayerとExoPlayerがある
- 前者はAPI Level 1からある、詳細はいじれないが単純なユースケースの場合はこれをポンと置いておけば良い
- 後者はAPI Level 14から利用でパラメータがカスタマイズできる
- 利用するときはGradleで
com:google:android:exoplayer:exoplayer:x.x.x
の依存を追加 - このライブラリは使う機能に合わせて全体やCore, UI, HLS, OKHTTP(通信周りのカスタマイズ)に分かれている
- 利用するときはGradleで
Streamingで動画をダウンロードする、そのためにそれ用のコーデックで圧縮する
- 動画をダウンロードして再生する、という場面では再生用のファイルをすべてDLしてから再生する場合と再生用のデータを都度都度ダウンロードして再生する場合がある
- ストリーミング、の場合は後者
- いわゆるストリーミングにも大きく分けてプログレッシブダウンロード方式(ファイルをHDにダウンロードする)とストリーミング方式(ファイルの内容はその場で取得するだけ)がある
- 動画を都度都度ダウンロードするとして
Apple製のストリーミング動画配信プロトコル HLS(HTTP Live Streaming)とその実装に必要なファイル
- h264で圧縮したファイルでストリーミング配信をしていますが、そのストリーミング配信のプロトコルはHLS(HTTP Live Streaming)がありますよ
- HLSを定義したのはApple
- HLSでストリーミング配信をするには3つのファイルが関係あって
- Master Playlist (拡張子
.m3u8
) - Media Playlist (拡張子
.m3u8
) - ts files (拡張子
.ts
)
- Master Playlist (拡張子
- ストリーミング再生のために関連ファイルを取得していく手順
- ちなみに、tsファイルは「MPEG-2 TS」ファイル
Abemaで採用しているストリーミング動画配信プロトコル MPEG-DASHの説明
- さっきHLSの話をしたんですが、Abema TVで実際利用しているのはMPEG-DASHというプロトコルですよ*7
- MPEG-DASHで主要なファイルは
MPD(.mpd)
,fragmented mp4 file(.m4s)
,Initialization file(.m4s)
*10 - 普通のmp4 vs fragmented mp4 file
- mp4はbox*11という要素が連なった木構造のデータ形式となっている
- Fragmented mp4 fileはわざわざ名前を変えている通り、少々勝手が異なる
.mp4
ファイルを構成する主要な要素について、フラグメントごとに要素を用意している(moof, mdat)- フラグメントのファイル群だけでは動画全体の情報が足りないため、全体を統括するためのファイルが必要でこれがinitialization fileやInitialization fragmentと呼ばれている*14
- Initialization Fragmentの方はftyp(ファイル形式)は
dash
で動画全体のメタデータに当たるmoov
を持っている
- Initialization Fragmentの方はftyp(ファイル形式)は
- 上記動画ファイルとは別にMPD(Media Presentation Description)がある
長くなってきたので一旦ここまでにします。。
*1:https://codeiq.jp/magazine/2017/11/55460/
*2: http://blog.socialcast.jp/05/post-15/ https://dev.classmethod.jp/tool/http-live-streaming/ など
*3:Wikipediaですが https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%BC%E3%83%87%E3%83%83%E3%82%AF#%E5%8B%95%E7%94%BB%E5%9C%A7%E7%B8%AE%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%87%E3%83%83%E3%82%AF
*4:http://blog.kokoni.jp/2014/02/14/hls%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9/
*5:ファイルごとの再生時間を見て次のファイルをいつ頃取ったらいいとか見るのだろうかと思ったけどこれは想像です
*6:https://k-tai.watch.impress.co.jp/docs/column/keyword/515059.html http://did2memo.net/2017/02/20/http-live-streaming/
*7:Amebaさんのプレス https://codeiq.jp/magazine/2017/11/55460/ 概要としてはこちらもよさそう https://www.ite.or.jp/contents/keywords/1701keyword.pdf
*8:https://codeiq.jp/magazine/2017/11/55460/ より引用
*9:https://www.jstage.jst.go.jp/article/itej/67/2/67_109/_pdf より引用
*10:ファイル拡張子がスライド表記と異なるが https://codeiq.jp/magazine/2017/11/55460/
*11:mp4の前身であるQuickTimeフォーマットに倣いatomとも呼ばれる この辺の参考は http://yebisupress.dac.co.jp/2015/11/04/profile%EF%BC%9Fatom%EF%BC%9Fmp4%E3%81%AE%E3%82%88%E3%81%8F%E3%82%8F%E3%81%8B%E3%82%89%E3%81%AA%E3%81%84%E3%81%82%E3%82%8C%E3%81%93%E3%82%8C%EF%BC%88atom%E7%B7%A8/
*12:ファイルサイズ、再生時間など動画本体以外の付随情報
*13:動画や音声ですね
*14:https://www.jstage.jst.go.jp/article/itej/67/2/67_109/_pdf も参照
「はじめてのOSコードリーディング UNIX V6で学ぶカーネルのしくみ」を読みました
Javaの並行処理の後はこちらの本を読んでいました。
はじめてのOSコードリーディング ~UNIX V6で学ぶカーネルのしくみ (Software Design plus)
- 作者: 青柳隆宏
- 出版社/メーカー: 技術評論社
- 発売日: 2013/01/09
- メディア: 単行本(ソフトカバー)
- 購入: 56人 クリック: 1,959回
- この商品を含むブログ (29件) を見る
この本を読む前に転職にあたって少し背伸びをして「Inside Android」や「Androidを支える技術」を読んでいたのですが、initプロセスやスケジューラの話でぴんと来ずもしかしなくてもLinux的な本を何か読んだほうがいいのでは、と感じていました。
この本を手に取ったきっかけは、ゲーム系から来た人にC言語系統で何を読めばいいかと聞かれた時Linuxのカーネルだよとばかり返ってくるため、それでは初心者はどこから読んだらいいかと聞きなおしたら勧められた感じです。
読んでいる最中は前半の実行プロセスの切り替えあたりでは知らない単語、知らない形のコード、大量の副作用でのデータのやり取りに頭が結構混乱するのですが、5章くらいから辞書として前半を弾き始めて多少理解できたような、できなかったような。。
前書きの読み解くコツに「まず最後まで読む」とあったのでなんとか最後まで通した感想としては、ファイルシステムと物理的なストレージの対応、パソコンの中でたくさんプロセスが動いておりそのやり取りの様子とは、みたいな当たり前のことに対してなんとなく自信を持てた感じです。
C言語の読解については自信がつきませんでしたが、nginxとかUnicornとかサーバの設定のその辺の話を調べられるようになっていて驚きました。
それにしても、この本で扱われているUnix v6のリリースが1975年、その前のJava並行処理の本が2006年初版なので、年明けはもうちょっと現代の空気にふれようと思います。
以下、いくつかの項目を雑にメモして締めます。口調が違う項目もありますがそういう感じで、現場からは以上です。
スケジューラと実行プロセス
proc[]エントリの1番に登録されていて、特定のアルゴリズムで次の実行プロセスの選択、切り替えを行う特別なプロセスおよびそのプロセスで動くプログラム。
実行プロセスの選択アルゴリズムは、Unix v6の場合、プロセスが使用したCPUの累積時間が大きいほど選択されにくい、というもの。
実行プロセスはカーネルがグローバル変数uを通してuser構造体にアクセスできるようにカーネルAPRが設定され、実行プロセスの切り替え時にはカーネルAPR, ユーザAPR, 前回実行時に汎用レジスタに入っていた値の復元を行う(p.75)。
プロセスが今のプログラムに関するデータをどういう形で持っているか
各プロセスはproc構造体とuser構造体を1つずつ持っていて、プロセスの実行状況などはuser構造体が持っている。
user構造体が持っているのは
- 各種計算やプログラムの実行位置の管理に使う汎用レジスタの状況
- ユーザID/グループID
- 実行中のカレントディレクトリ
- そのプロセスでオープンしているファイル
- シグナル、システムコール、ファイル読み込みのためのデータ受け渡し愚痴となる変数
などで、各種計算やプログラムの実行位置の管理に使う汎用レジスタの状況についてはプロセスが中断される時にuser構造体に退避されるが、その詳細が2章で紹介されている。
proc構造体が持っているのは
- プロセスが実行可能かどうか
- 実行時間がどれくらいか
- 何かの資源が必要で実行を中断しているのか
といった、カーネルがプロセスを交代させたりするのに使う情報といった感じ。
proc構造体の情報は次にどのプロセスを実行させるかを判断する際に使われるので、現在実行していないからといってメモリから退避させることはできないが、user構造体は実行中以外はメモリ上にいる必要がないため、スワッピングの対象となる。
ファイルシステムとinodeとは
Unix系のシステムで扱うデバイスはブロックデバイスとキャラクタデバイスがあるのだけれど、プログラム含めたファイルの内容が載っているのがブロックデバイスというイメージ。ファイルシステムはブロックデバイスのデータを構造的に扱う仕組みのこと(p.258)。
その文脈でファイルはそのファイルを定義するinodeとファイルのデータから成っている。inodeはカーネルがいじる単位でのメタデータといった感じでファイルサイズやファイルのアクセス権限、ファイルのデータが保存されている。ファイル操作を行う最、カーネルの一番最初の仕事は目的のファイルのinodeを取得することになる。
inodeはファイル本体のデータとは少し離れた領域に保存されている(p.264)。
面白かったのは、ファイルの削除を行った時、実際にはinode領域だけが削除されていて、ファイルのデータが入っている領域は別のファイルによって上書きされるまでそのまま、という話。
http://www.fujitsu.com/jp/services/infrastructure/maintenance/lcm/service-phase4/h-elimination/
こういうのはそういう意味なんですね!
ブロックデバイスサブシステム
ブロックデバイスに入ってるデータを人間が扱いやすい形で操作するための仕組みがファイルシステムとしたら、コンピュータが効率的にメモリ上に出し入れするための仕組みがブロックデバイスサブシステムっぽい。
メモリ上にバッファ領域があり、そこにブロックデバイスから読み込んだデータをどこから読み込んだかとセットで管理、といった感じ。
プロセスの割り込みについて
(p.144あたり)実行プロセスの処理を中断する場合は、割り込み
とトラップによるものがあって、割り込み
は
- ブロックデバイスの動作完了通知(ファイルからのデータ読み込み完了通知など)
- 端末からの入力
- クロック割込み
など、非同期処理に関して使われます。トラップも同様にプロセスの中断がおこなわれますが
- 0除算
- 割り当てられない領域へのアクセス
- バスタイムアウト
などCPU内部の出来事による中断・再開を扱います。
ユーザプロセスが引き起こすトラップは最終的にシグナルとして処理されます。
正直、ユーザプロセスから送られたトラップがシグナルとして~...のあたりを読んだときはあまりピンとこなかったのですが、こちらの記事など、ウェブアプリケーションフレームワークの設定ファイルでサーバのプロセスをどうこうするコードを読んだときなるほど!となって感動しました。
Unix v6のコード上でkill関数がプロセスにシグナルを送る程度の意味なのは結構面食らいましたが。。
calloutエントリで指された関数の実行タイミングと連結リスト
クロック割込みについての話題ではないのですが、クロック割込みハンドラで呼びだされるclock()関数にて処理されるcallo構造体の配列calloutの部分が面白かったです(p.163)。
calloutは連結リストの形になっているのですが、一つ一つのエントリが実行対象の関数へのポインタと次のエントリへのポインタ、前のエントリから何秒後((日本語の都合でこうなっているが実際の単位はティック)に実行されるかの情報を持っています。
そして、clock()関数のたびに最初のcalloutエントリだけ前のエントリから何秒後に実行してくれ、のカウンタがデクリメントされるわけです。
calloutに登録された一つ一つの関数の実行タイミング(「今から何秒後に実行してくれ」の秒数)は最初のエントリからひとつたどる度に「前のエントリから何秒後に実行してくれ」の秒数を合計していくことで求められます。なので、最初のひとつのエントリの「前のエントリから何秒後に実行してくれ」だけ変更してやれば、それ以降のエントリの関数の「今から何秒後に実行してくれ」の部分は全部変更されるわけですね。
あら便利。
だからなんだ!って話なんですが、いろんな言語でTimer系のクラスのAPIみるとき、これ思い出すと楽しそうだなとなったのでメモ。
実効ユーザID、実効グループIDと実ユーザID、実グループIDについて
アクセス権限はパーミッションと呼ばれる11ビットの制御情報を使ってファイルごとに管理され(p.260)、access()関数にてu_uid, u_gid(実効ユーザID/実効グループID)を元にファイルへアクセス可能かが判定されます(p.308)。
パーミッションの11のビット列のなかでSUIDビット、SGIDビットが立っていると、ファイル実行時にu_uid, u_gidが変更されます。その際、変更前の値が入っているのがu_ruid, u_rgid(実ユーザID/実グループID)のようです(p.261)。
ファイルのハードリンクについて
リンクによって、ファイル(inode+ストレージファイル)に対して複数の名前をつけることができます。
リンクと元のファイルの名前に主従はなく、inode構造体がいくつの名前を持っているかを管理しており、このカウントが0にならない限りファイルは削除されません。
と聞いた時、ハードリンク少し怖いな、と思ったのでメモ。
その他細々
パイプが他でも使っているファイルアクセスの仕組みを利用した特殊なinodeエントリへのデータ読み書きらしいことを知って、標準入出力の言葉に対してなるほど感が増えました。
「Java並行処理プログラミング ―その「基盤」と「最新API」を究める」を読みました
去年の11月はブログを一切書けていなかったのですが、その間*1何をしていたかというと主に下記の本を読んでいました。
Java並行処理プログラミング ―その「基盤」と「最新API」を究める―
- 作者: Brian Goetz,Joshua Bloch,Doug Lea
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2006/11/22
- メディア: 単行本
- 購入: 30人 クリック: 442回
- この商品を含むブログ (174件) を見る
読むのにかけた時間としては、だいたい11月の余暇をまるまる使って1回読んで、正月休みに缶詰になってもう一回読み進めた感じです。
この本の1章で触れられているのですが、この本が書かれた2006年ごろは「一昔前は並行処理が"前衛的な"話題だったけれど、普通のプログラマが(並行処理における)スレッドの安全性を意識しなければいけない」だったそうです。
不勉強なのであれですが個人的な肌感としては、クライアントサイドでは並行処理をそれとして意識しなくていいようなReactiveの考え方や、MVP, MVVMといったGUIシステムなど特定の条件下の作法に注目が集まっています。一方Railsなどのサーバサイドにおいては並行処理といわずにバッチの処理などもっと具体的な問題にフォーカスして対応していたり。。
そういう状況なので、一周回った2018年現在も自分が書いているコードを並行処理として考えることは、普通のプログラマにとって "前衛的"、あるいは"オタク的"なような気配があります。このため、1章のこの辺の話は分量として3行程度で短いのですが時代が変わった感じがあって興味深かったです*2。
この本全体の内容としては、そういう特殊な話題となったかもしれない並行処理に対して普通のデベロッパが取り組むときに生じる、なんとなくロックを獲得したりしているけれど、これが正しいかどうやってチェックすればよいのだろう、といった疑問にコードレベルで答えるものになっています。一つ一つのトピックによくないコード、あまり良くないコード、良いコードの事例が並べられ、それら一つ一つに解説が加えてあるため、あまり詳しくない人でも丁寧に読んでけばそれ相応につかめていく感触があってよかったなと思いました。
基本的な部分は変わらないような気がしているものの、具体的なライブラリクラスの詳細やイディオム、パフォーマンスの兼ね合いなどはJava6.0くらいまでの話となっているので、せめてJava7, Java8くらいまでは適宜Javadocなどで確認した方がよいです。
それなりに胃もたれ感はありましたが、新しいことに少し手がとどきそうな感覚はワクワクですね。結城先生の下記の本を手にとって面白いな、と思った方は本書も楽しいと思います。
増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2006/03/21
- メディア: 大型本
- 購入: 15人 クリック: 287回
- この商品を含むブログ (206件) を見る
以下、簡単なメモを残して締めます。現場からは以上です。
- 競り合い状態と複合アクション
- 複数のスレッドから都合が悪いようなタイミングでアクセスされる可能性があり、悪い方の順序でのアクセスの結果、特定のステート変数の値がおかしくなるような状態を
競り合い状態
という - 競り合い状態が起きるような典型的な実装事例として、
check-then-act
のような確認して、操作する、というような複数の操作がスレッドから見てアトミックにアクセスされる単位になっていないというのがある- 複合アクションには
put-if-absent
read-modify-write
などがある。全部check-then-act
でいいのでは、といえなくもないですが、自分で一回書いてみるとput-if-absent
見逃したりする...[感想]
- 複合アクションには
- 複合アクション全体をロックを使って一度に一つのスレッドしかアクセスできないようにする
- 複数のスレッドから都合が悪いようなタイミングでアクセスされる可能性があり、悪い方の順序でのアクセスの結果、特定のステート変数の値がおかしくなるような状態を
- ロックの粒度とデッドロックの話
- 「このステート変数は~~でなければならない」といった制約を不変項といい、1つの不変項に関わるステート変数への操作を同一のロックでガードする
- 別の不変項で制限されているステート変数(不変項Aでのみ制限されるステート変数aと不変項Bでのみ制限されるステート変数b)は別のオブジェクトでロックしてよい
- 動的なロック順デッドロック
- 同じクラスの2つのオブジェクトを1度に操作する場合などにやりがち。グローバルに固定順序でロックをとるように工夫する
- ロックのネストに着目すると見つけやすい
- 固定順序に関する工夫はメソッド内に押し込めて外部から隠してしまうとよい
- メソッドをまたいだロックは発見しにくいが、すでにメソッド内でロックを取っている時、よそ者のメソッドを呼び出さないようにするとまだ発見しやすい
- ロックを取らないで外部のメソッドを呼ぶことを
オープンコール
と呼ぶ
- 同じクラスの2つのオブジェクトを1度に操作する場合などにやりがち。グローバルに固定順序でロックをとるように工夫する
- Lockインタフェースの
tryLock
を使って時間が切れたらロックを外すようにして確率的にロック解除という話も
- 「このステート変数は~~でなければならない」といった制約を不変項といい、1つの不変項に関わるステート変数への操作を同一のロックでガードする
- Executorフレームワークの使い方
- Producer-Consumer型の実装をやりたい時はExecutorFramework使うと便利
- ExecutorFrameworkを使うと、タスクをどういう風にスレッド間へ分配するか、タスク自体がどうであるか、といったのを分離して考えやすい
- タイムアウトしたFutureは、そのままだと元のスレッドへreturnするだけでFuture動いてるスレッドは止まっていないため、cancelしよう
- ThreadPoolの用途など(第8章)
- Producer-Consumer型の実装をやりたい時はExecutorFramework使うと便利
- オブジェクトの可視性とメモリモデル
- ロックをしないであるスレッドで処理していた内容が他のスレッドに見えないことがあるが、それはなぜか
- たとえばスレッドAから
int a = 0; int b = 0; int a = 2; b = 3;
を実行した時、スレッドBでSystem.out.println("a = " + a + " b = " + b);
したときa = 0 b = 3
と出力されることがある理由
- たとえばスレッドAから
- JVMの最適化のルールは、ロックが取られていない場合は、一つのスレッドの中でトータルの実行結果が意味的に変わらないのであれば実行順を入れ替えて良いようになっている
- 変数を保存している領域は、スレッドごとがローカルにアクセスする領域と、他のスレッドと共有している領域がある
- あるスレッドで宣言&初期化された変数が他のスレッドにもアクセスできるなる手順は
スレッドごとのローカル領域に書き込み
,共有メモリに書き込み
の順になる 共有メモリに書き込み
まで行われないと他のスレッドからは古い値が見える
- あるスレッドで宣言&初期化された変数が他のスレッドにもアクセスできるなる手順は
- ロックをすると、この実行順の最適化を制限することができ、ロックを後に獲得した他のスレッドから前のスレッドがしていた操作の内容が見えることが保証される
- ロックをしないであるスレッドで処理していた内容が他のスレッドに見えないことがあるが、それはなぜか
- スレッドセーフなクラス設計と委譲
- 1つの不変項にたずさわるステート変数がたくさんある場合はそれらを別のデータクラスとしてくくり出して管理すると扱いやすくなる
- 並行性を求められるような部分は既存のJavaのライブラリクラスが使えないか検討する
- 機能を拡張したい場合は継承より委譲を使うと良い
- なぜなら、ステート変数にアクセスするためのロックオブジェクトがサブクラスに可視だとわからないから
- (Javaのライブラリクラスはだいたいselfでロックしているのでクライアントサイドロックが可能だが、それは不安定な実装となる)
- 大きな万能ライブラリやExecutorなどとまでいかずともLatch, CyclicBarrier, Semaphoreなど基本的なものを知っておくと特にテスト用モックさっくり書きたい時手軽(多分動作がわからないRxJavaなどのライブラリを調べる時にも)[感想]
- テストについて
- まずはシングルスレッドと同じ内容のテストをまずやる
- 原因が並行性にないことをはっきりさせるため
- ブロックする操作の試験
- 逐次処理のテストにおける例外の試験に似たような立ち位置
- 一定時間後に
interrupt()
メソッドよんで、スレッドが終了するのをjoin()で待って、スレッドのステータス確認、みたいな構成 p.282
- 資源管理のテストでは、不具合を誘発するためにスレッドの重なりを出すため、CyclicBarrierなどを使ってドライバに一工夫するとよい
- コールバックなどテストに使えるAPIの利用も検討
- RxJavaのテスト用クラスの利用などは多分この立ち位置からのアプローチでは[感想]
- テスト自体が並行性を壊してしまう可能性がある、実行性能についての試験は静的/動的コンパイラの最適化などの要素もあるのでなるべくアプリケーションに近い環境で行う、など
- まずはシングルスレッドと同じ内容のテストをまずやる
RackがよくわからなかったのでRackアプリケーションをUnicornで動かしてnginxからリクエストを転送してみた
この記事はRuby Advent Calendar 2017の23日目の記事です。
はじめに
Webアプリケーションフレームワーク(WAF)といえば、
- 薄いアプリケーションをサクッと書くのに適しているsinatra
- なんでもあり気味なRuby on Rails
など、rubyで有名なものだけでもぱっと複数名前が上がりますね。
これらのWAFはApacheやnginxと連携させて動作させますが、これらのWebサーバはそのままではWAFと連携して動きません。なにかしら間にコードを書く必要があるのですが、このコードのインタフェースがWAFによって違ったらWAFを選んだら連携させるサーバも固定されてしまいますね。
そこで、WAFとWebサーバの間にこれらが協調動作するためのインタフェースを設定しましょう、それがrack...
とまで書いていてやっぱり意味がよくわかっていないので、この記事ではnginxとRackアプリケーションをつないで動かしてみようと思います。
実行環境について
この記事で利用しているバージョンは
nginxをインストールする
http://nginx.org/en/download.html よりnginx 1.13.7をダウンロードします。
$ cd /path/to/nginx-1.13.7 $ ./configure ... 中略 ... ./configure: error: the HTTP rewrite module requires the PCRE library. You can either disable the module by using --without-http_rewrite_module option, or install the PCRE library into the system, or build the PCRE library statically from the source with nginx by using --with-pcre=<path> option.
と出たので、こちらの記事を参考にpcreを入れます。
$ cd /path/to/pcre $ curl --remote-name ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.41.tar.gz $ tar -xzvf pcre-8.41.tar.gz
pcreはURLのrewirteやLocationの指定で正規表現によるマッチングが行えるようにしてくれるそうです。 つまり、pcreがあれば、ディレクトリにファイルをおいた場合、そのパスにアクセスした際にファイルを返してくれるのかなと一瞬思ったので、pcreをはずしてnginxをビルドしてみました。
$ cd /path/to/nginx-1.13.7 # 試しに --without-http_rewrite_module オプションでやってみる $ ./configure --without-http_rewrite_module $ make $ sudo make install $ ls /usr/local/nginx/sbin nginx $ ls /usr/local/nginx conf html logs sbin $ /usr/local/nginx/sbin/nginx nginx: [alert] could not open error log file: open() "/usr/local/nginx/logs/error.log" failed (13: Permission denied) 2017/12/23 01:29:52 [emerg] 7844#0: mkdir() "/usr/local/nginx/client_body_temp" failed (13: Permission denied) $ sudo /usr/local/nginx/sbin/nginx $ ps aux | grep nginx woshidan 7900 0.0 0.0 2443044 828 s003 S+ 1:31AM 0:00.00 grep nginx nobody 7892 0.0 0.0 2456480 1040 ?? S 1:31AM 0:00.00 nginx: worker process root 7891 0.0 0.0 2455228 484 ?? Ss 1:31AM 0:00.00 nginx: master process /usr/local/nginx/sbin/nginx $ sudo /usr/local/nginx/sbin/nginx -s stop $ ps aux | grep nginx woshidan 7917 0.0 0.0 2443044 824 s003 S+ 1:31AM 0:00.00 grep nginx
sudo /usr/local/nginx/sbin/nginx
で http://localhost
にアクセスすると index.html が見れます。
そして、 http://localhost/50x.html
にアクセスするとちゃんとエラーページが表示されます。違ったみたいですね。
この記事のごくせまい範囲では問題ありませんでしたが多分正しくないと思います。ただ、今回はnginxの話がメインでないので、pcreの話はまた改めてじっくり調べます。。
最初のRackアプリケーションを書く
rackのインタフェースでWAFとWebサーバを協調させるとき、WAFの方は Rackアプリケーション
と呼ばれるのですが、Rackアプリケーションとは何かというと
- callというメソッドを持つ
- callメソッドの引数としてWebサーバからのリクエストに当たるオブジェクトを一個受け取る
- callメソッドは、次の要素を含むレスポンスにあたる配列を返す
- ステータスコード(Integer)
- レスポンスヘッダ(Hash)
- レスポンスボディ(Array)
というRubyプログラムのことみたいです。
http://gihyo.jp/dev/serial/01/ruby/0023 の記事を参考に単純なラックアプリケーションを書いてみます。
GETリクエストにのみ反応し、URLパラメータを含むJSONを返してくれるアプリケーションです。
# test_app.rb # config: utf-8 class TestApp def call(env) if env['REQUEST_METHOD'] == 'GET' [ 200, {'Content-Type' => 'application/json'}, ["{ \"your_input\": \#{env['QUERY_STRING']}"\" }"] ] end end end
test_app.rb
と同じディレクトリに以下のように config.ru
というファイルも用意して
# config.ru # coding: utf-8 require './test_app.rb' run TestApp.new
$ ls
... config.ru test_app.rb
$ ./rackup
とすると
$ rackup [2017-12-23 01:48:38] INFO WEBrick 1.3.1 [2017-12-23 01:48:38] INFO ruby 2.4.3 (2017-12-14) [x86_64-darwin16] [2017-12-23 01:48:38] INFO WEBrick::HTTPServer#start: pid=8095 port=9292
のようにどこかでよくみたWEBrickのログが現れます。
http://localhost:9292
にアクセスすると { "your_input": "" }
というJSONが表示され、 http://localhost:9292?test=parameter
のようにクエリパラメータをつけると { "your_input": "parameter=test" }
というJSONに変化します。
雑にもほどがありますが、一応動いてそうですね。
nginxとRackアプリケーションはこのままでは一緒に動作しませんが、これらをつなぐものとして Rackアプリケーション用サーバ
があり、Rackアプリケーション用サーバ
には Unicorn
などがあるらしいです。今回は Unicorn
をさわってみます。
UnicornとRackアプリケーション
http://w.koshigoe.jp/study/?ruby-unicorn-intro より引用すると
Unicornは、Unix系システムで動作するRackアプリケーション用サーバ。接続時間が短いことを前提とした設計となっている。
だそうです。書いてあったことで今回の作業に必要かもしれなさそうなことをざっくりまとめます。
- マスタプロセスが規定の数のワーカープロセスが稼働しているのを維持する
- Rackアプリケーションをロードする((Rackアプリケーションは少なくとも
Rackアプリケーション用サーバ
とは別のプロセスで動いてないんですね)) - 設定ファイルをRuby DSLで書く
- Unicornのプロセスを止めたり増やしたりするのはマスタープロセスにシグナルを送ることによって行う
アプリケーションのpreloadもできるそうですが、一旦先に進みます。ひとまず unicorn
のgemを入れます。
$ sudo gem install unicorn
unicornを動作させる前にいくつか必要なディレクトリを用意します。
mkdir tmp mkdir tmp/sockets mkdir tmp/pids mkdir log
こちらの記事やこちらの記事を元に、可能な限り少なく設定ファイル unicorn.rb
を書いてみます。
worker_processes 2 # 子プロセスいくつ立ち上げるか timeout 15 # Unicornのソケットが開くパスを指定 # この設定をnginx.confでも使います listen "#{@dir}tmp/sockets/unicorn.sock", :backlog => 64 # ログのパスを指定 stderr_path "#{@dir}log/unicorn.stderr.log" stdout_path "#{@dir}log/unicorn.stdout.log"
最低限の設定ファイルはこれだけで、
# ファイルの配置を確認 $ tree . . ├── config.ru # Rackアプリケーションの起動設定ファイル ├── log ├── test_app.rb # Rackアプリケーションの実装 ├── tmp │ ├── pids │ └── sockets └── unicorn.rb # Unicornの設定ファイル
上記を用意した上で unicorn
と入力すると、
$ unicorn I, [2017-12-23T16:09:57.684313 #11150] INFO -- : listening on addr=0.0.0.0:8080 fd=9 I, [2017-12-23T16:09:57.684439 #11150] INFO -- : worker=0 spawning... I, [2017-12-23T16:09:57.685355 #11150] INFO -- : master process ready I, [2017-12-23T16:09:57.686091 #11163] INFO -- : worker=0 spawned pid=11163 I, [2017-12-23T16:09:57.686483 #11163] INFO -- : Refreshing Gem list I, [2017-12-23T16:09:57.706864 #11163] INFO -- : worker=0 ready
見覚えのあるログを吐くプロセスが立ち上がりました。ブラウザで http://localhost:8080/?parameter=test
にアクセスすると先ほどのRackアプリケーションと同じJSONを返してもらえます。
実際動かすまでは、UnicornのプロセスからRackアプリケーションのプロセスにリクエストを接続するのかなーと思っていたのですが、Rackアプリケーションを unicorn
コマンドで起動した場合と rackup
コマンドで起動した場合とのプロセスを確認してみたところ、確かに Unicorn
のプロセスしか動いてなくて Unicorn
のプロセスでRackアプリケーションが動いている感じがしてきました。
# unicornから動かした場合 $ ps aux | grep unicorn woshidan 11267 0.0 0.0 2442020 816 s002 S+ 4:14PM 0:00.00 grep unicorn woshidan 11163 0.0 0.1 2467944 8392 s003 S+ 4:09PM 0:00.02 unicorn worker[0] -l0.0.0.0:8080 woshidan 11150 0.0 0.1 2467900 10724 s003 S+ 4:09PM 0:00.11 unicorn master -l0.0.0.0:8080 $ ps aux | grep rack # 何も出ない # rackupで動かした場合 $ ps aux | grep rack woshidan 11335 0.0 0.0 2442020 812 s000 S+ 4:14PM 0:00.00 grep rack woshidan 11297 0.0 0.1 2480696 11732 s003 S+ 4:14PM 0:00.13 /Users/woshidan/.rbenv/versions/2.4.3/bin/ruby /Users/woshidan/.rbenv/versions/2.4.3/bin/racku
nginxとUnicorn
さて、今回のテーマは「nginxとRackアプリケーションをつないでみる」だったので、Rackアプリケーション
を動かしている Unicorn
と nginx
を連携させるための設定もしてみます。
nginx.confの書き方については
http
httpモジュールの設定server
仮想サーバごとの設定を書きます*1upstream server_name
server
ディレクティブの中でproxy_pass server_name
と書かれていた場合、upstream
ディレクティブの中のサーバに転送する- ロードバランサとしての負荷分散みたいな設定をするところっぽい
と上記の内容だけ確認して書いた最小限の設定ファイルが以下となっていて
// nginx.conf #user nobody; worker_processes 1; #pid logs/nginx.pid; events { worker_connections 4; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 15; upstream test-app { server 127.0.0.1:8080; # ローカルでunicornが動いてるIPアドレス & ポートへ転送 } server { listen 80; server_name localhost; location / { root html; # /usr/local/nginx からのパスです try_files $uri @app; } location @app { proxy_pass http://test-app; } } }
設定内容をざっくりまとめると
- localhost(ポート番号80番) でHTTPリクエストを受け付けるHTTPサーバ
/
のパスへアクセスされた時はまず/usr/local/nginx/html
(ドキュメントルート)以下の対応するパスにファイルがあるか確認する- ファイルがあればファイルの内容を返す
- ファイルがなければRackアプリケーションへ転送する
- Rackアプリケーションは同じローカルマシンで動いていてポート番号 8080番にリクエストを投げれば良い
となります。
この内容を、元ファイルのバックアップを取った上で /usr/local/nginx/conf/nginx.conf
に上書きしてから nginx
を起動します。
$ sudo /usr/local/nginx/sbin/nginx
すると、ドキュメントルートの usr/local/nginx/html
から辿って静的ファイルのあるパスの /index.html
や /50x.html
に対してはhtmlをそれ以外の場合は先ほどのRackアプリケーションが処理したJSONを返してくれます。
nginx
がパスを見て静的ファイルを返すかアプリケーションへリクエストを転送するかといった判断をしてくれているようで、それっぽくていいですね。
まとめ
ここまで動かしてみてRackでWebサーバとWAFを連携させるとかいうのは多分この辺りのことを言ってるんだろうな、と思ったことをメモします。
- Rackアプリケーションとは何かしらの引数を一つ取る
call
メソッドを持ったRubyのプログラムのことでRailsやSinatraもRackアプリケーションである - rackインタフェースないしRack protocolはその
call
メソッドで受け取るオブジェクトやcall
メソッドの戻り値などを規定している - HTTPリクエストを受け取ってrackインタフェースにそったオブジェクトを
Rack
アプリケーションに渡してくれるWebサーバを Rackアプリケーション用サーバという - 一般的にはリクエストを受け取ったらすべてそのままWebアプリケーションに流したいわけではなく静的ページに流したりロードバランサ挟んだりしたいので通常のWebサーバをRackアプリケーション用サーバ の前段に置く
- nginxやApacheあたりのこと
- nginxの設定で、特定のパスへのリクエストを
Rackアプリケーション用サーバ
に転送する- 転送の際にロードバランサの設定を挟んだり、特定のページへは静的ファイルを返すようにしたりする
まだまだ気になることは残っているのですが、十分長くなったのでおかわり案件にして現場からは以上です。
参考
- unicornの話
- unicorn/nginx/rackアプリケーションをつなぐ話
- rackの話
- nginx
- 導入
- コマンド
- 設定
- https://qiita.com/syou007/items/3e2d410bbe65a364b603
- https://heartbeats.jp/hbblog/2012/02/nginx03.html
- https://qiita.com/morrr/items/7c97f0d2e46f7a8ec967#%E5%9F%BA%E6%9C%AC%E8%A8%AD%E5%AE%9A
- http://nginx.org/en/docs/beginners_guide.html ファイルのパスなど一番正確なのは公式ですね...
- https://qiita.com/kaikusakari/items/cc5955a57b74d5937fd8 try_files の件
- http://shim0mura.hatenadiary.jp/entry/20120110/1326198429 upstreamの件
*1:仮想サーバについては http://www.sakc.jp/blog/archives/41325