nodejs 實踐:express 最佳實踐(四) express-session 解析


nodejs 實踐:express 最佳實踐(四) express-session 解析

nodejs 發展很快,從 npm 上面的包托管數量就可以看出來。不過從另一方面來看,也是反映了 nodejs 的基礎不穩固,需要開發者創造大量的輪子來解決現實的問題。

知其然,並知其所以然這是程序員的天性。所以把常用的模塊拿出來看看,看看高手怎么寫的,學習其想法,讓自己的技術能更近一步。

引言

最近 ‘雙十一‘ 快到了,領導安排我們給網站做性能優化。其中最要的方向是保證網站的穩定性。我主要是負責用戶登錄入口這一塊的工作。

優化的目標是:在高峰下,如果系統服務器端 session 的存儲(memcached)出現了問題,用戶還能正常登錄和使用我們的網站。

並已經給出了技術思路:對 session, 進行服務器端 session(memcached) 和 瀏覽器端 session(cookie) 雙備份,一但連接發現服務器端 session 出現了問題,就啟用瀏覽器端 session, 實現自動降級處理。

借此機會,正好比較深入的了解了一下 session 等相關知識實現。

session

session 是什么

注意這里說的都是網站相關技術環境下。

session 是一種標識對話的技術說法。通過 session ,我們能快速識別用戶的信息,針對用戶提供不一樣的信息。

session 的技術實現上:會對一次對話產生一個唯一的標識進行標識。

session 生命周期

session 標識產生的時機和清除時機:

  1. 用戶已經登錄:這個唯一標識會在用戶登錄時產生,用戶點擊退出時或者關閉瀏覽器時清除。
  2. 用戶未登錄: 這個唯一標識會用用戶進入網站時產生,用戶關閉所有網站相關頁面時清除。

session 生命周期: 在生成和清除之間,在網站內的頁面任意跳轉,session 標識不會發生變化。

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

session 特點

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

session id 需要每次請求都由客戶端帶過來,用來標識本次會話。這樣就要求客戶端有能用保存的 sesssionId。

session 技術方案

當前業界通用的方案是:cookie 。當然還有無 cookie 的方案,對每個鏈接都加上 sessionId 參數。

session 使用流程

  1. 用戶登錄后,將 sessionId 存到 cookie 中。
  2. 用戶在請求的網站別的服務時,由瀏覽器請求帶上 cookie,發送到服務器。
  3. 服務器拿到 sessionId 后,通過該 Id 找到保存到在服務器的用戶信息。
  4. 然后再跟據用戶信息,進行相應的處理。

從流程有幾個點要關注:

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

下面就結合 express-session 來講講具體 session 的實現。

express-session 的分析

主要關注問題:

  1. 怎樣產生 session
  2. 怎樣去拿到 session
  3. 怎樣去保存 session
  4. 怎樣去清除 session

express-session 位置

這一一張更詳細的 session 流程圖,同時也說明了 express-session 的基本的工作模塊。

express-session 有四個部分:

  1. request, response 與 session 的交互的部分
  2. session 數據結構
  3. session 中數據存儲的接口 store
  4. store 默認實現 memory(cookie 實現已被廢)

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

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

因為我們的網站是 session store 是基於 memcached 的。所以我把 connect-memcached 和 memcached 都看了一遍。

connect-memcached 是基於 memcached 實現 session store 接口。

memcached 是基於連接池的應用,下面是我畫的結構圖:

問題解決方案

上面把 session 和 express-session + connect-memcached 都仔細看過了。

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

  1. 基於 memcached 的 session 怎么把數據同步到基於 cookie 的 session 中。
  2. 怎么把 cookie 的 session 數據恢復到 session 中。
  3. 怎樣判斷 memcached 已經失去連接。

解決1,2兩個問題,可以讓用戶在一次對話中,在 mecached 和 cookie 中切換,數據還一直存在,不會丟掉。

第3個問題,就是在 memcached 斷開時,程序能知道 memcached 已斷,然后數據從 cookie 中拿。

庫選擇

已經有 express-session 的方案,要有一個在客戶端找一個基於 cookie 的 session 方案:cookie-sessionclient-session 這兩個都可以。我選了第二個,主要是加密第二個更好。

方案

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

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

方案二:在兩個 session 之上,再建一個 session,就是對從哪里讀數據通過這個 session 來實現,也就是代理模式。

方案三:在 store 層上做一層 common-store , 然后由他負責從哪個store 中讀取數據,就是 store 的代理。

方案四:不做中間層,直接使用進行處理,只用 express-session 進行處理數據。

這四個方案都在選擇,區別只是實現上的難度:

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

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

再一次閱讀 express-session 重點查看 session 中 store 連接這塊。發現如果 memcached 不可連接,req.session 是 undefined 的。

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

第二個問題:因為業務代碼中使用都是 req.session 的形式, 從 cookie 中恢復數據的時候,就要成初始化成 express-session 的接口。

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

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

通過以上的代碼就可以從數據中恢復 session。

第三個問題: 要保證 session 一致,就讓數據指向同一個對象

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
        });

        // 備份數據
        req[backSession].sessionBack = req.session;

        next();
    };
};

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

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

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

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

總結

網站穩定性一直是一個重要的話題。這次通過 session 的改造,讓我復習了很多的知識,學無止盡。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM