nodejs 實踐:express 最佳實踐 express-session 解析

nodejs 實踐:express 最佳實踐 express-session 解析

nodejs 發(fā)展很快,從 npm 上面的包托管數(shù)量就可以看出來钧椰。不過從另一方面來看考婴,也是反映了 nodejs 的基礎(chǔ)不穩(wěn)固,需要開發(fā)者創(chuàng)造大量的輪子來解決現(xiàn)實的問題闯估。

知其然睹欲,并知其所以然這是程序員的天性供炼。所以把常用的模塊拿出來看看一屋,看看高手怎么寫的,學(xué)習(xí)其想法袋哼,讓自己的技術(shù)能更近一步冀墨。

引言

最近 ‘雙十一‘ 快到了,領(lǐng)導(dǎo)安排我們給網(wǎng)站做性能優(yōu)化涛贯。其中最要的方向是保證網(wǎng)站的穩(wěn)定性诽嘉。我主要是負(fù)責(zé)用戶登錄入口這一塊的工作。

優(yōu)化的目標(biāo)是:在高峰下弟翘,如果系統(tǒng)服務(wù)器端 session 的存儲(memcached)出現(xiàn)了問題虫腋,用戶還能正常登錄和使用我們的網(wǎng)站。

并已經(jīng)給出了技術(shù)思路:對 session, 進行服務(wù)器端 session(memcached) 和 瀏覽器端 session(cookie) 雙備份衅胀,一但連接發(fā)現(xiàn)服務(wù)器端 session 出現(xiàn)了問題,就啟用瀏覽器端 session, 實現(xiàn)自動降級處理酥筝。

借此機會滚躯,正好比較深入的了解了一下 session 等相關(guān)知識實現(xiàn)。

session

session 是什么

注意這里說的都是網(wǎng)站相關(guān)技術(shù)環(huán)境下嘿歌。

session 是一種標(biāo)識對話的技術(shù)說法掸掏。通過 session ,我們能快速識別用戶的信息宙帝,針對用戶提供不一樣的信息丧凤。

session 的技術(shù)實現(xiàn)上:會對一次對話產(chǎn)生一個唯一的標(biāo)識進行標(biāo)識。

session 生命周期

session 標(biāo)識產(chǎn)生的時機和清除時機:

  1. 用戶已經(jīng)登錄:這個唯一標(biāo)識會在用戶登錄時產(chǎn)生步脓,用戶點擊退出時或者關(guān)閉瀏覽器時清除愿待。
  2. 用戶未登錄: 這個唯一標(biāo)識會用用戶進入網(wǎng)站時產(chǎn)生,用戶關(guān)閉所有網(wǎng)站相關(guān)頁面時清除靴患。

session 生命周期: 在生成和清除之間仍侥,在網(wǎng)站內(nèi)的頁面任意跳轉(zhuǎn),session 標(biāo)識不會發(fā)生變化鸳君。

從 session 開始到清除农渊,我們叫一次會話,也就是生成 session或颊。

session 特點

每次對話砸紊, session 的 id 是不一樣的。

session id 需要每次請求都由客戶端帶過來囱挑,用來標(biāo)識本次會話醉顽。這樣就要求客戶端有能用保存的 sesssionId。

session 技術(shù)方案

當(dāng)前業(yè)界通用的方案是:cookie 平挑。當(dāng)然還有無 cookie 的方案徽鼎,對每個鏈接都加上 sessionId 參數(shù)。

session 使用流程

  1. 用戶登錄后,將 sessionId 存到 cookie 中否淤。
  2. 用戶在請求的網(wǎng)站別的服務(wù)時悄但,由瀏覽器請求帶上 cookie,發(fā)送到服務(wù)器石抡。
  3. 服務(wù)器拿到 sessionId 后檐嚣,通過該 Id 找到保存到在服務(wù)器的用戶信息。
  4. 然后再跟據(jù)用戶信息啰扛,進行相應(yīng)的處理嚎京。

從流程有幾個點要關(guān)注:

  1. 什么時候根據(jù) sessionId 去拿 session
  2. 確保 session 可用性

下面就結(jié)合 express-session 來講講具體 session 的實現(xiàn)。

express-session 的分析

主要關(guān)注問題:

  1. 怎樣產(chǎn)生 session
  2. 怎樣去拿到 session
  3. 怎樣去保存 session
  4. 怎樣去清除 session

express-session 位置

image

這一一張更詳細(xì)的 session 流程圖隐解,同時也說明了 express-session 的基本的工作模塊鞍帝。

express-session 有四個部分:

  1. request, response 與 session 的交互的部分
  2. session 數(shù)據(jù)結(jié)構(gòu)
  3. session 中數(shù)據(jù)存儲的接口 store
  4. store 默認(rèn)實現(xiàn) memory(cookie 實現(xiàn)已被廢)
image

這張是 express-session 的流程圖,從圖中可以看到煞茫, express-session 的工作流程帕涌。

具體的情況只能去看代碼了。

因為我們的網(wǎng)站是 session store 是基于 memcached 的续徽。所以我把 connect-memcached 和 memcached 都看了一遍钦扭。

connect-memcached 是基于 memcached 實現(xiàn) session store 接口客情。

memcached 是基于連接池的應(yīng)用瑞凑,下面是我畫的結(jié)構(gòu)圖:

image

問題解決方案

上面把 session 和 express-session + connect-memcached 都仔細(xì)看過了惰匙。

回到前面引言中的方案项鬼,我們需要解決以下的問題:

  1. 基于 memcached 的 session 怎么把數(shù)據(jù)同步到基于 cookie 的 session 中绘盟。
  2. 怎么把 cookie 的 session 數(shù)據(jù)恢復(fù)到 session 中锡垄。
  3. 怎樣判斷 memcached 已經(jīng)失去連接货岭。

解決1,2兩個問題,可以讓用戶在一次對話中敦第,在 mecached 和 cookie 中切換垮卓,數(shù)據(jù)還一直存在诬滩,不會丟掉。

第3個問題空镜,就是在 memcached 斷開時,程序能知道 memcached 已斷洼怔,然后數(shù)據(jù)從 cookie 中拿镣隶。

庫選擇

已經(jīng)有 express-session 的方案轻猖,要有一個在客戶端找一個基于 cookie 的 session 方案:cookie-sessionclient-session 這兩個都可以。我選了第二個,主要是加密第二個更好檐束。

方案

我前前后后,考慮了多個方案,方案如下:

image

首先方案一:主要思路是通過一個基礎(chǔ)的監(jiān)控程序去按時間定時(比如5分鐘)去ping memcached 服務(wù)器,去判斷是否可用办陷,然后把結(jié)果寫入到 zookeeper 中险毁,通過 zookeeper 的變量去控制數(shù)據(jù)從 memcached 的session 中讀取,還是從 cookie session 中讀數(shù)據(jù)。

方案二:在兩個 session 之上嵌戈,再建一個 session覆积,就是對從哪里讀數(shù)據(jù)通過這個 session 來實現(xiàn),也就是代理模式熟呛。

方案三:在 store 層上做一層 common-store , 然后由他負(fù)責(zé)從哪個store 中讀取數(shù)據(jù)宽档,就是 store 的代理。

方案四:不做中間層庵朝,直接使用進行處理吗冤,只用 express-session 進行處理數(shù)據(jù)。

這四個方案都在選擇九府,區(qū)別只是實現(xiàn)上的難度:

  1. memcached 的不可連接是否可以在框架層感知道
  2. 業(yè)務(wù)代碼盡量不用調(diào)整
  3. session 同步方案是否有效

其中第一個問題最重要椎瘟,如果框架層不可感知,那就要有一個外部程序進行處理侄旬,或者寫一個中間件去主動連接一下肺蔚,看看是否可連接。

再一次閱讀 express-session 重點查看 session 中 store 連接這塊儡羔。發(fā)現(xiàn)如果 memcached 不可連接宣羊,req.session 是 undefined 的。

這樣汰蜘,就可以通過判斷 req.session 是否是直來判斷是否可連接仇冯。

第二個問題:因為業(yè)務(wù)代碼中使用都是 req.session 的形式, 從 cookie 中恢復(fù)數(shù)據(jù)的時候族操,就要成初始化成 express-session 的接口苛坚。

這個問題也通過閱讀代碼解決:

  req.sessionID = uuid.v4();
  req.session = new expresssession.Session(req, data);
  req.session.cookie = new memcachedSession.Cookie({
    domain: config.cookieDomain,
    httpOnly: false
  });

通過以上的代碼就可以從數(shù)據(jù)中恢復(fù) session。

第三個問題: 要保證 session 一致坪创,就讓數(shù)據(jù)指向同一個對象

req.session2.sessionBack = req.session;

因此方案1炕婶,方案2姐赡, 方案3 都扔掉莱预,直接方案4。

完整的代碼如下:

const config = global.config;
const session = require('express-session');

/**
 * 該中間件主要把 express-session 和 client-session 集中起來處理项滑,如果 memcached 出錯了依沮,使用 cookie session
 * @param backSession cookeSession 的鍵名
 * @returns {function(*=, *=, *)}
 */
module.exports = (backSession) => {
    return (req, res, next) => {
        let notUseMemcached = _.get(req.app.locals.pc, 'session.removeMemcached', false);

        if (req.session && !notUseMemcached) {  // memcached 可用
            req.memcachedSessionError = false;
        } else {  // memcached 不可用
            // 重建 session
            res.emit('sessionError');
            req.memcachedSessionError = true;
            req.session = new session.Session(req);
            req.session.cookie = new session.Cookie({
                domain: config.cookieDomain,
                httpOnly: false
            });

            req.session = Object.assign(req.session, req[backSession].sessionBack);
        }

        Object.defineProperty(req.session, 'reset', {
            configurable: true,
            enumerable: false,
            value: function() {
                req.session.destory();
                req[backSession].reset();
            },
            writable: false
        });

        // 備份數(shù)據(jù)
        req[backSession].sessionBack = req.session;

        next();
    };
};

這里就不貼 express-session 和 client-session 初始化代碼,需要注意的是:這個中間件要放到初始化的后面枪狂。

 app.use(memcachedSession({
    // ... options
    }));

    app.use(cookieSession({
    // ... options
    }));

    app.use(yohoSession({
        backSession: 'session2'
    }));

總結(jié)

網(wǎng)站穩(wěn)定性一直是一個重要的話題危喉。這次通過 session 的改造,讓我復(fù)習(xí)了很多的知識州疾,學(xué)無止盡辜限。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市严蓖,隨后出現(xiàn)的幾起案子薄嫡,更是在濱河造成了極大的恐慌氧急,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毫深,死亡現(xiàn)場離奇詭異吩坝,居然都是意外死亡,警方通過查閱死者的電腦和手機哑蔫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門钉寝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人闸迷,你說我怎么就攤上這事嵌纲。” “怎么了腥沽?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵疹瘦,是天一觀的道長。 經(jīng)常有香客問我巡球,道長言沐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任酣栈,我火速辦了婚禮险胰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘矿筝。我一直安慰自己起便,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布窖维。 她就那樣靜靜地躺著榆综,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铸史。 梳的紋絲不亂的頭發(fā)上鼻疮,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天,我揣著相機與錄音琳轿,去河邊找鬼判沟。 笑死,一個胖子當(dāng)著我的面吹牛崭篡,可吹牛的內(nèi)容都是我干的挪哄。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼琉闪,長吁一口氣:“原來是場噩夢啊……” “哼迹炼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起颠毙,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤斯入,失蹤者是張志新(化名)和其女友劉穎拿霉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咱扣,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡绽淘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了闹伪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沪铭。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖偏瓤,靈堂內(nèi)的尸體忽然破棺而出杀怠,到底是詐尸還是另有隱情,我是刑警寧澤厅克,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布赔退,位于F島的核電站,受9級特大地震影響证舟,放射性物質(zhì)發(fā)生泄漏硕旗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一女责、第九天 我趴在偏房一處隱蔽的房頂上張望漆枚。 院中可真熱鬧,春花似錦抵知、人聲如沸墙基。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽残制。三九已至,卻和暖如春掖疮,著一層夾襖步出監(jiān)牢的瞬間初茶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工氮墨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纺蛆,地道東北人吐葵。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓规揪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親温峭。 傳聞我的和親對象是個殘疾皇子猛铅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容