GoFrame GoFrame - SESSION 管理

gqcn · 2020年02月12日 · 593 次阅读

GF框架提供了完善的Session管理能力,由gsession模块实现。由于Session机制在HTTP服务中最常用,因此后续章节中将着重以HTTP服务为示例介绍Session的使用。

接口文档

https://godoc.org/github.com/gogf/gf/os/gsession

任何时候都可以通过ghttp.Request获取Session对象,因为CookieSession都是和请求会话相关,因此都属于Request的成员对象,并对外公开。GF框架的Session默认过期时间是24小时

SessionId默认通过Cookie来传递,并且也支持客户端通过Header传递SessionIdSessionId的识别名称可以通过ghttp.ServerSetSessionIdName进行修改。

此外,需要说明的是,Session的操作是支持并发安全的,这也是框架在对Session的设计上不采用直接以map的形式操作数据的原因。在HTTP请求流程中,我们可以通过ghttp.Request对象来获取Session对象,并执行相应的数据操作。

gsession模块

Session的管理功能由独立的gsession模块实现,并已完美整合到了ghttp.Server中。由于该模块是解耦独立的,因此可以应用到更多不同的场景中,例如:TCP通信、gRPC接口服务等等。

gsession模块中有比较重要的三个对象/接口:

  1. gsession.Manager:管理Session对象、Storage持久化存储对象、以及过期时间控制。
  2. gsession.Session:单个Session会话管理对象,用于Session参数的增删查改等数据管理操作。
  3. gsession.Storage:这是一个接口定义,用于Session对象的持久化存储、数据写入/读取、存活更新等操作,开发者可基于该接口实现自定义的持久化存储特性。该接口定义如下: https://github.com/gogf/gf/blob/master/os/gsession/gsession_storage.go

    type Storage interface {
        // New creates a custom session id.
        // This function can be used for custom session creation.
        New(ttl time.Duration) (id string)
    
        // Get retrieves session value with given key.
        // It returns nil if the key does not exist in the session.
        Get(id string, key string) interface{}
    
        // GetMap retrieves all key-value pairs as map from storage.
        GetMap(id string) map[string]interface{}
    
        // GetSize retrieves the size of key-value pairs from storage.
        GetSize(id string) int
    
        // Set sets key-value session pair to the storage.
        // The parameter <ttl> specifies the TTL for the session id (not for the key-value pair).
        Set(id string, key string, value interface{}, ttl time.Duration) error
    
        // SetMap batch sets key-value session pairs with map to the storage.
        // The parameter <ttl> specifies the TTL for the session id(not for the key-value pair).
        SetMap(id string, data map[string]interface{}, ttl time.Duration) error
    
        // Remove deletes key with its value from storage.
        Remove(id string, key string) error
    
        // RemoveAll deletes all key-value pairs from storage.
        RemoveAll(id string) error
    
        // GetSession returns the session data as *gmap.StrAnyMap for given session id from storage.
        //
        // The parameter <ttl> specifies the TTL for this session, and it returns nil if the TTL is exceeded.
        // The parameter <data> is the current old session data stored in memory,
        // and for some storage it might be nil if memory storage is disabled.
        //
        // This function is called ever when session starts.
        GetSession(id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error)
    
        // SetSession updates the data for specified session id.
        // This function is called ever after session, which is changed dirty, is closed.
        // This copy all session data map from memory to storage.
        SetSession(id string, data *gmap.StrAnyMap, ttl time.Duration) error
    
        // UpdateTTL updates the TTL for specified session id.
        // This function is called ever after session, which is not dirty, is closed.
        UpdateTTL(id string, ttl time.Duration) error
    }
    

存储实现方式

gsession实现并为开发者提供了常见的三种Session存储实现方式:

  1. 基于文件存储(默认):单节点部署方式下比较高效的持久化存储方式;
  2. 基于纯内存存储:性能最高效,但是无法持久化保存,重启即丢失;
  3. 基于Redis存储:远程Redis节点存储Session数据,支持应用多节点部署;

三种方式各有优劣,详细介绍请查看后续章节。

内存存储

内存存储比较简单,性能也很高效,但没有持久化存储Session数据,因此应用程序重启之后便会丢失Session数据,可用于特定的业务场景中。gsession内存存储使用StorageMemory对象实现,

使用示例

https://github.com/gogf/gf/blob/master/.example/os/gsession/storage-memory/memory.go

package main

import (
    "github.com/gogf/gf/frame/g"
    "github.com/gogf/gf/net/ghttp"
    "github.com/gogf/gf/os/gsession"
    "github.com/gogf/gf/os/gtime"
    "time"
)

func main() {
    s := g.Server()
    s.SetConfigWithMap(g.Map{
        "SessionMaxAge":  time.Minute,
        "SessionStorage": gsession.NewStorageMemory(),
    })
    s.Group("/", func(group *ghttp.RouterGroup) {
        group.ALL("/set", func(r *ghttp.Request) {
            r.Session.Set("time", gtime.Timestamp())
            r.Response.Write("ok")
        })
        group.ALL("/get", func(r *ghttp.Request) {
            r.Response.Write(r.Session.Map())
        })
        group.ALL("/del", func(r *ghttp.Request) {
            r.Session.Clear()
            r.Response.Write("ok")
        })
    })
    s.SetPort(8199)
    s.Run()
}

在该实例中,为了方便观察过期失效,我们将Session的过期时间设置为1分钟。执行后,

  1. 首先,访问 http://127.0.0.1:8199/set 设置一个Session变量;
  2. 随后,访问 http://127.0.0.1:8199/get 可以看到该Session变量已经设置并成功获取;
  3. 接着,我们停止程序,并重新启动,再次访问 http://127.0.0.1:8199/get ,可以看到Session变量已经没有了;

文件存储

在默认情况下,ghttp.ServerSession存储使用了内存+文件的方式,使用StorageFile对象实现。具体原理为:

  1. Session的数据操作完全基于内存;
  2. 使用gcache进程缓存模块控制数据过期;
  3. 使用文件存储持久化存储管理Session数据;
  4. 当且仅有当Session被标记为dirty时(数据有更新)才会执行Session序列化并执行文件持久化存储;
  5. 当且仅当内存中的Session不存在时,才会从文件存储中反序列化恢复Session数据到内存中,降低IO调用;
  6. 序列化/反序列化使用的是标准库的json.Marshal/UnMarshal方法;

从原理可知,当Session为读多写少的场景中,Session的数据操作非常高效。

有个注意的细节,由于文件存储涉及到文件操作,为便于降低IO开销并提高Session操作性能,并不是每一次Session请求结束后都会立即刷新对应SessionTTL时间。而只有当涉及到更新操作(被标记为dirty)时才会立即刷新其TTL;针对于读取请求,将会每隔一分钟更新前一分钟内读取操作对应的Session文件TTL时间,以便于Session自动续活。

使用示例

https://github.com/gogf/gf/blob/master/.example/os/gsession/storage-file/file.go

package main

import (
    "github.com/gogf/gf/frame/g"
    "github.com/gogf/gf/net/ghttp"
    "github.com/gogf/gf/os/gtime"
    "time"
)

func main() {
    s := g.Server()
    s.SetConfigWithMap(g.Map{
        "SessionMaxAge": time.Minute,
    })
    s.Group("/", func(group *ghttp.RouterGroup) {
        group.ALL("/set", func(r *ghttp.Request) {
            r.Session.Set("time", gtime.Timestamp())
            r.Response.Write("ok")
        })
        group.ALL("/get", func(r *ghttp.Request) {
            r.Response.Write(r.Session.Map())
        })
        group.ALL("/del", func(r *ghttp.Request) {
            r.Session.Clear()
            r.Response.Write("ok")
        })
    })
    s.SetPort(8199)
    s.Run()
}

在该实例中,为了方便观察过期失效,我们将Session的过期时间设置为1分钟。执行后,

  1. 首先,访问 http://127.0.0.1:8199/set 设置一个Session变量;
  2. 随后,访问 http://127.0.0.1:8199/get 可以看到该Session变量已经设置并成功获取;
  3. 接着,我们停止程序,并重新启动,再次访问 http://127.0.0.1:8199/get ,可以看到Session变量已经从文件存储中恢复;
  4. 等待 1 分钟后,再次访问 http://127.0.0.1:8199/get 可以看到已经无法获取该Session,因为该Session已经过期;

Redis 存储

文件存储的方式在单节点的场景下非常不错,但是涉及到对应用进行多节点部署的场景下,各个节点的Session无法共享,因此需要将Session存储单独剥离出来管理,Redis服务器是比较常见的一个选择。

gsessionRedis存储使用StorageRedis对象实现,与文件存储比较类似,为了提高执行效率,也是采用了内存+Redis的方式。与文件存储唯一不同的是,在每一次请求中如果需要对Session进行操作时,将会从Redis中拉取一次最新的Session数据(而文件存储只会在Session不存在时读取一次文件)。

使用示例

https://github.com/gogf/gf/blob/master/.example/os/gsession/storage-redis/redis.go

package main

import (
    "github.com/gogf/gf/frame/g"
    "github.com/gogf/gf/net/ghttp"
    "github.com/gogf/gf/os/gsession"
    "github.com/gogf/gf/os/gtime"
    "time"
)

func main() {
    s := g.Server()
    s.SetConfigWithMap(g.Map{
        "SessionMaxAge":  time.Minute,
        "SessionStorage": gsession.NewStorageRedis(g.Redis()),
    })
    s.Group("/", func(group *ghttp.RouterGroup) {
        group.ALL("/set", func(r *ghttp.Request) {
            r.Session.Set("time", gtime.Timestamp())
            r.Response.Write("ok")
        })
        group.ALL("/get", func(r *ghttp.Request) {
            r.Response.Write(r.Session.Map())
        })
        group.ALL("/del", func(r *ghttp.Request) {
            r.Session.Clear()
            r.Response.Write("ok")
        })
    })
    s.SetPort(8199)
    s.Run()
}

在该实例中,为了方便观察过期失效,我们将Session的过期时间设置为1分钟。执行后,

  1. 首先,访问 http://127.0.0.1:8199/set 设置一个Session变量;
  2. 随后,访问 http://127.0.0.1:8199/get 可以看到该Session变量已经设置并成功获取;
  3. 接着,我们停止程序,并重新启动,再次访问 http://127.0.0.1:8199/get ,可以看到Session变量已经从Redis存储中恢复;如果我们手动修改Redis中的对应键值数据,页面刷新时也会读取到最新的值;
  4. 等待 1 分钟后,再次访问 http://127.0.0.1:8199/get 可以看到已经无法获取该Session,因为该Session已经过期;
更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册