点 Redis 8 已发布——并且它是开源的

了解更多

什么是会话状态?

会话状态是捕获用户与应用程序(如网站或游戏)当前交互状态的数据。

典型的 Web 应用程序为每个连接的用户保持一个会话,只要用户处于登录状态即可。会话状态是应用程序记住用户身份、登录凭据、个性化信息、最近操作、购物车等的方式。

在每次用户交互时读写会话数据必须在不损害用户体验的情况下进行。在幕后,会话状态是针对特定用户或应用程序的缓存数据,可以快速响应用户操作。因此,在用户会话处于活动状态时,不需要往返于中心数据库。

会话状态生命周期的最后一步发生在用户断开连接时。一些数据将持久保存在数据库中以备将来使用,但瞬态信息可以在会话结束后丢弃。


会话状态的挑战和最佳实践

理解会话状态最佳实践是评估和解决与会话相关的常见问题(如隔离、易失性和持久性)的关键。

在会话处于活动状态时,应用程序只从内存会话存储读写数据。这意味着写入操作更快,但也对数据丢失零容忍。由于会话存储数据不是来自另一个数据库的简单快照,因此它必须具有高持久性并始终可用。

会话状态类似于缓存,但其读/写生命周期不同:缓存允许数据丢失,并且可以随时从主数据库恢复。写入缓存还需要写入底层数据库。相比之下,会话状态只能在用户会话开始时从主数据源恢复,并且只有在会话结束时才会持久化回源。

会话状态可以是易失性的或永久性的,这意味着用户会话结束时数据可以被丢弃或持久化到磁盘存储。易失性会话数据的一个例子可能是公司内网中的页面导航历史——很少需要保留它。相比之下,电子商务应用中的持久购物车对业务至关重要,必须保存在永久存储上。

会话状态以键值对的形式存储,用户标识符作为键,会话数据作为值。这确保了用户会话不会相互访问信息。

将会话状态存储在快速内存缓存中允许一些在线分析场景,否则这些场景会降低事务数据库的性能。这些应用包括实时分析和仪表板、推荐引擎和欺诈检测。

我们如何使其快速

  • Redis Enterprise 基于共享无关的对称架构,使数据集大小能够线性且无缝地增长,无需更改应用程序代码。
  • Redis Enterprise 提供多种高可用性和地域分布模型,可在需要时为您的用户提供本地延迟。
  • 多种持久化选项(每写一次或每秒一次的 AOF 和快照)不影响性能,确保您在故障后无需重建数据库服务器。
  • 支持超大型数据集,通过智能分层内存访问(DRAM、持久内存或闪存)确保您可以根据用户的需求扩展数据集,而不会显著影响性能。

如何使用我们进行会话管理

考虑一个文本聊天应用程序,它使用 MySQL 作为关系数据库,Node.js 作为后端服务器技术,以及 Redis Enterprise 进行会话管理。前端由两个页面组成:一个首页,用户在此登录;一个聊天页面,用户在此输入和发送消息。

为了简单起见,这里只展示服务器代码。它将解释如何在 Node.js 中实现会话状态生命周期。我们还省略了 HTML 视图页面和应用程序的其余部分。

首先,应用程序加载依赖项,包括会话、Redis 对象和 MySQL 客户端

var express = require("express");
var session = require('express-session');
var mysql = required("mysql");
var redis = require("redis");
var redisStore = require('connect-redis')(session);
Var redisClient = redis.createClient();
//more dependencies are loaded here....

上面的声明创建了用于管理 Web 路由、会话、数据库、缓存和会话 Node.js 库的对象。然后将 Redis 设置为会话存储

app.use(session({
    secret: 'mysecret',
    // create new redis store.
    store: new redisStore({
        host: 'localhost',
        port: 6379,
        client: redisClient
    }),
    saveUninitialized: false,
    resave: false
}));

接下来,配置用于首页和聊天页面的 Node.js express 路由,以及支持来自客户端的 AJAX 请求,包括登录、注销和发送评论。

当用户请求首页时,服务器会根据用户是否登录,将他们重定向到 chat.html 页面或显示 login page.html。以下代码片段显示了 /get Web 路由的处理程序代码

app.get('/', function (req, res) {
    // create new session object.
    if (req.session.key) {
        // user is already logged in
        res.redirect('/chat');
    } else {
        // no session found, go to login page
        res.render("login.html");
    }
});

当用户提交登录表单数据(包含电子邮件和密码)时,客户端 JavaScript AJAX 将表单数据发送到服务器。在此示例中,它调用 executeLoginDbCommand 函数(此处未显示),该函数对 MySQL 数据库执行 SQL 查询,并返回一个包含用户先前保存的会话数据的对象。

如果登录成功,来自 MySQL 的用户数据将保存到由 Redis 会话存储支持的 Web 会话中,客户端 JavaScript 代码会将用户重定向到聊天页面

app.post('/login', function (req, res) {
    // SQL Query will compare login and password
    // from HTTP request body to user table data
    executeLoginDbCommand(req.body.Email, req.body.Password, function (dbResult) {
        //
        if (!dbResult) {
            res.json({
                "success": false,
                "message": "Login failed ! Please register"
            });
        } else {
            req.session.key = dbResult;
            res.json({
                "success": true,
                "message": "Login success."
            });
        }
    });
});

应用程序聊天页面允许用户阅读和发布消息给已登录应用程序的其他用户。由于用户只能看到他们与他人自己的消息交互,服务器为聊天页面请求返回的数据因用户而异。最重要的是,该页面的访问仅限于已登录用户。检查会话密钥会显示用户是否已登录

app.get('/chat', function (req, res) {
    if (req.session.key) {
        //user is already logged in,
        //so let's render the chat page with the user email
        res.render("chat.html", {
            email: req.session.key["UserEmail"],
            name: req.session.key["UserName"]
        });
    } else {
        // no session found, go to login page
        res.redirect("/");
    }
});

当用户从聊天页面提交新评论时,客户端 JavaScript AJAX 将表单数据发送到服务器。如果用户已登录,评论将插入到 MySQL UserComments 表中。我们通过调用 executeSendCommmentDbCommand 函数(此处未显示)来完成此操作。

app.post("/sendComment", function (req, res) {
    // This SQL command will insert a new comment in
    // users table
    if (req.session.key) {
        executeSendCommmentDbCommand(req.body.Email, req.body.Recipient, req.body.Comment, function (dbResult) {
            if (!dbResult) {
                res.json({
                    "success": true,
                    "message": "Comment has been sent successfully."
                });
            } else {
                res.json({
                    "success": false,
                    "message": "SendComment failed!"
                });
            }
        });
    } else {
        res.json({
            "success": false,
            "message": "Please login first."
        });
    }
});

当用户注销时,会话对象将被销毁,并将用户重定向到登录页面。但首先,executePersistSessionDbCommand(此处未显示)将内存中的用户会话保存到 MySQL 数据库。

app.get('/logout', function (req, res) {
    // user is logged in, so let's destroy the session
    // and redirect to login page.
    if (req.session.key) {
        executePersistSessionDbCommand(req.session, function (dbResult) {
            if (!dbResult) {
                req.session.destroy(function () {
                        res.redirect('/');
                    } else {
                        res.json({
                            "success": false,
                            "message": "Session persistence failed!"
                        });
                    }
                });
        });
    } else {
        res.redirect('/');
    }
});

这些代码片段仅触及使用 Redis 作为会话存储的真实应用程序的皮毛。但它们说明了 Redis 如何结合 MySQL 等永久数据库存储来管理内存中的会话状态生命周期。


相关资源

帖子

2017 年 11 月 15 日

缓存对比会话存储

在全球范围内扩展,同时保持低延迟并更有效地缓存以降低成本:点击此处与 Redis Enterprise 团队交谈。有句俗话说:“在硅谷…

帖子

2019 年 4 月 30 日

使用 Redis 进行查询缓存

了解您的缓存是否达到企业级,并学习如何:在全球范围内扩展,同时保持低延迟并更有效地缓存以降低成本 当我刚开始时…

帖子

2019 年 5 月 29 日

缓存、Promises 和锁

Instagram 最近在他们的工程博客上发表了一篇关于将缓存值 promises 化的概念的帖子。其思想是,当缓存未命中时,需要一段时间…