session管理組件實現[go實現]

回想起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會話組件"原創",後面會有更"驚喜"的文章)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章