公司大数据部门有一个需求,要将所有的交易数据进行落地,以便进行分析和价格预测等等。
具体场景:
1,写多读少
2,数据量庞大,并且每天日益增长
为什么选mongodb:
1,mongodb是弱数据结构模式,每个文档之间的结构互不影响,日后如果需要增加字段直接增加即可,对集合不会产生任何影响。
2,分布式,面对庞大的数据量,mongo原生支持sharding存储。
3,无事务和join的查询需求
下面使用go来和mongodb进行交互:
mongo serer是3.6版本,安装过程略过。
go的mongo驱动用的是”https://github.com/go-mgo/mgo”
.
.
.
var globalSession = new(mgo.Session)
func MongoInit() error {
var err error
var url string
mongoConfig := config.GetConfig().MongoDb
url = "mongodb://" + mongoConfig.UserName + ":" + mongoConfig.Password + "@" +
mongoConfig.IP + ":" + mongoConfig.Port + "/" + mongoConfig.DbName
globalSession, err = mgo.Dial(url)
if err != nil {
logger.Error("mongo init error", err)
}
globalSession.SetMode(mgo.Strong, true)
return err
}
初始化mongo连接,同时设置模式为”Strong”。
Strong 一致性模式
session 的读写操作总向 primary 服务器发起并使用一个唯一的连接,因此所有的读写操作完全的一致(不存在乱序或者获取到旧数据的问题)。
Monotonic 一致性模式
session 的读操作开始是向某个 secondary 服务器发起(且通过一个唯一的连接),只要出现了一次写操作,session 的连接就会切换至 primary 服务器。由此可见此模式下,能够分散一些读操作到 secondary 服务器,但是读操作不一定能够获得最新的数据。
Eventual 一致性模式
session 的读操作会向任意的 secondary 服务器发起,多次读操作并不一定使用相同的连接,也就是读操作不一定有序。session 的写操作总是向 primary 服务器发起,但是可能使用不同的连接,也就是写操作也不一定有序。Eventual 一致性模式最快,其是一种资源友好(resource-friendly)的模式。
因为mongo没有开启主从,所以这里设为strong一致性模式。
但是strong模式和Monotonic模式会缓存socket到session中,导致我拿到的始终是同一个连接,这对并发请求mongo server会损失一定的效率(一个连接在使用过程中会有多次加锁解锁操作)。所以后续为了从连接池拿空闲的连接而不是一直使用同一个连接,会用到copy方法,拿到一个没有缓存连接的session,这样它就会去连接池拿空闲的可用的连接。
func GetCollection(collectionName string, se *mgo.Session) *mgo.Collection {
return se.DB("").C(collectionName)
}
func GetDb(se *mgo.Session) *mgo.Database {
return se.DB("")
}
func GetSession() *mgo.Session {
return globalSession.Copy()
}
GetSession()方法用的是copy(),下面进源码分析一下
type Session struct {
...
syncTimeout time.Duration
sockTimeout time.Duration
poolLimit int
...
consistency Mode
creds []Credential
...
slaveSocket *mongoSocket
masterSocket *mongoSocket
...
slaveOk bool
}
// Copy works just like New, but preserves the exact authentication
// information from the original session.
func (s *Session) Copy() *Session {
s.m.Lock()
scopy := copySession(s, true)
s.m.Unlock()
scopy.Refresh()
return scopy
}
copy方法,只会将一些认证信息等进行copy,而slaveSocket和masterSocket 不会进行拷贝,这样我从新得到的session去获取连接的时候发现连接为空,就会去连接池里拿空闲可用的连接。
func (s *Session) acquireSocket(slaveOk bool) (*mongoSocket, error) {
// Read-only lock to check for previously reserved socket.
s.m.RLock()
// If there is a slave socket reserved and its use is acceptable, take it as long
// as there isn't a master socket which would be preferred by the read preference mode.
if s.slaveSocket != nil && s.slaveSocket.dead == nil && s.slaveOk && slaveOk && (s.masterSocket == nil || s.consistency != PrimaryPreferred && s.consistency != Monotonic) {
socket := s.slaveSocket
socket.Acquire()
s.m.RUnlock()
return socket, nil
}
if s.masterSocket != nil && s.masterSocket.dead == nil {
socket := s.masterSocket
socket.Acquire()
s.m.RUnlock()
return socket, nil
}
s.m.RUnlock()
// No go. We may have to request a new socket and change the session,
// so try again but with an exclusive lock now.
s.m.Lock()
defer s.m.Unlock()
if s.slaveSocket != nil && s.slaveOk && slaveOk && (s.masterSocket == nil || s.consistency != PrimaryPreferred && s.consistency != Monotonic) {
if s.slaveSocket.dead == nil {
s.slaveSocket.Acquire()
return s.slaveSocket, nil
} else {
s.unsetSocket()
}
}
if s.masterSocket != nil {
if s.masterSocket.dead == nil {
s.masterSocket.Acquire()
return s.masterSocket, nil
} else {
s.unsetSocket()
}
}
// Still not good. We need a new socket.
sock, err := s.cluster().AcquireSocketWithPoolTimeout(
s.consistency, slaveOk && s.slaveOk, s.syncTimeout, s.sockTimeout, s.queryConfig.op.serverTags, s.poolLimit, s.poolTimeout,
)
if err != nil {
return nil, err
}
.
.
.
发现session里面没有连接,则会调用s.cluster().AcquireSocketWithPoolTimeout()方法,再次经过层层调用
if n > 0 {
socket = server.unusedSockets[n-1]
server.unusedSockets[n-1] = nil // Help GC.
server.unusedSockets = server.unusedSockets[:n-1]
info := server.info
server.Unlock()
err = socket.InitialAcquire(info, timeout)
if err != nil {
continue
}
} else {
这个unusedSockets切片,就是我们的空闲连接池,下面是server的数据结构:
type mongoServer struct {
sync.RWMutex
Addr string
ResolvedAddr string
tcpaddr *net.TCPAddr
unusedSockets []*mongoSocket //这个和下面那个liveSockets就是mgo使用的连接池
liveSockets []*mongoSocket
sync chan bool
dial dialer
pingValue time.Duration
pingIndex int
pingWindow [6]time.Duration
info *mongoServerInfo
pingCount uint32
closed bool
abended bool
minPoolSize int
maxIdleTimeMS int
poolWaiter *sync.Cond
}
拿到连接之后,就可以和mongo server交互了,这里就举个增的例子,其余的删改查都差不多。
func Insert(data *models.QuoteData) error {
metal := utils.GetProductNameByInstrumentID(data.InstrumentID)
se := GetSession() //获取session,此方法见文章开头定义
co := GetCollection(metal, se) //获取集合,此方法见文章开头定义
defer se.Close() //最后session别忘了关闭,否则连接池一会会就被用完了
var mongoData = models.MongoQuoteData{
InstrumentId: data.InstrumentID,
TradingDay: data.TradingDay,
Data: data,
}
err := co.Insert(&mongoData) //插入数据
return err
}