簡単なGo session実現
もっと読む
Goのnet/httpパッケージはhttpプログラミングを実現しましたが、会話すなわちsessionは自分で実行する必要があります。本論文ではメモリ記憶に基づいて簡単なsession管理を実現し、いくつかの言語の重要な基礎概念(例えば、伝値アドレス)を説明し、Go 1.5.2に基づく。
分析
簡単に言えばsession管理の難点は、期限が切れたsessionが自動的に廃棄され、メモリの漏洩を避けるために回収されることであるが、この廃棄過程は他の非期限sessionの正常な使用に影響を与えないようにすることである。
通常は全てのセッションが一つのmapに格納され、session idをキーとしているので、このようにして素早くidからセッションを取得することができる。ただし、期限切れのセッションは即時にクリアできるので、map以外にlinked listを追加することを考慮して、すべてのsessionをアクティブ度にリンクします。sessionを取得するたびにチェーンヘッドにアップグレードします。そうすると、チェーンの最後には活発でないセッションが徐々に蓄積され、期限切れの清理はチェーンの最後から迅速に実行できます。
このようにmap valueをlistの要素に変えます。Go container/list双方向リストを使用して、リスト全体を巡回することなく、すばやく移動することができます。
デザイン
ディレクトリ構造は以下の通りです。
最初にセッションタイプを定義します。
セッションマネージャ:
実現する
一つのセッションに必要なすべてのデータを一つのstructに定義します。
ロックフィールドのタイプは、sync.RWMutexではなく、*sync.RWMuttexであり、違いは何ですか?godoc faq菗pass_を参照してください。by_value:Go関数の呼び出し時に、すべてのパラメータ、結果の伝達はアドレスではなく、転送値です。このようにses.lock.XX方法を呼び出すと、*sync.RWMutexのコピー-ポインタコピーは元のlockオブジェクトをそのまま使用し、sync.RWMutexコピーであれば元のlockオブジェクトではないと伝えられる。RWMustex godocには「An RWMustex must not be copied after first use.」が要求されます。
同様にfaq_Ci_referencesを参照してください。Go map_pace_chanelは、実際にポインタです。つまり、mapオブジェクト(mapポインタではない)の内部に実際に格納されているのは、実際のデータを指すポインタです。だから、map_slice_chanel伝値も可能です。ただし、Go arrayは実際の値のオブジェクトであり、大きなarrayを転送する場合は、転送のポインタに変更したほうがいいです。
また、小さいstructはコピーが「安い」ので、値を伝えられます。最後に統一したほうがいいです。同じオブジェクトのすべての方法はいずれも値を伝えたり、住所を伝えたりします。
session訪問時間ses.timeについては、managerを通じてsessionを取得したときにのみ更新するように簡略化し、その後のsession.get/set/.は更新しない。
ses.mgrフィールドは、session.Invalidate方法を実現するために使用されます。
Goの中の1つのタイプが、あるインターフェースのすべての方法を実現すると、このタイプは、Javaで明確な「implements」のようなものを必要としないインターフェースを実現する。ses structはISessionインターフェースを実現しました。
そしてsession managerの実現struct:
ISessitionManagerのGet方法の実現:
List.Element.Valueのタイプはinterface{}です。“e.Value.(ses)”はtype astertion“x.(T)”である:xはインターフェースタイプである必要があり、Tはインターフェースタイプではない場合、xのダイナミックタイプ=Tであるべきである。
new(xx)はポインタを返し、make(xx)は値を返します。
整理はチェーンの尻尾から始まります。時間がかかりすぎるのを避けるために、上限を設定します。チェーンが空ではない時は、次回のクリーンアップが予定されていますが、チェーンが空になっていたり、残りの期限が切れていない時は、頻繁に次のクリーンアップを開始しないでください。
ユニットテスト
Goは便利なユニットテスト編纂を提供し、session.goと同じディレクトリでsessionを作成します。test.goは、それぞれの「Fnc TestXX(*testing.T)」方法をテスト方法として、go testによってテストを実行します。
Goの中に同じカバンのタイプがお互いに見えます。prvateは存在しません。testクラスとsession.goは一つのカバンの中にあるので、私達はsession managerを作成するのに便利ですが、自動整理のツールを起動しません。
gcOnceメソッドを呼び出してクリーンアップをテストします。
ブラウザで実際に会話を検証する必要があります。main.goはnet/httpを使ってhttp serverを起動します。ブラウザの要求を受けた時(Java servletのようです。)固定名称のcookieを通じて発生したsessionのidを格納します。このクッキーはブラウザに送信されます。ブラウザは後で要求するたびにserverに送信します。
プログラムを実行して、展示ページにアクセスして、毎回更新するsession idと同じであることを確認します。また、set値を設定し、現在のsessionを廃棄することもできます。 memses.zip(3.8 KB) ダウンロード回数:4
Goのnet/httpパッケージはhttpプログラミングを実現しましたが、会話すなわちsessionは自分で実行する必要があります。本論文ではメモリ記憶に基づいて簡単なsession管理を実現し、いくつかの言語の重要な基礎概念(例えば、伝値アドレス)を説明し、Go 1.5.2に基づく。
分析
簡単に言えばsession管理の難点は、期限が切れたsessionが自動的に廃棄され、メモリの漏洩を避けるために回収されることであるが、この廃棄過程は他の非期限sessionの正常な使用に影響を与えないようにすることである。
通常は全てのセッションが一つのmapに格納され、session idをキーとしているので、このようにして素早くidからセッションを取得することができる。ただし、期限切れのセッションは即時にクリアできるので、map以外にlinked listを追加することを考慮して、すべてのsessionをアクティブ度にリンクします。sessionを取得するたびにチェーンヘッドにアップグレードします。そうすると、チェーンの最後には活発でないセッションが徐々に蓄積され、期限切れの清理はチェーンの最後から迅速に実行できます。
このようにmap valueをlistの要素に変えます。Go container/list双方向リストを使用して、リスト全体を巡回することなく、すばやく移動することができます。
デザイン
ディレクトリ構造は以下の通りです。
$GOPATH/src/sample/memses
main.go
$GOPATH/src/sample/memses/session
session.go
main.goはメインプログラムで、sessionディレクトリはセッション実現です。最初にセッションタイプを定義します。
type ISession interface {
Id() string
Get(key string) interface{}
Set(key string, value interface{})
Remove(key string)
Invalidate()
}
GoはJavaと同様に、インターフェース定義タイプも使用できる。sessionにはget/set/削除があり、自発的に期限が切れる。session idは一般的に固定されています。修正されません。インターフェース{}はJavaのObjectに似ていますが、int/float 32などでもいいです。セッションマネージャ:
type ISessionManager interface {
Get(id string) ISession // get or create a session (with new id)
}
sessionマネージャはsessionを取得し、session idに入る。もしあるなら、それを返します。ないか、または期限が切れたら、新しいのを作って戻ってきます。実現する
一つのセッションに必要なすべてのデータを一つのstructに定義します。
type ses struct {
mgr *sesmgr
id string
lock *sync.RWMutex // for smap,time
smap map[string]interface{}
time time.Time //access time
}
sessionのデータはsmapに格納されます。sessionは、同時に使用されることがある(例えば、1ページで2つのバックグラウンド要求を開始する)ので、アクセス同期は、sync RWロックによって行われる。Go言語に注意すると、通常は、チャンネル/goroutineによって、一つのデータが一つのgoroutineによってしかアクセスできないようにして、複数のgoroutineを回避して同時に同じデータにアクセスすることを提案していますが、一つのmapに対する読み書きと制御を細分化する場合、簡単なRWロックがより適切に見えるようになります。ロックフィールドのタイプは、sync.RWMutexではなく、*sync.RWMuttexであり、違いは何ですか?godoc faq菗pass_を参照してください。by_value:Go関数の呼び出し時に、すべてのパラメータ、結果の伝達はアドレスではなく、転送値です。このようにses.lock.XX方法を呼び出すと、*sync.RWMutexのコピー-ポインタコピーは元のlockオブジェクトをそのまま使用し、sync.RWMutexコピーであれば元のlockオブジェクトではないと伝えられる。RWMustex godocには「An RWMustex must not be copied after first use.」が要求されます。
同様にfaq_Ci_referencesを参照してください。Go map_pace_chanelは、実際にポインタです。つまり、mapオブジェクト(mapポインタではない)の内部に実際に格納されているのは、実際のデータを指すポインタです。だから、map_slice_chanel伝値も可能です。ただし、Go arrayは実際の値のオブジェクトであり、大きなarrayを転送する場合は、転送のポインタに変更したほうがいいです。
また、小さいstructはコピーが「安い」ので、値を伝えられます。最後に統一したほうがいいです。同じオブジェクトのすべての方法はいずれも値を伝えたり、住所を伝えたりします。
session訪問時間ses.timeについては、managerを通じてsessionを取得したときにのみ更新するように簡略化し、その後のsession.get/set/.は更新しない。
ses.mgrフィールドは、session.Invalidate方法を実現するために使用されます。
Goの中の1つのタイプが、あるインターフェースのすべての方法を実現すると、このタイプは、Javaで明確な「implements」のようなものを必要としないインターフェースを実現する。ses structはISessionインターフェースを実現しました。
func (s ses) Id() string {
return s.id
}
func (s ses) Get(key string) interface{} {
s.lock.Lock()
defer s.lock.Unlock()
return s.smap[key]
}
func (s ses) Set(key string, value interface{}) {
s.lock.Lock()
defer s.lock.Unlock()
s.smap[key] = value
}
func (s ses) Remove(key string) {
。。。
}
func (s ses) Invalidate() {
s.mgr.invalidate(s.id)
}
上記のようにGetメソッドでは合併を防ぐためにロックします。defer Ulockは、Javaのtry/finallyと同様に、方法が戻る前に実行されます。そしてsession managerの実現struct:
type sesmgr struct {
lock *sync.RWMutex
list *list.List // a list of ses Element, active (top) -> inactive (bottom)
smap map[string]*list.Element // id => ses Element
timeout time.Duration
}
map以外に双方向チェーンを使う:map valueはチェーンの要素で、チェーン要素のValueはses structです。idがmapからlist elementを見つけたと知っています。そのvalueはsesです。同様に、list elementは、急速にチェーン内を移動し、削除することができます。同様にRWロックで同時制御します。ISessitionManagerのGet方法の実現:
func (sm sesmgr) Get(id string) ISession {
sm.lock.Lock()
defer sm.lock.Unlock()
if e, ok := sm.smap[id]; ok {
s := e.Value.(ses)
if s.checkTimeout(sm.timeout) {
sm.list.MoveToFront(e) // front means most active
return s
} else {
sm.delete(e)
}
}
// not exists or timed out
s := ses{
mgr: &sm,
id: genSesId(),
lock: new(sync.RWMutex),
smap: make(map[string]interface{}, 24),
time: time.Now(),
}
e := sm.list.PushFront(s)
sm.smap[s.id] = e
return s
}
smapから見つかって、まだ期限が切れていないなら、チェーンの頭に移動して返します。そうでないと、古いものを削除して、新しいものを作って返します。List.Element.Valueのタイプはinterface{}です。“e.Value.(ses)”はtype astertion“x.(T)”である:xはインターフェースタイプである必要があり、Tはインターフェースタイプではない場合、xのダイナミックタイプ=Tであるべきである。
new(xx)はポインタを返し、make(xx)は値を返します。
整理はチェーンの尻尾から始まります。時間がかかりすぎるのを避けるために、上限を設定します。チェーンが空ではない時は、次回のクリーンアップが予定されていますが、チェーンが空になっていたり、残りの期限が切れていない時は、頻繁に次のクリーンアップを開始しないでください。
func (sm sesmgr) gcOnce() time.Duration {
sm.lock.Lock()
defer sm.lock.Unlock()
for i := 0; i < 1000; i++ { // max 1000 del
e := sm.list.Back()
if e == nil {
break
}
s := e.Value.(ses)
if d := s.getLeftTimeout(sm.timeout); d >= 0 {
sm.delete(e)
} else {
if -d < 2*time.Minute { // still valid, wait a bit longer
return 2 * time.Minute
} else {
return -d
}
}
}
if sm.list.Len() > 0 { // assume more to gc, catch up
return 1 * time.Second
} else {
return 2 * time.Minute
}
}
ここの戻り値=次の整理までの待ち時間は、session managerを作成する時にgoroutineを起動して整理を続けます。
go func() {
for {
time.Sleep(sm.gcOnce())
}
}()
go funcxxはgoroutineを起動します。goroutineはもっと軽いスレッドであり、Javaスレッドとして複数のgoroutineをスケジューリングできると考えられています。faqを参照してください。goroutineは最初は数kのメモリを占めています。プログラムは100以上の千goroutineに使用できます。ユニットテスト
Goは便利なユニットテスト編纂を提供し、session.goと同じディレクトリでsessionを作成します。test.goは、それぞれの「Fnc TestXX(*testing.T)」方法をテスト方法として、go testによってテストを実行します。
Goの中に同じカバンのタイプがお互いに見えます。prvateは存在しません。testクラスとsession.goは一つのカバンの中にあるので、私達はsession managerを作成するのに便利ですが、自動整理のツールを起動しません。
func createMgr(d time.Duration) sesmgr {
sm := sesmgr{
lock: new(sync.RWMutex),
list: new(list.List),
smap: make(map[string]*list.Element, 100),
timeout: d,
}
return sm
}
簡単なテスト:
func Test1(t *testing.T) {
sm := createMgr(time.Minute)
//1. one
s := sm.Get("")
if sm.list.Len() != 1 {
t.Errorf("one: len != 1")
}
//。。。
}
「t.Errorf」方法は一回の失敗を報告します。gcOnceメソッドを呼び出してクリーンアップをテストします。
sm = createMgr(10 * time.Second) //10s timeout
id1 = sm.Get("").Id()
sm.gcOnce()
if sm.list.Len() != 1 {
t.Errorf("gc: should gc none")
}
ブラウザテストブラウザで実際に会話を検証する必要があります。main.goはnet/httpを使ってhttp serverを起動します。ブラウザの要求を受けた時(Java servletのようです。)固定名称のcookieを通じて発生したsessionのidを格納します。このクッキーはブラウザに送信されます。ブラウザは後で要求するたびにserverに送信します。
const TOKEN = "GSESSIONID" // session cookie name
func getSession(w http.ResponseWriter, req *http.Request) session.ISession {
var id = ""
if c, err := req.Cookie(TOKEN); err == nil {
id = c.Value
}
ses := sesmgr.Get(id)
if ses.Id() != id { //new session
http.SetCookie(w, &http.Cookie{
Name: TOKEN,
Value: ses.Id(),
})
}
return ses
}
上記のget Session法はテスト用にのみ使用されており、生産にはまだいくつかの潜在的な問題(例えばドメイン名変換ip)があるかもしれない。プログラムを実行して、展示ページにアクセスして、毎回更新するsession idと同じであることを確認します。また、set値を設定し、現在のsessionを廃棄することもできます。