Más contenido relacionado Similar a EchoyaGinhanazeSu_inoka.pptx (20) EchoyaGinhanazeSu_inoka.pptx3. Short Session の内容
- 本セッションで扱う HTTP Router について
- HTTP Router の実装のお話
- よく使われる sync.Pool
○ GoでHTTP Routerを実装するには?
○ 機能とパフォーマンスを落とさず提供する
- Router内部で高速に文字列処理を扱う方法
- EchoやGinで工夫されているところ
3
4. 本セッションで扱う HTTP Router について
1. 静的ルーティング (/health などURLのパスが変わらないもの)
2. パスパラメータルーティング (/:user のような動的に値が変わるもの)
4
5. 一般的な HTTP Router の内部実装
Trie Tree や Radix Tree といった木構造をベースに実装されている
/ h e a l t h
/ health
5
6. 一般的な HTTP Router の内部実装
Trie Tree や Radix Tree といった木構造をベースに実装されている
6
“/health”
/ h e a l t h
/ health
7. 一般的な HTTP Router の内部実装
Trie Tree や Radix Tree といった木構造をベースに実装されている
7
“/health”
/ h e a l t h
/ health
8. 一般的な HTTP Router の内部実装
Trie Tree や Radix Tree といった木構造をベースに実装されている
8
“/health”
/ h e a l t h
/ health
/health のハンドラ
12. type Handler interface {
ServeHTTP(ReponseWriter, *Request)
}
リクエスト → レスポンスを制御する箇所にルーティングのロジックを呼び出す
→ http.Handler インターフェースを実装する
12
GoでHTTP Routerを実装すると?
17. (*http.Server).Serve() の挙動
for {
// 中略
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
参考: https://github.com/golang/go/blob/master/src/net/http/server.go
17
18. (*http.Server).Serve() の挙動
for {
// 中略
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
18
参考: https://github.com/golang/go/blob/master/src/net/http/server.go
19. (*http.Server).Serve() の挙動
for {
// 中略
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
1 リクエスト毎にgoroutineを起動してそう
19
参考: https://github.com/golang/go/blob/master/src/net/http/server.go
22. Handler内でgoroutineIDを出力する
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
goroutineID := []byte(strings.Split(string(debug.Stack()), "¥n")[0])
w.WriteHeader(http.StatusOK)
// write goroutine id as response
w.Write(goroutineID)
})
srv := &http.Server{
Handler: mux,
}
srv.Addr = ":8001"
srv.ListenAndServe()
}
22
23. Handler内でgoroutineIDを出力する
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
goroutineID := []byte(strings.Split(string(debug.Stack()), "¥n")[0])
w.WriteHeader(http.StatusOK)
// write goroutine id as response
w.Write(goroutineID)
})
srv := &http.Server{
Handler: mux,
}
srv.Addr = ":8001"
srv.ListenAndServe()
}
23
28. go のリクエスト → レスポンス
28
goroutine起動
リクエスト待機
リクエスト
goroutine起動
リクエスト待機
29. go のリクエスト → レスポンス
29
goroutine起動 リクエスト待機
リクエスト待機
リクエスト
30. go のリクエスト → レスポンス
30
ハンドラ処理 レスポンス
リクエスト
goroutine起動 リクエスト待機
リクエスト待機
31. go のリクエスト → レスポンス
31
ハンドラ処理 レスポンス
リクエスト
goroutine起動 リクエスト待機
リクエスト待機
ハンドラ処理 レスポンス
…
32. go のリクエスト → レスポンス
32
ハンドラ処理 レスポンス
リクエスト
goroutine起動 リクエスト待機
リクエスト待機
ハンドラ処理 レスポンス
…
Router側で提供したい共通の機能やデータは?
33. go のリクエスト → レスポンス
33
ハンドラ処理 レスポンス
リクエスト
goroutine起動 リクエスト待機
リクエスト待機
ハンドラ処理 レスポンス
…
理想
Router側で提供したい共通の機能やデータは?
34. go のリクエスト → レスポンス
34
ハンドラ処理 レスポンス
リクエスト
goroutine起動 リクエスト待機
リクエスト待機
ハンドラ処理 レスポンス
…
メモリプール
理想
Router側で提供したい共通の機能やデータは?
35. go のリクエスト → レスポンス
35
ハンドラ処理 レスポンス
リクエスト
goroutine起動 リクエスト待機
リクエスト待機
ハンドラ処理 レスポンス
…
メモリプール
スレッドセーフの必要がある
理想
Router側で提供したい共通の機能やデータは?
73. パスパラメータの抽出
73
次の ‘/’ または 空文字 までの文字列を抽出する
1. 一文字ずつ += で文字列結合する
2. bytes.Buffer.WriteString() を使って文字列結合する
3. スライスのインデックスで文字列を抽出する
4. 正規表現で抽出する
75. HTTP Routing で高速に文字列を扱うには?
75
1. 次のノードの探索はインデックスを使って検索する
→ Echo や Gin では頭文字をインデックスとして扱っている
2. 文字列(パスパラメータ)の抽出はスライスを利用する
ただし、標準パッケージの最適化によって変わる可能性はある
1. データ構造の長所/短所を考慮しながら実装する
e.g.)
- Trie Treeはバックトラックに弱い
- Radix Treeは実装が複雑になる
76. Echo や Gin で工夫されているところ
76
1. sync.Poolで必要になる分は予めアロケートしておく
2. パスパラメータは独自のcontextで持つ
3. 関数呼び出しを減らす
77. Echo や Gin で工夫されているところ
77
sync.Poolで必要になる分は予めアロケートしておく
78. Echo や Gin で工夫されているところ
78
sync.Poolで必要になる分は予めアロケートしておく
Echo → パスパラメータのスライス、NotFound時のハンドラ、...
Gin → パスパラメータのスライス
79. Echo や Gin で工夫されているところ
79
sync.Poolで必要になる分は予めアロケートしておく
Echo → パスパラメータのスライス、NotFound時のハンドラ、...
Gin → パスパラメータのスライス
パスパラメータのスライスは共通して最大長アロケートしている
e.g.) “/:hoge/:fuga/:piyo” → 長さ3のスライスを用意する
→ スライスのappendは速度面からするとコストがかかる
80. Echo や Gin で工夫されているところ
80
パスパラメータは独自Contextで持つ
81. Echo や Gin で工夫されているところ
81
パスパラメータは独自Contextで持つ
リクエストのcontext.Contextに持たせることもできる
→ chi はこの実装でパスパラメータを格納している
context.Contextに格納する場合は速度面でかなり遅くなる
82. Echo や Gin で工夫されているところ
82
パスパラメータは独自Contextで持つ
リクエストのcontext.Contextに持たせることもできる
→ chi はこの実装でパスパラメータを格納している
context.Contextに格納する場合は速度面でかなり遅くなる
フレームワーク 設計思想 パフォーマンス
chi 標準net/httpパッケージに準拠 比較的遅い
Echo/Gin 内部はnet/httpをベースでパフォーマンスも求める まずまず
fasthttp 独自実装でパフォーマンスを求める 比較的速い
84. Echo や Gin で工夫されているところ
84
関数呼び出しを減らす
Echo → ルーティングの一部で goto 文が利用されている
Gin → ルーティングアルゴリズムは大きなトランザクションスクリプトで実装
85. Echo や Gin で工夫されているところ
85
関数呼び出しを減らす
Echo → ルーティングの一部で goto 文が利用されている
Gin → ルーティングアルゴリズムは大きなトランザクションスクリプトで実装
goto 文だとロジックを追いづらくならない?
→ goto 文が内部での関数呼び出しのようになっていて寧ろ読みやすい
個人的には Gin のトランザクションスクリプトの方が読みづらい
86. 高機能で高速なHTTP Routerを作るコツ (まとめ)
86
→ sync.Pool でハンドラ共通で利用する機能はPoolする
小さな構造体でもパフォーマンスが向上する
予め使う分は先にアロケートしておく
→ Routingの文字列操作は以下のようにする
1. 次の探索ノードはインデックスを使う
2. パスパラメータの抽出はスライスで抽出する
→ ルーティングにおいて、関数呼び出しは減らす
可読性 x パフォーマンス のトレードオフなので、実装者がここを見極める必要がある
→ どのようにAPIを提供したいかによってパフォーマンスも変わってくる
Notas del editor http.ListenAndServeやhttp.Server.ListenAndServeは、http.Server.Serveのラッパー関数のようなものなので、http.Server.Serveの挙動について説明します
http.ListenAndServeやhttp.Server.ListenAndServeは、http.Server.Serveのラッパー関数のようなものなので、http.Server.Serveの挙動について説明します
http.ListenAndServeやhttp.Server.ListenAndServeは、http.Server.Serveのラッパー関数のようなものなので、http.Server.Serveの挙動について説明します
http.ListenAndServeやhttp.Server.ListenAndServeは、http.Server.Serveのラッパー関数のようなものなので、http.Server.Serveの挙動について説明します
http.ListenAndServeやhttp.Server.ListenAndServeは、http.Server.Serveのラッパー関数のようなものなので、http.Server.Serveの挙動について説明します
http.ListenAndServeやhttp.Server.ListenAndServeは、http.Server.Serveのラッパー関数のようなものなので、http.Server.Serveの挙動について説明します
http.ListenAndServeやhttp.Server.ListenAndServeは、http.Server.Serveのラッパー関数のようなものなので、http.Server.Serveの挙動について説明します
http.ListenAndServeやhttp.Server.ListenAndServeは、http.Server.Serveのラッパー関数のようなものなので、http.Server.Serveの挙動について説明します
http.ListenAndServeやhttp.Server.ListenAndServeは、http.Server.Serveのラッパー関数のようなものなので、http.Server.Serveの挙動について説明します
http.ListenAndServeやhttp.Server.ListenAndServeは、http.Server.Serveのラッパー関数のようなものなので、http.Server.Serveの挙動について説明します
http.ListenAndServeやhttp.Server.ListenAndServeは、http.Server.Serveのラッパー関数のようなものなので、http.Server.Serveの挙動について説明します
1リクエスト毎1goroutineで処理するため、スレッドセーフの必要がある
エンドポイントやリクエスト毎にBodyやHeaderの大きさが違うので、足りない場合はアロケートされる
同じような大きさのリクエストがきた時にプールから取得するためメモリのアロケーションが必要なくなる 構造体が大きくなるとアロケーションのコストも大きくなる
EchoやGinはそこまでsync.Poolを最適化して使っていない
雑に使ってもそれなりのパフォーマンスチューニングが可能
fasthttpはこのあたりのプーリングの機構を独自に持っていてかなり最適化がされている