5

ロード バランシングを向上させるために、mongo レプリカ セットのプライマリ ノードと 2 つのセカンダリ ノードからの読み取りを構成しようとしています。3 つのノードはそれぞれ、ip1、ip2、ip3 の IP アドレスを持つ異なるマシン上にあります。

私のGoLangサイトは、 2 つの URLとmartiniを持つ Web サーバーです。/insert/get

package main

import (
    "github.com/go-martini/martini"
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
    "net/http"
)

const (
    dialStr        = "ip1:port1,ip2:port2,ip3:port3"
    dbName         = "test"
    collectionName = "test"
    elementsCount  = 1000
)

var mainSessionForSave *mgo.Session

func ConnectToMongo() {
    var err error
    mainSessionForSave, err = mgo.Dial(dialStr)
    mainSessionForSave.SetMode(mgo.Monotonic, true)
    if err != nil {
        panic(err)
    }
}

func GetMgoSessionPerRequest() *mgo.Session {
    var sessionPerRequest *mgo.Session
    sessionPerRequest = mainSessionForSave.Copy()
    return sessionPerRequest
}

func main() {
    ConnectToMongo()
    prepareMartini().Run()
}

type Element struct {
    I int `bson:"I"`
}

func prepareMartini() *martini.ClassicMartini {
    m := martini.Classic()
    sessionPerRequest := GetMgoSessionPerRequest()
    m.Get("/insert", func(w http.ResponseWriter, r *http.Request) {
        for i := 0; i < elementsCount; i++ {
            e := Element{I: i}
            err := collection(sessionPerRequest).Insert(&e)
            if err != nil {
                panic(err)
            }
        }
        w.Write([]byte("data inserted successfully"))
    })
    m.Get("/get", func(w http.ResponseWriter, r *http.Request) {
        var element Element
        const findI = 500
        err := collection(sessionPerRequest).Find(bson.M{"I": findI}).One(&element)
        if err != nil {
            panic(err)
        }
        w.Write([]byte("get data successfully"))

    })

    return m
}

func collection(s *mgo.Session) *mgo.Collection {
    return s.DB(dbName).C(collectionName)
}

コマンドを使用してこのGoLangサイトを実行し、go run site.go要求された実験を準備しhttp://localhost:3000/insertます。約 1 分後にテスト データが挿入されました。

次に、セカンダリ ノードとプライマリ ノードからの読み取りのテストを開始しましたattacker.go

package main

import (
    "fmt"
    "time"

    vegeta "github.com/tsenart/vegeta/lib"
)

func main() {

    rate := uint64(4000) // per second
    duration := 4 * time.Second
    targeter := vegeta.NewStaticTargeter(&vegeta.Target{
        Method: "GET",
        URL:    "http://localhost:3000/get",
    })
    attacker := vegeta.NewAttacker()

    var results vegeta.Results
    for res := range attacker.Attack(targeter, rate, duration) {
        results = append(results, res)
    }

    metrics := vegeta.NewMetrics(results)
    fmt.Printf("99th percentile: %s\n", metrics.Latencies.P99)
}

実行すると、毎秒4000go run attacker.go回URL をリクエストしました。攻撃者が作業している間、私は 3 つのサーバーをすべて開き、コマンドを実行してリソースの消費を監視しました。PRIMARY ノードは、CPU が約 80% の高負荷状態にあることを示しています。SECONDARIESは落ち着いていました。http://localhost:3000/get htop

なんで?

使っていたのでmgo.Monotonic...

mainSessionForSave.SetMode(mgo.Monotonic, true)

... 私はすべてのノードから読み取ることを期待していました:ip1, ip2, ip3そして、すべてのノードを同じ負荷と同じ CPU 消費で監視することを期待していました。しかし、そうではありません。何を間違って設定しましたか? 実際mgo.Monotonic、私の場合は機能しておらず、PRIMARYノードからのみ読み取ります。

4

2 に答える 2

5

sessionPerRequest一度だけ作成されます。prepareMartiniサーバーの起動時に呼び出され、sessionPerRequest設定されます。m.Get()その変数にアクセスするために渡されるクロージャー。次に、最初の書き込み後 (テスト セットアップ中)、mgoプライマリにのみアクセスします

モノトニック一貫性は、可能であればスレーブからの読み取りを開始するため、負荷がより適切に分散され、最初の書き込みが発生すると、接続がマスターに切り替えられます。

(mgoプライマリへの書き込み後にセカンダリからの読み取りを続けた場合、読み取りは必ずしも作成したばかりの書き込みを反映するとは限らず、これは苦痛になる可能性があります。二次的で決して古くならず、単調性が保たれます.とにかく、それが理想的に機能する方法です.詳細については、以下の「未解決の問題」のリンクを参照してください.)

解決策は、セッションの作成をハンドラーにプッシュすることです。たとえば、削除sessionPerRequestして各ハンドラーの上に明示的なものを置きます。

coll := mainSessionForSave.Copy().DB(dbName).Collection(collName)

すべての一貫性の約束は、MongoDB の一貫性に関する未解決の問題に照らして読む必要があります。現在、ネットワーク パーティションの間、読み取りmgoは、プライマリから読み取ろうとしている場合でも、後でロールバックされる古いデータと書き込みを見ることができます。(Compare-and-Set にはこの問題はありませんが、もちろん、それはより大きな低速の操作です。) 一貫性レベルの議論と、アプリのさまざまなデータベースの動作がどのように現れるかについての説明のためだけに、その投稿を熟読する価値もあります。利用者。

于 2015-05-02T02:54:03.920 に答える
0

使用後に接続を閉じるのを忘れます:

defer mainSessionForSave.Close()

おそらく、それが理由かもしれません。

PSすべてのノードが利用可能であることを確認してください:)

于 2015-04-29T06:43:15.353 に答える