使用 mgo.Monotonic 从辅助读取
read from secondary with mgo.Monotonic
我正在尝试配置从 mongo 副本集的主节点和两个辅助节点读取数据以提供更好的负载平衡。 3 个节点中的每一个都在具有 IP 地址的不同机器上:ip1、ip2、ip3。
我的 GoLang
网站,这是 martini
网络服务器,有两个 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)
}
我 运行 这个 GoLang
站点使用命令 go run site.go
并准备我请求的实验 http://localhost:3000/insert
- 大约一分钟后我的测试数据被插入。
然后我开始测试从二级节点和主节点读取
在 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)
}
运行它go run attacker.go
我刚刚请求URLhttp://localhost:3000/get
4000次每秒。当攻击者工作时,我打开了我所有的 3 个服务器和 运行 htop
命令来观察资源消耗。 PRIMARY 节点显示它处于高负载状态 CPU 大约 80%。中学很平静。
为什么?
因为我使用了 mgo.Monotonic
...
mainSessionForSave.SetMode(mgo.Monotonic, true)
...我希望从所有节点读取:ip1, ip2, ip3
并且我希望在相同的负载和相同的 CPU 消耗下观察所有节点。但事实并非如此。我配置错了什么?事实上 mgo.Monotonic
在我的情况下不起作用,我只从 PRIMARY 节点读取。
您使用后忘记关闭连接:
defer mainSessionForSave.Close()
大概吧,可能是一个原因。
P.S。确保所有节点都可用:)
sessionPerRequest
只创建一次:prepareMartini
在服务器启动时调用,然后设置sessionPerRequest
。传递给 m.Get()
的闭包访问该变量。然后,在第一次写入之后(在测试设置期间),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.
(如果 mgo
在写入主节点后继续从辅助节点读取,读取不一定会反映您刚刚进行的写入,这可能会很痛苦。切换到主节点应该只获得比从辅助服务器获得的数据 更新 的数据,永远不会更旧,从而保持单调性。无论如何,这就是理想的工作方式;请参阅 "open issues" link下面了解更多。)
解决方案是将创建会话向下推到您的处理程序中,例如,删除 sessionPerRequest
并在每个处理程序上放置一些明确的内容,例如
coll := mainSessionForSave.Copy().DB(dbName).Collection(collName)
应根据 open issues with MongoDB consistency 阅读所有一致性承诺:现在,在网络分区期间,读取可以看到旧数据和稍后将回滚的写入,即使 mgo
正在尝试从小学读。 (比较和设置没有这个问题,但当然这是一个更大的更慢的操作。)同样值得细读 post 只是为了讨论一致性级别和不同数据库行为可能的描述应用程序最终用户的清单。
我正在尝试配置从 mongo 副本集的主节点和两个辅助节点读取数据以提供更好的负载平衡。 3 个节点中的每一个都在具有 IP 地址的不同机器上:ip1、ip2、ip3。
我的 GoLang
网站,这是 martini
网络服务器,有两个 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)
}
我 运行 这个 GoLang
站点使用命令 go run site.go
并准备我请求的实验 http://localhost:3000/insert
- 大约一分钟后我的测试数据被插入。
然后我开始测试从二级节点和主节点读取
在 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)
}
运行它go run attacker.go
我刚刚请求URLhttp://localhost:3000/get
4000次每秒。当攻击者工作时,我打开了我所有的 3 个服务器和 运行 htop
命令来观察资源消耗。 PRIMARY 节点显示它处于高负载状态 CPU 大约 80%。中学很平静。
为什么?
因为我使用了 mgo.Monotonic
...
mainSessionForSave.SetMode(mgo.Monotonic, true)
...我希望从所有节点读取:ip1, ip2, ip3
并且我希望在相同的负载和相同的 CPU 消耗下观察所有节点。但事实并非如此。我配置错了什么?事实上 mgo.Monotonic
在我的情况下不起作用,我只从 PRIMARY 节点读取。
您使用后忘记关闭连接:
defer mainSessionForSave.Close()
大概吧,可能是一个原因。
P.S。确保所有节点都可用:)
sessionPerRequest
只创建一次:prepareMartini
在服务器启动时调用,然后设置sessionPerRequest
。传递给 m.Get()
的闭包访问该变量。然后,在第一次写入之后(在测试设置期间),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.
(如果 mgo
在写入主节点后继续从辅助节点读取,读取不一定会反映您刚刚进行的写入,这可能会很痛苦。切换到主节点应该只获得比从辅助服务器获得的数据 更新 的数据,永远不会更旧,从而保持单调性。无论如何,这就是理想的工作方式;请参阅 "open issues" link下面了解更多。)
解决方案是将创建会话向下推到您的处理程序中,例如,删除 sessionPerRequest
并在每个处理程序上放置一些明确的内容,例如
coll := mainSessionForSave.Copy().DB(dbName).Collection(collName)
应根据 open issues with MongoDB consistency 阅读所有一致性承诺:现在,在网络分区期间,读取可以看到旧数据和稍后将回滚的写入,即使 mgo
正在尝试从小学读。 (比较和设置没有这个问题,但当然这是一个更大的更慢的操作。)同样值得细读 post 只是为了讨论一致性级别和不同数据库行为可能的描述应用程序最终用户的清单。