package mongo import ( "encoding/json" "fmt" "github.com/globalsign/mgo" webapi_proto "mongo.games.com/game/protocol/webapi" "mongo.games.com/goserver/core/logger" "os" "sync" "time" ) const ( G_P = "__$G_P$__" ) var MgoSessionMgrSington = &MgoSessionMgr{ pltCfgs: new(sync.Map), pltMgos: make(map[string]*PlatformSession), } type MgoCfg struct { HostName string HostPort int32 Database string Username string Password string Options string CfgVer int32 WithEtcd bool } type Config struct { Global map[string]*MgoCfg Platforms map[string]map[string]*MgoCfg // 平台:内部库名称:数据库名称 } type Collection struct { *mgo.Collection } type Database struct { sync.RWMutex *mgo.Database mc map[string]*Collection } func (db *Database) C(name string) (*Collection, bool) { db.RLock() c, ok := db.mc[name] if ok { db.RUnlock() return c, false } db.RUnlock() db.Lock() c, ok = db.mc[name] if ok { db.Unlock() return c, false } cc := &Collection{Collection: db.Database.C(name)} db.mc[name] = cc db.Unlock() return cc, true } type Session struct { sync.RWMutex *mgo.Session db *Database cfg *MgoCfg } func (s *Session) DB() *Database { s.RLock() if s.db != nil { s.RUnlock() return s.db } s.RUnlock() s.Lock() s.db = &Database{Database: s.Session.DB(s.cfg.Database), mc: make(map[string]*Collection)} s.Unlock() return s.db } type PlatformSession struct { sync.RWMutex sesses map[string]*Session // 内部库名称 } func (ps *PlatformSession) GetSession(name string) (*Session, bool) { ps.RLock() s, ok := ps.sesses[name] ps.RUnlock() return s, ok } func (ps *PlatformSession) SetSession(name string, s *Session) { ps.Lock() ps.sesses[name] = s ps.Unlock() } func (ps *PlatformSession) DiscardSession(name string, s *Session) { ps.Lock() old, ok := ps.sesses[name] delete(ps.sesses, name) ps.Unlock() if ok && old != nil && old == s { //1分钟后释放,防止有pending的任务在执行 time.AfterFunc(time.Minute, func() { old.Close() }) } } type MgoSessionMgr struct { sync.RWMutex pltCfgs *sync.Map pltMgos map[string]*PlatformSession // 平台名称 } func newMgoSession(user, password, host string, port int32, options string) (s *mgo.Session, err error) { login := "" if user != "" { login = user + ":" + password + "@" } if host == "" { host = "localhost" } if port == 0 { port = 27017 } if options != "" { options = "?" + options } // http://goneat.org/pkg/labix.org/v2/mgo/#Session.Mongo // [mongodb://][user:pass@]host1[:port1][,host2[:port2],...][/database][?options] url := fmt.Sprintf("mongodb://%s%s:%d/admin%s", login, host, port, options) //fmt.Println(url) session, err := mgo.Dial(url) if err != nil { return nil, err } session.SetSafe(&mgo.Safe{}) return session, nil } func (msm *MgoSessionMgr) LoadConfig(cfgFile string) error { data, err := os.ReadFile(cfgFile) if err != nil { logger.Logger.Errorf("MgoSessionMgr.LoadConfig ReadFile err:%v", err) return err } var cfg Config err = json.Unmarshal(data, &cfg) if err != nil { logger.Logger.Errorf("MgoSessionMgr.LoadConfig Unmarshal err:%v", err) return err } //全局配置 for key, cfg := range cfg.Global { if _, ok := msm.UptCfg(G_P, key, cfg); ok { if ps, olds, ok := msm.HasPltMgoSession(G_P, key); ok { if olds.cfg.CfgVer < cfg.CfgVer { ps.DiscardSession(key, olds) } } } } //平台配置 for plt, cfgs := range cfg.Platforms { for key, cfg := range cfgs { if _, ok := msm.UptCfg(plt, key, cfg); ok { if ps, olds, ok := msm.HasPltMgoSession(plt, key); ok { if olds.cfg.CfgVer < cfg.CfgVer { ps.DiscardSession(key, olds) } } } } } return nil } func (msm *MgoSessionMgr) GetPlt(plt string) (*PlatformSession, bool) { msm.RLock() ps, ok := msm.pltMgos[plt] msm.RUnlock() return ps, ok } func (msm *MgoSessionMgr) HasPltMgoSession(plt, key string) (*PlatformSession, *Session, bool) { ps, ok := msm.GetPlt(plt) if !ok { return nil, nil, false } s, ok := ps.GetSession(key) return ps, s, ok } func (msm *MgoSessionMgr) GetPltMgoSession(plt, key string) *Session { ps, ok := msm.GetPlt(plt) if !ok { msm.Lock() ps, ok = msm.pltMgos[plt] if !ok { ps = &PlatformSession{ sesses: make(map[string]*Session), } msm.pltMgos[plt] = ps } msm.Unlock() } if ps == nil { return nil } if s, ok := ps.GetSession(key); ok { return s } ps.Lock() defer ps.Unlock() s, ok := ps.sesses[key] if ok { return s } if c, ok := msm.GetCfg(plt, key); ok { s, err := newMgoSession(c.Username, c.Password, c.HostName, c.HostPort, c.Options) if s == nil || err != nil { logger.Logger.Error("GetPltMgoSession(%s,%s) err:%v", plt, key, err) return nil } ss := &Session{Session: s, cfg: c} ps.sesses[key] = ss return ss } return nil } func (msm *MgoSessionMgr) GetGlobal(key string) *Session { return msm.GetPltMgoSession(G_P, key) } func (msm *MgoSessionMgr) GetCfg(plt, key string) (*MgoCfg, bool) { if val, ok := msm.pltCfgs.Load(plt); ok { if cfgs, ok := val.(*sync.Map); ok { if cfg, ok := cfgs.Load(key); ok { if c, ok := cfg.(*MgoCfg); ok { return c, true } } } } return nil, false } func (msm *MgoSessionMgr) UptCfg(plt, key string, cfg *MgoCfg) (*MgoCfg, bool) { if val, ok := msm.pltCfgs.Load(plt); ok { cfgs, _ := val.(*sync.Map) if old, ok := cfgs.Load(key); ok { cfgs.Store(key, cfg) return old.(*MgoCfg), true } else { cfgs.Store(key, cfg) return nil, false } } else { cfgs := new(sync.Map) msm.pltCfgs.Store(plt, cfgs) cfgs.Store(key, cfg) return nil, false } } func (msm *MgoSessionMgr) UptCfgWithEtcd(plt, key string, cfg *webapi_proto.MongoDbSetting) { //文件配置优先级高于etcd if oldCfg, exist := msm.GetCfg(plt, key); exist && !oldCfg.WithEtcd { return } newcfg := &MgoCfg{ HostName: cfg.HostName, HostPort: cfg.HostPort, Database: cfg.Database, Username: cfg.Username, Password: cfg.Password, Options: cfg.Options, CfgVer: cfg.CfgVer, WithEtcd: true, } if _, ok := msm.UptCfg(plt, key, newcfg); ok { if ps, olds, ok := msm.HasPltMgoSession(plt, key); ok { if olds.cfg.CfgVer < cfg.CfgVer { ps.DiscardSession(key, olds) } } } return } func (msm *MgoSessionMgr) Close() { msm.RLock() defer msm.RUnlock() for _, plt := range msm.pltMgos { plt.RLock() for _, s := range plt.sesses { s.Close() } plt.RUnlock() } }