使用mgo.Monotonic从次要节点读取数据。

huangapple go评论76阅读模式
英文:

read from secondary with mgo.Monotonic

问题

我正在尝试配置从Mongo副本集的主节点和两个辅助节点读取数据,以提供更好的负载均衡。每个节点都在不同的机器上,具有以下IP地址:ip1、ip2、ip3。

我的GoLang网站是使用martini Web服务器和两个URL /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)
}

我使用命令 go run site.go 运行这个GoLang网站,并在实验准备阶段请求了 http://localhost:3000/insert - 大约一分钟后,我的测试数据被插入。

然后,我开始测试从辅助节点和主节点读取数据,在 attacker.go 中:

package main

import (
    "fmt"
    "time"

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

func main() {

    rate := uint64(4000) // 每秒请求数
    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)
}

运行 go run attacker.go,我每秒只请求URL http://localhost:3000/get 4000 次。当攻击者工作时,我打开了我的3个服务器,并运行了 htop 命令来观察资源消耗。主节点显示其负载很高,CPU 使用率约为 80%。辅助节点则保持平静。

为什么?

由于我使用了 mgo.Monotonic ...

mainSessionForSave.SetMode(mgo.Monotonic, true)

... 我期望从所有节点 ip1, ip2, ip3 进行读取,并且我期望观察到所有节点的负载均衡和相等的 CPU 使用率。但事实并非如此。我配置错了什么?实际上,在我的情况下 mgo.Monotonic 并没有起作用,我只从主节点读取数据。

英文:

I am trying to configure reading from primary and two secondary nodes of mongo replica set to provide better load balancing. Each of 3 nodes are on different machines with IP addresses: ip1, ip2, ip3.

My GoLang site, which is the martini web server with two urls /insert and /get:

package main
import (
&quot;github.com/go-martini/martini&quot;
&quot;gopkg.in/mgo.v2&quot;
&quot;gopkg.in/mgo.v2/bson&quot;
&quot;net/http&quot;
)
const (
dialStr        = &quot;ip1:port1,ip2:port2,ip3:port3&quot;
dbName         = &quot;test&quot;
collectionName = &quot;test&quot;
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:&quot;I&quot;`
}
func prepareMartini() *martini.ClassicMartini {
m := martini.Classic()
sessionPerRequest := GetMgoSessionPerRequest()
m.Get(&quot;/insert&quot;, func(w http.ResponseWriter, r *http.Request) {
for i := 0; i &lt; elementsCount; i++ {
e := Element{I: i}
err := collection(sessionPerRequest).Insert(&amp;e)
if err != nil {
panic(err)
}
}
w.Write([]byte(&quot;data inserted successfully&quot;))
})
m.Get(&quot;/get&quot;, func(w http.ResponseWriter, r *http.Request) {
var element Element
const findI = 500
err := collection(sessionPerRequest).Find(bson.M{&quot;I&quot;: findI}).One(&amp;element)
if err != nil {
panic(err)
}
w.Write([]byte(&quot;get data successfully&quot;))
})
return m
}
func collection(s *mgo.Session) *mgo.Collection {
return s.DB(dbName).C(collectionName)
}

I run this GoLang site with the command go run site.go and to prepare my experiment requested http://localhost:3000/insert - after about a minute my test data was inserted.

Then I started to test reading from secondary and primary nodes
in attacker.go:

package main
import (
&quot;fmt&quot;
&quot;time&quot;
vegeta &quot;github.com/tsenart/vegeta/lib&quot;
)
func main() {
rate := uint64(4000) // per second
duration := 4 * time.Second
targeter := vegeta.NewStaticTargeter(&amp;vegeta.Target{
Method: &quot;GET&quot;,
URL:    &quot;http://localhost:3000/get&quot;,
})
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(&quot;99th percentile: %s\n&quot;, metrics.Latencies.P99)
}

Running it go run attacker.go I just requested URL http://localhost:3000/get 4000 times per second. While attacker was working I opened all my 3 servers and run htop command to watch resources consumption. The PRIMARY node shows that it is under high load with CPU about 80%. The SECONDARIES were calm.

Why?

As I used mgo.Monotonic ...

mainSessionForSave.SetMode(mgo.Monotonic, true)

... I expected to read from all nodes: ip1, ip2, ip3 and I expected to watch all the nodes under equal load and with equal CPU consumption. But it is not so. What did I configure wrong? In fact mgo.Monotonic is not working in my case and I read only from the PRIMARY node.

答案1

得分: 5

sessionPerRequest只会在启动服务器时创建一次:在调用prepareMartini时创建,并在那时设置sessionPerRequest。传递给m.Get()的闭包访问该变量。然后,在第一次写入之后(在测试设置期间),mgo将只访问主节点

> 如果可能,单调一致性将从从节点开始读取,以便负载更好地分布,并且一旦发生第一次写入,连接将切换到主节点

(如果mgo在写入主节点后继续从从节点读取,读取不一定会反映您刚刚进行的写入,这可能会很麻烦。而且切换到主节点应该只会获得比从从节点获取的更新数据,而不是旧数据,这保持了单调性。这是理想情况下的工作方式;有关更多信息,请参阅下面的“未解决问题”链接。)

解决方案是将创建会话推迟到处理程序中,例如,删除sessionPerRequest并在每个处理程序的顶部放置一个明确的内容,如下所示:

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

在阅读有关MongoDB一致性的未解决问题时,应该考虑所有一致性承诺:目前,在网络分区期间,读取可能会看到旧数据和稍后将回滚的写入,即使mgo试图从主节点读取。(比较和设置操作没有此问题,但当然这是一个更大更慢的操作。)此外,浏览该帖子还值得讨论一致性级别以及不同数据库行为如何对应应用程序的最终用户。

英文:

The sessionPerRequest is only created once: prepareMartini is called at server startup, and sessionPerRequest is set then. The closures passed to m.Get() access that variable. Then, after the first write (during your test setup), mgo will only access the primary:

> Monotonic consistency will start reading from a slave if possible, so that the load is better distributed, and once the first write happens the connection is switched to the master.

(If mgo just continued reading from the secondary after writing to the primary, a read wouldn't necessarily reflect a write you just made, which could be a pain. And switching to the primary should only get you newer data than you were getting from the secondary, never older, which preserves monotonicity. That's how it should ideally work, anyway; see the "open issues" link below for more.)

The solution is to push creating the session down into your handlers, e.g., remove sessionPerRequest and put something explicit atop each handler, like

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

All consistency promises should be read in light of open issues with MongoDB consistency: right now, during network partitions, reads can see old data and writes that will later be rolled back, even when mgo is trying to read from the primary. (Compare-and-set doesn't have this issue, but of course that's a larger slower operation.) It's also worth perusing that post just for the discussion of the consistency levels and the descriptions of how different database behaviors might manifest for an app's end-users.

答案2

得分: 0

你忘记在使用后关闭连接:

defer mainSessionForSave.Close()

可能这是一个原因。

P.S. 确保你的所有节点都可用 使用mgo.Monotonic从次要节点读取数据。

英文:

You forget to close connections after use it:

defer mainSessionForSave.Close()

Probably, it may be a reason.

P.S. make sure that all your nodes is available 使用mgo.Monotonic从次要节点读取数据。

huangapple
  • 本文由 发表于 2015年4月25日 21:35:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/29865895.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定