okadato の雑記帳

スタートアップでSREとしてはたらくokadatoの雑記です。

Locust vs Gorilla!苛烈な負荷に耐えるゴリラ🦍の孤独な闘いと、とある英雄の物語【Hipster Shop 防衛編】

さて前回 loadgenerator のリミッターを解除、frontend を陥落させたので、一歩ふみこんで内部の挙動を確認しようというところまで進みました。



というわけで今回は loadgenerator の内部で動いている Locust(イナゴ🦗ですね)の挙動の整理から始めます。



《イナゴの 軍勢 ( ぐんぜい ) /Swarm of Locusts》

Locust の挙動は src/loadgenerator/locustfile.py に記されています。


まずはじめに注目すべきは、どのようなフローで Synthetic な(≒ ユーザ行動を模した)リクエストを投げるのかという シナリオ の定義箇所です。そのものズバリ UserBehavior でユーザ行動のシナリオを定義しています。


class UserBehavior(TaskSet):  
  
    def on_start(self):  
        index(self)  
  
    tasks = {index: 1,  
        setCurrency: 2,  
        browseProduct: 10,  
        addToCart: 2,  
        viewCart: 3,  
        checkout: 1}  


これら dictionary 型の value の比率で各処理を行います。それぞれの処理はざっくりと以下のような感じ。


1. index:           メイン画面を表示  
2. setCurrency:     通貨単位を設定  
3. browseProduct:   各商品ページを表示  
4. addToCart:       商品をカートに追加  
5. viewCart:        カートの中身を表示  
6. checkout:        カートの中身の商品を購入  


on_start というのは各ユーザ行動の開始時に必ず実施するタスクです。
index を必ず実施するということは、ユーザはまずメイン画面からアクセスするということになりますね。直感的!





🦍 に導かれ…

さてそれぞれの処理はまず必ず frontend を通りますので、src/frontend/main.go の実装を確認します。
gorilla/mux という Golang 製のルーティング機能を使用しているみたい。



ちょっと待てよ? frontend の入口には gorilla が…?







つ、つまり前回イナゴの軍勢🦗 をひとり叩き落とし続けていたのはゴリラ🦍 だったのです!!

これはアツい!!!!!!

f:id:okadato623:20191202074818j:plain
前回の戦いはこうなっていたのだ!!!



ダイアグラム図を再掲すると、こーんな感じです!!!



f:id:okadato623:20191202074711j:plain
ゴリラ vs イナゴの軍勢!!



ちょ、ちょっと出来すぎでは!!!??!?!?!




f:id:okadato623:20191202095657p:plain
前回の記事から抜粋




ゴ、ゴリ蔵(ごりぞう)…お前………

いったい誰がこんなひどいことを…!!??






閑話休題

r.HandleFunc("/", svc.homeHandler).Methods(http.MethodGet, http.MethodHead)  
r.HandleFunc("/setCurrency", svc.setCurrencyHandler).Methods(http.MethodPost)  
r.HandleFunc("/product/{id}", svc.productHandler).Methods(http.MethodGet, http.MethodHead)  
r.HandleFunc("/cart", svc.addToCartHandler).Methods(http.MethodPost)  
r.HandleFunc("/cart", svc.viewCartHandler).Methods(http.MethodGet, http.MethodHead)  
r.HandleFunc("/cart/checkout", svc.placeOrderHandler).Methods(http.MethodPost)  



上記は main.go からの抜粋です。それぞれの HandlerFunc メソッドが前掲のそれぞれの処理と紐付いています。


すべての実装は handler.go に定義されていますので、こちらをご参照〜



千里の未知も一歩から

話はがらっと変わりますが、ぼくは 自己紹介の記事 にも書いたとおり、ショーン・エイカー 氏を信奉しています。



氏の代表作、『幸福優位7つの法則』はモチベーション3.0 などとならび、ぼくのバイブルのひとつです。


そんな7つの法則のひとつに、ゾロ・サークル というものが挙げられています。
どういった内容か簡潔に説明すると、



自分が達成できる小さな円(ゾロ・サークル)に目標を集中することでコントロール感覚を獲得し、徐々にその円を拡げることで自分の能力であらゆる物事を成し遂げられるという信念を取り戻すこと



です!
ゾロというのは 怪傑ゾロ というシリーズの主人公を務める仮面の剣士らしいです(原作はみたことない)
初代ゾロは二代目ゾロを鍛える際にまず小さな円を地面に描き

この円がお前の世界だ。生活のすべてだ。わしが次の指示をするまで、この円の外にはなにもないと思え

と伝え、徐々にその円(ゾロ・サークル!)の大きさを拡げていったそうです。


我が心の師の教えには素直に従いましょう。前掲の HandlerFunc からまずは最も手頃なものに手を出してみます!



なにもわからない。そんなときには LOC。

というわけで、最も行数(Line Of Code)の少ない コチラ の関数が最初の ゾロ・サークル です。



func (fe *frontendServer) setCurrencyHandler(w http.ResponseWriter, r *http.Request) {  
    log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger)  
    cur := r.FormValue("currency_code")  
    log.WithField("curr.new", cur).WithField("curr.old", currentCurrency(r)).  
        Debug("setting currency")  
  
    if cur != "" {  
        http.SetCookie(w, &http.Cookie{  
            Name:   cookieCurrency,  
            Value:  cur,  
            MaxAge: cookieMaxAge,  
        })  
    }  
    referer := r.Header.Get("referer")  
    if referer == "" {  
        referer = "/"  
    }  
    w.Header().Set("Location", referer)  
    w.WriteHeader(http.StatusFound)  
}  



setCurrencyHandler なので、通貨単位の設定の入口ですね。
ジャスト20行!とりあえずこれくらいの円ならなんとかなるような気がしませんか?
しかも前半はなんかアレですね、 Logger の準備とかっぽいですね。


特に大切なのはおそらく以下の処理でしょう。



if cur != "" {  
    http.SetCookie(w, &http.Cookie{  
        Name:   cookieCurrency,  
        Value:  cur,  
        MaxAge: cookieMaxAge,  
    })  
}  



cur つまり currency が定義されていなかった場合、cookieCurrency に新しい通貨単位を設定するという処理ですね。ふむふむ。


まずは Cookie を確認し…



f:id:okadato623:20191202070851p:plain
URL の左のアイコンをクリック



f:id:okadato623:20191202071103p:plain
使用中の Cookie はひとつだけ



実際に通貨単位を変更してみましょう。



f:id:okadato623:20191202071334p:plain
赤枠部をクリックし、通貨単位を JPY に変更すると…



f:id:okadato623:20191202071452p:plain
Cookie が増え、確かに JPY になっています!



またこのとき currencyservice Pod のログを確認すると、以下の内容が出力されていました。



{  
    "severity": "info",  
    "time": 1575208447937,  
    "message": "Getting supported currencies...",  
    "pid": 1,  
    "hostname": "currencyservice-7dc7f7756b-hbxdf",  
    "name": "currencyservice-server",  
    "v": 1  
},  
{  
    "severity": "info",  
    "time": 1575208447946,  
    "message": "received conversion request",  
    "pid": 1,  
    "hostname": "currencyservice-7dc7f7756b-hbxdf",  
    "name": "currencyservice-server",  
    "v": 1  
},  
{  
    "severity": "info",  
    "time": 1575208447946,  
    "message": "conversion request successful",  
    "pid": 1,  
    "hostname": "currencyservice-7dc7f7756b-hbxdf",  
    "name": "currencyservice-server",  
    "v": 1  
}



いずれも通貨単位変更に伴うリロード時に別のハンドラが呼ばれ(つまり別のマイクロサービスと通信し!)、convertCurrency 呼び出し時に吐かれている模様です。






と、いうわけで

ここからいよいよマイクロサービス間の連携、gRPC(+ Protocol Buffers)という真骨頂に入っていきます!


今回はわりとボリュームが大きめになってしまったので、続きはまた次回に!
ゾロ・サークルをさらに拡大 し、マイクロサービスのマイクロサービスっぽいところ(?)を見ていきます!

引き続き乞うご期待!です 🦍 🦗🦗🦗