zoukankan      html  css  js  c++  java
  • beego Session redis源码解读

    在文章beego Session redis存储以及是否阻塞 我们说到session 是无阻塞的,session的接口对用户来说是一个字典(C# diction, Go是map), 存储所有的ssion也是一个字典(类似于C# Dictionary<string【sessionid 用于区分用户】,Dictionary<string【用户设置的key】,object>> )

    首先我们来看看redis session (ledis_session.go)的源码吧,和C#相同, 有一个SessionStore负责存储和Provider;

    package redis
     
    import (
        "net/http"
        "strconv"
        "strings"
        "sync"
        "time"
     
        "github.com/astaxie/beego/session"
     
        "github.com/gomodule/redigo/redis"
    )
     
    var redispder = &Provider{}
     
    // MaxPoolSize redis max pool size
    var MaxPoolSize = 100
     
    // SessionStore redis session store
    type SessionStore struct {
        p           *redis.Pool
        sid         string
        lock        sync.RWMutex
        values      map[interface{}]interface{}
        maxlifetime int64
    }
     
    // Set value in redis session
    func (rs *SessionStore) Set(key, value interface{}) error {
        rs.lock.Lock()
        defer rs.lock.Unlock()
        rs.values[key] = value
        return nil
    }
     
    // Get value in redis session
    func (rs *SessionStore) Get(key interface{}) interface{} {
        rs.lock.RLock()
        defer rs.lock.RUnlock()
        if v, ok := rs.values[key]; ok {
            return v
        }
        return nil
    }
     
    // Delete value in redis session
    func (rs *SessionStore) Delete(key interface{}) error {
        rs.lock.Lock()
        defer rs.lock.Unlock()
        delete(rs.values, key)
        return nil
    }
     
    // Flush clear all values in redis session
    func (rs *SessionStore) Flush() error {
        rs.lock.Lock()
        defer rs.lock.Unlock()
        rs.values = make(map[interface{}]interface{})
        return nil
    }
     
    // SessionID get redis session id
    func (rs *SessionStore) SessionID() string {
        return rs.sid
    }
     
    // SessionRelease save session values to redis
    func (rs *SessionStore) SessionRelease(w http.ResponseWriter) {
        b, err := session.EncodeGob(rs.values)
        if err != nil {
            return
        }
        c := rs.p.Get()
        defer c.Close()
        c.Do("SETEX", rs.sid, rs.maxlifetime, string(b))
    }
     
    // Provider redis session provider
    type Provider struct {
        maxlifetime int64
        savePath    string
        poolsize    int
        password    string
        dbNum       int
        poollist    *redis.Pool
    }
     
    // SessionInit init redis session
    // savepath like redis server addr,pool size,password,dbnum,IdleTimeout second
    // e.g. 127.0.0.1:6379,100,astaxie,0,30
    func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
        rp.maxlifetime = maxlifetime
        configs := strings.Split(savePath, ",")
        if len(configs) > 0 {
            rp.savePath = configs[0]
        }
        if len(configs) > 1 {
            poolsize, err := strconv.Atoi(configs[1])
            if err != nil || poolsize < 0 {
                rp.poolsize = MaxPoolSize
            } else {
                rp.poolsize = poolsize
            }
        } else {
            rp.poolsize = MaxPoolSize
        }
        if len(configs) > 2 {
            rp.password = configs[2]
        }
        if len(configs) > 3 {
            dbnum, err := strconv.Atoi(configs[3])
            if err != nil || dbnum < 0 {
                rp.dbNum = 0
            } else {
                rp.dbNum = dbnum
            }
        } else {
            rp.dbNum = 0
        }
        var idleTimeout time.Duration = 0
        if len(configs) > 4 {
            timeout, err := strconv.Atoi(configs[4])
            if err == nil && timeout > 0 {
                idleTimeout = time.Duration(timeout) * time.Second
            }
        }
        rp.poollist = &redis.Pool{
            Dial: func() (redis.Conn, error) {
                c, err := redis.Dial("tcp", rp.savePath)
                if err != nil {
                    return nil, err
                }
                if rp.password != "" {
                    if _, err = c.Do("AUTH", rp.password); err != nil {
                        c.Close()
                        return nil, err
                    }
                }
                // some redis proxy such as twemproxy is not support select command
                if rp.dbNum > 0 {
                    _, err = c.Do("SELECT", rp.dbNum)
                    if err != nil {
                        c.Close()
                        return nil, err
                    }
                }
                return c, err
            },
            MaxIdle: rp.poolsize,
        }
     
        rp.poollist.IdleTimeout = idleTimeout
     
        return rp.poollist.Get().Err()
    }
     
    // SessionRead read redis session by sid
    func (rp *Provider) SessionRead(sid string) (session.Store, error) {
        c := rp.poollist.Get()
        defer c.Close()
     
        var kv map[interface{}]interface{}
     
        kvs, err := redis.String(c.Do("GET", sid))
        if err != nil && err != redis.ErrNil {
            return nil, err
        }
        if len(kvs) == 0 {
            kv = make(map[interface{}]interface{})
        } else {
            if kv, err = session.DecodeGob([]byte(kvs)); err != nil {
                return nil, err
            }
        }
     
        rs := &SessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime}
        return rs, nil
    }
     
    // SessionExist check redis session exist by sid
    func (rp *Provider) SessionExist(sid string) bool {
        c := rp.poollist.Get()
        defer c.Close()
     
        if existed, err := redis.Int(c.Do("EXISTS", sid)); err != nil || existed == 0 {
            return false
        }
        return true
    }
     
    // SessionRegenerate generate new sid for redis session
    func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
        c := rp.poollist.Get()
        defer c.Close()
     
        if existed, _ := redis.Int(c.Do("EXISTS", oldsid)); existed == 0 {
            // oldsid doesn't exists, set the new sid directly
            // ignore error here, since if it return error
            // the existed value will be 0
            c.Do("SET", sid, "", "EX", rp.maxlifetime)
        } else {
            c.Do("RENAME", oldsid, sid)
            c.Do("EXPIRE", sid, rp.maxlifetime)
        }
        return rp.SessionRead(sid)
    }
     
    // SessionDestroy delete redis session by id
    func (rp *Provider) SessionDestroy(sid string) error {
        c := rp.poollist.Get()
        defer c.Close()
     
        c.Do("DEL", sid)
        return nil
    }
     
    // SessionGC Impelment method, no used.
    func (rp *Provider) SessionGC() {
    }
     
    // SessionAll return all activeSession
    func (rp *Provider) SessionAll() int {
        return 0
    }
     
    func init() {
        session.Register("redis", redispder)
    }

    SessionStore的Get,Set,Delete,Flush 都是操作自己的字典values (map[interface{}]interface{}),所以在一个具体的api 里面设置了session 然后获取 肯定不会串,因为这个时候都还在内存 没有保存到redis。Provider的SessionInit实际是初始化redis的一些链接信息,func (rp *Provider) SessionRead(sid string) (session.Store, error)  这个才是真正加载session的,func (rs *SessionStore) SessionRelease(w http.ResponseWriter) 才是真生保存session的。具体在哪里加载和保存的了?在router.go的func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)  方法里面有如下代码:

    // session init
        if BConfig.WebConfig.Session.SessionOn {
            var err error
            context.Input.CruSession, err = GlobalSessions.SessionStart(rw, r)
            if err != nil {
                logs.Error(err)
                exception("503", context)
                goto Admin
            }
            defer func() {
                if context.Input.CruSession != nil {
                    context.Input.CruSession.SessionRelease(rw)
                }
            }()
        }

    GlobalSessions.SessionStart(rw, r)的实现在session.go的func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Store, err error)方法里面, 有如下代码:

    if sid != "" && manager.provider.SessionExist(sid) {
            return manager.provider.SessionRead(sid)
        }
     
        // Generate a new session
        sid, errs = manager.sessionID()
        if errs != nil {
            return nil, errs
        }
     
        session, err = manager.provider.SessionRead(sid)
        if err != nil {
            return nil, err
        }

    我们知道在http 请求开始, beego去装载session,然后在请求方法结束的时候去保存session。下次获取的session 就是最近一次保存的值,而不是最后一次请求的数据。

    go的代码:

    func (c *MainController) V1() {
        v := c.GetSession("asta")
        time.Sleep(time.Second * 4)
        c.SetSession("asta", "v1")
        c.Data["json"] = fmt.Sprintf("get session:%s, set session:V1", v)
        c.ServeJSON()
    }
    func (c *MainController) V2() {
        v := c.GetSession("asta")
        time.Sleep(time.Second * 2)
        c.SetSession("asta", "v2")
        c.Data["json"] = fmt.Sprintf("get session:%s, set session:V2", v)
     
        c.ServeJSON()
    }
    func (c *MainController) V3() {
        v := c.GetSession("asta")
        c.Data["json"] = fmt.Sprintf("get session:%s", v)
        c.ServeJSON()
    }

    html代码:

     
    <body>
        <div>test</div>
        <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
        <input type="button" value="set session" id="setsession"/><br/>
       V1: <p id="txt1"></p><br/>
       V2:  <p id="txt2"></p><br/>
       <input type="button" value="get session" id="getsession"/><br/>
       V3:  <p id="txt3"></p><br/>
         <script>
        $("#setsession").click(function(){
            $.get("http://localhost:8080/v1",function(data){$("#txt1").html(data) ;});
            $.get("http://localhost:8080/v2",function(data){$("#txt2").html(data) ;});
          
        });
        $("#getsession").click(function(){
            $.get("http://localhost:8080/v3",function(data){$("#txt3").html(data) ;});
          
        });
      </script>
    </body>

    先点击setsession调用V1和V2, 按理说V1先到达->V2到达->V2返回->V1返回,session最后保存的是V1的值

     【通过打印信息可以得到如图】

  • 相关阅读:
    Java重命名文件
    三星Samsung 4.4.2该负责人制度,简化名单
    hdu 1203 I NEED A OFFER!
    springMVC整合JAXB
    主流芯片解决方案Ambarella的高清网络摄像机、德州仪器和控制海思
    Objective-C路成魔【18-复制对象】
    QStyleFactory类参考
    QT QSqlQuery QSqlQueryModel
    linux mysql 卸载后重装
    更改Mysql数据库存储位置的具体步骤
  • 原文地址:https://www.cnblogs.com/majiang/p/14180895.html
Copyright © 2011-2022 走看看