From a36e00efeb0d60175328bd80e38f96411ccba2e4 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 28 Jul 2019 13:10:34 +0800 Subject: [PATCH] improve ghttp for session storage with redis server --- g/net/ghttp/ghttp_server_session.go | 111 +++++++++------- g/os/gcache/gcache_z_bench_test.go | 120 +++++++----------- geg/net/ghttp/server/session/session_redis.go | 47 ++++--- version.go | 2 +- 4 files changed, 133 insertions(+), 147 deletions(-) diff --git a/g/net/ghttp/ghttp_server_session.go b/g/net/ghttp/ghttp_server_session.go index 6a2411ae0..68a9c2264 100644 --- a/g/net/ghttp/ghttp_server_session.go +++ b/g/net/ghttp/ghttp_server_session.go @@ -1,9 +1,8 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// 并发安全的Session管理器 package ghttp @@ -20,17 +19,18 @@ import ( "github.com/gogf/gf/g/util/grand" ) -// SESSION对象 +// SESSION对象,并发安全 type Session struct { id string // SessionId data *gmap.StrAnyMap // Session数据 + dirty bool // 数据是否被修改 server *Server // 所属Server request *Request // 关联的请求 } // 生成一个唯一的SessionId字符串,长度18位。 func makeSessionId() string { - return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 36) + grand.RandStr(6)) + return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 36) + grand.Str(6)) } // 获取或者生成一个session对象(延迟初始化) @@ -43,15 +43,12 @@ func GetSession(r *Request) *Session { } } -// 执行初始化(用于延迟初始化). +// 延迟初始化 func (s *Session) init() { if len(s.id) == 0 { s.server = s.request.Server - // 根据提交的SESSION ID获取已存在SESSION - id := s.request.Cookie.GetSessionId() - if id != "" { - data := s.server.sessions.Get(id) - if data != nil { + if id := s.request.Cookie.GetSessionId(); id != "" { + if data := s.server.sessions.Get(id); data != nil { s.id = id s.data = data.(*gmap.StrAnyMap) return @@ -61,14 +58,10 @@ func (s *Session) init() { s.id = s.request.Cookie.MakeSessionId() s.data = gmap.NewStrAnyMap() s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000) + s.dirty = true } } -// MarshalJSON implements the interface MarshalJSON for json.Marshal. -func (s *Session) MarshalJSON() ([]byte, error) { - return json.Marshal(s.data) -} - // 获取/创建SessionId func (s *Session) Id() string { s.init() @@ -97,12 +90,14 @@ func (s *Session) Size() int { func (s *Session) Set(key string, value interface{}) { s.init() s.data.Set(key, value) + s.dirty = true } // 批量设置 func (s *Session) Sets(m map[string]interface{}) { s.init() s.data.Sets(m) + s.dirty = true } // 判断键名是否存在 @@ -114,6 +109,58 @@ func (s *Session) Contains(key string) bool { return false } +// 判断session是否有修改(包括新创建) +func (s *Session) IsDirty() bool { + return s.dirty +} + +// 删除指定session键值对 +func (s *Session) Remove(key string) { + if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { + s.init() + s.data.Remove(key) + s.dirty = true + } +} + +// 将session数据导出为[]byte数据(目前使用json进行序列化) +func (s *Session) Export() (data []byte, err error) { + if s.Size() > 0 { + data, err = json.Marshal(s.data) + } + return +} + +// 从[]byte中恢复session数据(目前使用json进行序列化) +func (s *Session) Restore(data []byte) (err error) { + if len(data) == 0 { + return nil + } + if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { + s.init() + s.data.LockFunc(func(m map[string]interface{}) { + err = json.Unmarshal(data, &m) + }) + } + return +} + +// 清空session +func (s *Session) Clear() { + if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { + s.init() + s.data.Clear() + s.dirty = true + } +} + +// 更新过期时间(如果用在守护进程中长期使用,需要手动调用进行更新,防止超时被清除) +func (s *Session) UpdateExpire() { + if len(s.id) > 0 && s.data.Size() > 0 { + s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000) + } +} + // 获取SESSION变量 func (s *Session) Get(key string, def ...interface{}) interface{} { if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { @@ -133,40 +180,6 @@ func (s *Session) GetVar(key string, def ...interface{}) *gvar.Var { return gvar.New(s.Get(key, def...), true) } -// 删除指定session键值对 -func (s *Session) Remove(key string) { - if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { - s.init() - s.data.Remove(key) - } -} - -// 从json字符串中恢复session数据 -func (s *Session) RestoreFromJson(data []byte) (err error) { - if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { - s.init() - s.data.LockFunc(func(m map[string]interface{}) { - err = json.Unmarshal(data, &m) - }) - } - return -} - -// 清空session -func (s *Session) Clear() { - if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { - s.init() - s.data.Clear() - } -} - -// 更新过期时间(如果用在守护进程中长期使用,需要手动调用进行更新,防止超时被清除) -func (s *Session) UpdateExpire() { - if len(s.id) > 0 && s.data.Size() > 0 { - s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000) - } -} - func (s *Session) GetString(key string, def ...interface{}) string { return gconv.String(s.Get(key, def...)) } diff --git a/g/os/gcache/gcache_z_bench_test.go b/g/os/gcache/gcache_z_bench_test.go index ffef27c30..066585624 100644 --- a/g/os/gcache/gcache_z_bench_test.go +++ b/g/os/gcache/gcache_z_bench_test.go @@ -10,104 +10,70 @@ package gcache_test import ( "github.com/gogf/gf/g/os/gcache" - "sync" "testing" ) var ( - c = gcache.New() - clru = gcache.New(10000) - mInt = make(map[int]int) - mMap = make(map[interface{}]interface{}) - - muInt = sync.RWMutex{} - muMap = sync.RWMutex{} + cache = gcache.New() + cacheLru = gcache.New(10000) ) func Benchmark_CacheSet(b *testing.B) { - for i := 0; i < b.N; i++ { - c.Set(i, i, 0) - } + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + cache.Set(i, i, 0) + i++ + } + }) } func Benchmark_CacheGet(b *testing.B) { - for i := 0; i < b.N; i++ { - c.Get(i) - } + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + cache.Get(i) + i++ + } + }) } func Benchmark_CacheRemove(b *testing.B) { - for i := 0; i < b.N; i++ { - c.Remove(i) - } + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + cache.Remove(i) + i++ + } + }) } func Benchmark_CacheLruSet(b *testing.B) { - for i := 0; i < b.N; i++ { - clru.Set(i, i, 0) - } + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + cacheLru.Set(i, i, 0) + i++ + } + }) } func Benchmark_CacheLruGet(b *testing.B) { - for i := 0; i < b.N; i++ { - clru.Get(i) - } + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + cacheLru.Get(i) + i++ + } + }) } func Benchmark_CacheLruRemove(b *testing.B) { - for i := 0; i < b.N; i++ { - clru.Remove(i) - } -} - -func Benchmark_InterfaceMapWithLockSet(b *testing.B) { - for i := 0; i < b.N; i++ { - muMap.Lock() - mMap[i] = i - muMap.Unlock() - } -} - -func Benchmark_InterfaceMapWithLockGet(b *testing.B) { - for i := 0; i < b.N; i++ { - muMap.RLock() - if _, ok := mMap[i]; ok { - + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + cacheLru.Remove(i) + i++ } - muMap.RUnlock() - } -} - -func Benchmark_InterfaceMapWithLockRemove(b *testing.B) { - for i := 0; i < b.N; i++ { - muMap.Lock() - delete(mMap, i) - muMap.Unlock() - } -} - -func Benchmark_IntMapWithLockWithLockSet(b *testing.B) { - for i := 0; i < b.N; i++ { - muInt.Lock() - mInt[i] = i - muInt.Unlock() - } -} - -func Benchmark_IntMapWithLockGet(b *testing.B) { - for i := 0; i < b.N; i++ { - muInt.RLock() - if _, ok := mInt[i]; ok { - - } - muInt.RUnlock() - } -} - -func Benchmark_IntMapWithLockRemove(b *testing.B) { - for i := 0; i < b.N; i++ { - muInt.Lock() - delete(mInt, i) - muInt.Unlock() - } + }) } diff --git a/geg/net/ghttp/server/session/session_redis.go b/geg/net/ghttp/server/session/session_redis.go index 525f43b8e..bfd6164ce 100644 --- a/geg/net/ghttp/server/session/session_redis.go +++ b/geg/net/ghttp/server/session/session_redis.go @@ -1,8 +1,6 @@ package main import ( - "encoding/json" - "github.com/gogf/gf/g" "github.com/gogf/gf/g/net/ghttp" "github.com/gogf/gf/g/os/gtime" @@ -22,17 +20,21 @@ func SessionGet(r *ghttp.Request) { // 请求处理之前将Redis中的数据读取出来并存储到SESSION对象中。 func RedisHandlerGet(r *ghttp.Request) { if !r.IsFileRequest() { - sessionId := r.Cookie.GetSessionId() - if sessionId == "" { + id := r.Cookie.GetSessionId() + if id == "" { return } - value, err := g.Redis().DoVar("GET", sessionId) + // 当内存中的SESSION存在时不需要读取Redis + if r.Session.Size() > 0 { + return + } + // SESSION不存在时,例如服务重启,自动从Redis读取并恢复数据 + value, err := g.Redis().DoVar("GET", id) if err != nil { panic(err) } if !value.IsNil() { - err := r.Session.RestoreFromJson(value.Bytes()) - if err != nil { + if err := r.Session.Restore(value.Bytes()); err != nil { panic(err) } } @@ -42,25 +44,30 @@ func RedisHandlerGet(r *ghttp.Request) { // 请求结束时将SESSION数据存储到Redis中,或者在SESSION删除时也删除Redis中的数据。 func RedisHandlerSet(r *ghttp.Request) { if !r.IsFileRequest() { - sessionId := r.Cookie.GetSessionId() - if sessionId == "" { + id := r.Cookie.GetSessionId() + if id == "" { return } err := (error)(nil) + value := ([]byte)(nil) if r.Session.Size() > 0 { - value, err := json.Marshal(r.Session) - if err != nil { - panic(err) - } - _, err = g.Redis().Do("SETEX", r.Cookie.GetSessionId(), r.Server.GetSessionMaxAge(), value) - if err != nil { - panic(err) + if value, err = r.Session.Export(); err == nil { + if len(value) == 0 { + return + } else if !r.Session.IsDirty() { + // 更新过期时间 + _, err = g.Redis().Do("EXPIRE", id, r.Server.GetSessionMaxAge()) + } else { + // 更新Redis数据 + _, err = g.Redis().Do("SETEX", id, r.Server.GetSessionMaxAge(), value) + } } } else { - _, err = g.Redis().Do("DEL", r.Cookie.GetSessionId()) - if err != nil { - panic(err) - } + // 清空SESSION后自动删除Redis数据 + _, err = g.Redis().Do("DEL", id) + } + if err != nil { + panic(err) } } } diff --git a/version.go b/version.go index 87865121b..e5fd06629 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gf -const VERSION = "v1.8.2" +const VERSION = "v1.8.3" const AUTHORS = "john"