回想起MySQL中輔助索引和聚簇索引的關係和php7.0版本中Array的底層數據結構實現,session的底層數據結構也以類似的數據結構組合模式進行session會話的管理,所以用到的核心數據結構爲最小堆+Map。使用最小堆的目的是爲了session集合進行有效管理,而Map則是爲了通過sid快速找到session。(分享一個開發心得:session 不僅佔用服務器資源再加上其生產方式,很容易受到網絡黑客攻擊,所以只給合法的用戶在服務端設置session,沒通過驗證的,我們儘量使用cookie代替session的功能——`cookie: "你的良心不會痛嗎?",session:"不會!!!"`。)
廢話不多說上源碼!
代碼架構如下:
|------------------------
|- component
|-- test.go
|- include
|-- contrainer.go
|-- pkg.go
|-- redis.go
|-- session.go
|- init
|-- init.go
|- test
|-- session.go
|- util
|-- rand.go
|- main.go
|------------------------
a. 程序入口文件 main.go
package main
import _ "./include"
import "./test"
func main() {
test.TSession()
}
b. 編寫基礎結構+接口
與測試相關
package component
type DpTest struct {
}
c. 系統初始化
package init
import _ "../include"
d. 依賴包加載
(1) 容器
//容器, 用於管理中間件
package include
import (
"fmt"
"sync"
)
type Container struct {
mux sync.Mutex
mds map[string]interface{}
}
var Cont *Container = &Container{
sync.Mutex{},
make(map[string]interface{}, 8),
}
func (c *Container) Register(n string, sl interface{}) {
c.mux.Lock()
defer c.mux.Unlock()
_, ok := c.mds[n]
if !ok {
c.mds[n] = sl
}
}
func (c *Container) Get(n string) (interface{}, error) {
sl, ok := c.mds[n]
if !ok {
return nil, fmt.Errorf("未找到單例`%v`", n)
}
return sl, nil
}
(2) redis中間件引用
package include
import (
"fmt"
"github.com/go-redis/redis"
)
var rc *redis.Client
func init() {
lc := struct {
Host string
Port string
Password string
}{
`localhost`,
`6379`,
``,
}
dsn := lc.Host + ":" + lc.Port
rc = redis.NewClient(&redis.Options{
Addr: dsn,
Password: lc.Password,
})
fmt.Printf("rc:`%v`\n", rc)
Cont.Register("redis", rc)
}
func GetRedis(r interface{}) *redis.Client {
rc, ok := r.(*redis.Client)
if !ok {
return nil
}
return rc
}
(3) session會話管理實現
package include
import (
"encoding/json"
"fmt"
"reflect"
"sync"
"time"
//工具
"../util"
)
type SessionError struct {
err string
}
func (s *SessionError) Error() string {
return s.err
}
type SessionStore struct {
id string //sessionID
data map[string]interface{}
expireAt time.Time //過期時間
lastUpAt time.Time //上一次更新時間
}
type Manager struct {
sm map[string]int //索引表
sl []*SessionStore //數組結構
lock sync.RWMutex //互斥鎖
prefix string //前綴
expired int //有效時間, 單位m
openSession int //當前管理的會話數量
maxOpenSession int //最大會話數量
}
var (
mg Manager
SessionNotFound = &SessionError{
"session不存在",
}
SessionNotInit = &SessionError{
"session初始化未完成",
}
ManagerNotInit = &SessionError{
"manager初始化未完成",
}
)
func IsManagerNotInit(err error) bool {
if err == nil {
return false
}
if err != ManagerNotInit {
return false
}
return true
}
func IsSessionNotInit(err error) bool {
if err == nil {
return false
}
if err != SessionNotInit {
return false
}
return true
}
func IsSessionNotFound(err error) bool {
if err == nil {
return false
}
if err != SessionNotFound {
return false
}
return true
}
func init() {
mc := struct {
Prefix string
Expired int
MaxOpenSession int
}{
`dora`,
2,
10000,
}
gcTime := time.Duration(30)
mg = Manager{
sm: make(map[string]int, 1024),
sl: make([]*SessionStore, 0, 1024),
lock: sync.RWMutex{},
prefix: mc.Prefix,
expired: mc.Expired,
maxOpenSession: mc.MaxOpenSession,
}
Cont.Register("session", &mg)
go func(gc time.Duration) {
for {
select {
case <-time.After(gc * time.Minute):
//計時
ts := time.Now()
mg.gc()
tc := time.Since(ts).Milliseconds()
//todo log tc
_ = tc
}
}
}(gcTime)
}
func GetManager(s interface{}) *Manager {
m, ok := s.(*Manager)
if !ok {
return nil
}
return m
}
func (s *SessionStore) Set(key string, value interface{}) error {
ref := reflect.ValueOf(value)
switch ref.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
s.data[key] = ref.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
s.data[key] = ref.Uint()
case reflect.Float32, reflect.Float64:
s.data[key] = fmt.Sprintf("%.6f", ref.Float())
case reflect.String:
s.data[key] = ref.String()
case reflect.Bool:
s.data[key] = ref.Bool()
default:
s.data[key] = fmt.Sprintf("%v", value)
}
s.update()
return nil
}
func (s *SessionStore) Get(key string) interface{} {
if v, ok := s.data[key]; ok {
return v
}
return nil
}
func (s *SessionStore) Delete(key string) error {
if _, ok := s.data[key]; ok {
delete(s.data, key)
} else {
return SessionNotFound
}
return nil
}
func (s *SessionStore) SessionID() (string, error) {
return s.id, nil
}
func (s *SessionStore) Destroy() error {
//置爲過期
s.expireAt = time.Unix(0, 0)
s.lastUpAt = time.Unix(0, 0)
//清空緩存
_, err := rc.Del(s.id).Result()
if err != nil {
//todo log
}
return nil
}
//數據同步到redis緩存中
func (s *SessionStore) update() {
if len(s.data) > 0 {
//sync cache
jd, err := json.Marshal(s.data)
if err != nil {
//todo log
}
_, err = rc.Set(s.id, jd, time.Duration(mg.expired)*time.Minute).Result()
if err != nil {
//todo log
}
s.lastUpAt = time.Now()
s.expireAt = s.lastUpAt.Add(time.Duration(mg.expired) * time.Minute)
}
}
//會話初始化
func (m *Manager) SessionInit() (*SessionStore, error) {
var sid string
var session *SessionStore
sid = m.prefix + "_" + util.CreateUUID()
//不需要進行堆的調整, 所以忽略第二個返回值
session, _ = m.getSS(sid)
if session == nil {
session = m.setSS(sid)
}
return session, nil
}
func (m *Manager) SessionRead(sid string) (*SessionStore, error) {
var session *SessionStore
var needAdjustHeap bool
session, needAdjustHeap = m.getSS(sid)
if session == nil {
return nil, SessionNotFound
}
if needAdjustHeap {
session.update()
m.heapDown(m.sm[session.id])
}
return session, nil
}
//清除過期會話-暫時不考慮會話上限
func (m *Manager) gc() {
var session *SessionStore
if len(m.sl) > 0 {
for {
if len(m.sl) == 0 {
break
}
session = m.sl[0]
timestamp := time.Now().Unix()
if session.expireAt.Unix() < timestamp {
//清空session緩存中的內容
m.heapCrop(session)
continue
}
break
}
}
}
func (m *Manager) heapCrop(session *SessionStore) {
var heapLastIndex int
m.lock.Lock()
defer m.lock.Unlock()
if session == nil {
return
}
//會話銷燬
session.Destroy()
delete(m.sm, session.id)
heapLastIndex = m.openSession - 1
//從索引中刪除該session
if heapLastIndex < 0 {
return
}
//堆刪除
m.sl[0], m.sl[heapLastIndex] = m.sl[heapLastIndex], m.sl[0]
//更新索引
m.sm[m.sl[0].id] = 0
if heapLastIndex == 0 {
//不會收m.sl所使用的堆空間(即始終保持着那塊空間的引用)
m.openSession = 0
} else {
//堆的大小-1
m.sl = m.sl[:heapLastIndex-1]
m.openSession = len(m.sl)
//堆調整
m.heapDown(0)
}
}
//堆調整-下沉
func (m *Manager) heapDown(i int) {
var heapLastIndex int
m.lock.Lock()
defer m.lock.Unlock()
heapLastIndex = m.openSession-1
for {
nl := i*2 + 1 //左節點所在位置
nr := i*2 + 2 //右節點所在位置
if nl <= heapLastIndex && nr <= heapLastIndex {
if m.sl[nl].lastUpAt.Unix() >= m.sl[nr].lastUpAt.Unix() && m.sl[i].lastUpAt.Unix() < m.sl[nl].lastUpAt.Unix() {
//與左節點比較, 如果左右節點相等的情況, 左子節點優先進行
m.sm[m.sl[nl].id], m.sm[m.sl[i].id] = m.sm[m.sl[i].id], m.sm[m.sl[nl].id]
m.sl[nl], m.sl[i] = m.sl[i], m.sl[nl]
i = nl
continue
} else if m.sl[i].lastUpAt.Unix() < m.sl[nr].lastUpAt.Unix() {
//與右節點比較
m.sm[m.sl[nr].id], m.sm[m.sl[i].id] = m.sm[m.sl[i].id], m.sm[m.sl[nr].id]
m.sl[nr], m.sl[i] = m.sl[i], m.sl[nr]
i = nr
continue
}
} else if nl <= heapLastIndex && m.sl[i].lastUpAt.Unix() < m.sl[nl].lastUpAt.Unix() {
//同末尾節點比較
m.sm[m.sl[nl].id], m.sm[m.sl[i].id] = m.sm[m.sl[i].id], m.sm[m.sl[nl].id]
m.sl[nl], m.sl[i] = m.sl[i], m.sl[nl]
}
break
}
}
//堆重構-上浮
func (m *Manager) heapUp() int {
var nodeIndex int
m.lock.Lock()
defer m.lock.Unlock()
//上沉
nodeIndex = m.openSession - 1
for {
pi := (nodeIndex - 1) / 2 //節點的父結點所在位置
if m.sl[nodeIndex].lastUpAt.Unix() < m.sl[pi].lastUpAt.Unix() {
m.sl[nodeIndex], m.sl[pi] = m.sl[pi], m.sl[nodeIndex]
nodeIndex = pi
continue
}
break
}
return nodeIndex
}
//獲取會話
func (m *Manager) getSS(sid string) (*SessionStore, bool) {
var session *SessionStore
var needHeapAdjust bool
m.lock.RLock()
if i, ok := m.sm[sid]; ok {
if i < m.openSession-1 && m.sl[i] != nil {
defer m.lock.RUnlock()
needHeapAdjust = true
return m.sl[i], needHeapAdjust
}
}
m.lock.RUnlock()
//分佈式引用,可以使用下面代碼, 從緩存中獲取會話數據(單機應用, 如果不追求穩定性且爲了更短的處理時間, 可以註釋掉)
cacheSessionData := make(map[string]interface{}, 8)
if sd, err := rc.Get(sid).Result(); err == nil {
err := json.Unmarshal([]byte(sd), &cacheSessionData)
if err != nil {
//todo log
return session, needHeapAdjust
}
m.lock.Lock()
defer m.lock.Unlock()
t := time.Now()
session = &SessionStore{
id: sid,
data: cacheSessionData,
lastUpAt: t,
expireAt: t.Add(time.Duration(m.expired) * time.Minute),
}
m.sl = append(m.sl, session)
m.sm[sid] = m.openSession
m.openSession = len(m.sl)
} else {
//todo log
}
return session, needHeapAdjust
}
//設置會話
func (m *Manager) setSS(sid string) *SessionStore {
m.lock.Lock()
defer m.lock.Unlock()
t := time.Now()
m.sm[sid] = m.openSession
m.sl = append(m.sl, &SessionStore{
id: sid,
data: make(map[string]interface{}, 2),
lastUpAt: t,
expireAt: t.Add(time.Duration(m.expired) * time.Minute),
})
m.openSession = len(m.sl)
return m.sl[m.sm[sid]]
}
(4) 中間組件包裝函數
package include
//啓動session
func SessionStart(sid *string) *SessionStore {
var mg *Manager
var ss *SessionStore
s, err := Cont.Get("session")
if err != nil {
//todo log
return nil
}
mg = GetSession(s)
if sid == nil || *sid == "" {
ss, err = mg.SessionInit()
} else {
ss, err = mg.SessionRead(*sid)
}
if err != nil {
//todo log
return nil
}
return ss
}
e. 公共函數
(1) 隨機數
package util
import (
"crypto/rand"
"encoding/hex"
"io"
)
func CreateUUID() string {
id := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, id); err != nil {
return ""
}
return hex.EncodeToString(id) //base64, 32(byte)*8/6 ~ 43(byte)
}
f. 測試
package test
import (
"fmt"
"time"
"../component"
"../include"
)
//import
type SessionTest struct {
component.DpTest
}
func (s *SessionTest) Test() {
//嘗試10000數量的初始化
t := time.Now()
var l = 20000
for i := 0; i < l; i++ {
_ = include.SessionStart(nil)
//se.Set(`time`, time.Now().Unix())
}
et := time.Now()
e := time.Since(t).Milliseconds()
fmt.Printf("start:`%v`, end:`%v`, cost:`%v`\n", t.Unix(), et.Unix(), e)
}
func TSession() {
t := &SessionTest{}
t.Test()
}
以上就是本片文章的所有內容,嘿嘿~~(這是我在我的畢設中用到的session會話組件"原創",後面會有更"驚喜"的文章)