SSF0SSF0
首页
前端
  • Node
  • Go
  • Python
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
  • 开源项目
Timeline
Github
标签
分类
首页
前端
  • Node
  • Go
  • Python
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
  • 开源项目
Timeline
Github
标签
分类
  • Node.js

    • 在 Docker 容器中运行 Node.js Koa 应用的网络配置与常见问题
    • 在 Node.js 后端实现邮箱验证码功能
    • Redis 与 JWT 结合的 Token 验证方案
    • Node.js 后端三层架构中的错误处理最佳实践
    • 在 Koa 中批量设置路由 JWT 校验的最佳实践
    • 前端开发者的 Redis 入门指南
    • AI工具系统架构设计
    • Koa 项目中间件设计技巧与最佳实践
  • Go

    • Go1
    • Go2
  • Python

    • Python 项目 API 代理转发:解决跨平台外网访问难题
  • MySql

    • 数据库连接

前端开发者的 Redis 入门指南

引言

作为前端开发者,你可能习惯了使用 localStorage 和 sessionStorage 来存储数据,但当你与后端协作时,常会听到"Redis"这个词。本文将从前端视角解释 Redis,帮助你快速理解这个强大的工具。

Redis 是什么?

Redis 是一个运行在服务器端的高性能键值存储数据库,主要将数据存储在内存中,可选择持久化到硬盘。它相当于服务器上的一个超级强化版的 localStorage。

localStorage 与 Redis 对比

特性localStorageRedis
运行环境浏览器端服务器端
存储容量通常 5MB 左右可达数 GB
过期机制无原生支持内置过期时间设置
数据类型仅字符串字符串、哈希表、列表、集合等
作用域仅限当前域名所有后端服务可访问
持久性持久存储直到手动清除可配置持久化策略
访问方式直接 JavaScript 访问通过后端 API 间接访问
并发处理不支持支持原子操作、事务等

Redis 核心数据类型

注意 使用 ioredis 操作 Redis 时,设置过期时间时,需要使用"EX"参数。

字符串(String)

类似 localStorage 的键值存储:

// localStorage
localStorage.setItem("username", "zhangsan");

// Redis等效操作
redis.set("username", "zhangsan");
redis.set("username", "zhangsan", 3600); // 设置1小时过期

// 正确的ioredis写法
redis.set("username", "zhangsan", "EX", 3600);

哈希表(Hash)

相当于存储 JavaScript 对象:

// localStorage需要序列化对象
localStorage.setItem("user", JSON.stringify({ name: "张三", age: 25 }));

// Redis直接支持对象字段操作
redis.hset("user:1001", "name", "张三");
redis.hset("user:1001", "age", "25");

在 Redis 中形成的数据结构如下:

键: "user:1001" (示例)
值: {
        name: "张三",
        age: "25"
    }

列表(List)

适合存储有序数据集合:

// localStorage需要序列化数组
localStorage.setItem("messages", JSON.stringify(["消息1", "消息2"]));

// Redis列表操作
redis.rpush("messages", "消息1"); // 右侧添加
redis.lpush("messages", "新消息"); // 左侧添加
redis.lrange("messages", 0, -1); // 获取全部

集合(Set)

存储不重复元素:

// localStorage无直接支持
const tags = new Set(["前端", "后端"]);
localStorage.setItem("tags", JSON.stringify([...tags]));

// Redis集合操作
redis.sadd("tags", "前端", "后端");
redis.sadd("tags", "前端"); // 重复添加无效

前端如何与 Redis 交互?

前端不能直接访问 Redis,必须通过后端 API 间接操作:

// 前端请求
async function getUserProfile(userId) {
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
}

// 后端实现(伪代码)
router.get("/api/users/:id", async (ctx) => {
  // 先尝试从Redis获取
  const cachedUser = await redisRepository.hgetall(`user:${ctx.params.id}`);

  if (Object.keys(cachedUser).length > 0) {
    return (ctx.body = cachedUser); // 返回缓存数据
  }

  // 缓存未命中,从数据库查询
  const user = await database.getUser(ctx.params.id);

  // 将结果存入Redis
  await redisRepository.hset(`user:${ctx.params.id}`, "name", user.name);
  await redisRepository.hset(
    `user:${ctx.params.id}`,
    "age",
    user.age.toString()
  );
  await redisRepository.expire(`user:${ctx.params.id}`, 3600); // 1小时过期

  return (ctx.body = user);
});

Redis 的实际应用场景

1. 会话管理

替代传统的服务器会话存储,更适合分布式系统:

// 登录成功后,后端生成token并存储在Redis
redis.set(`session:${userId}`, sessionData, 86400); // 24小时过期

// 正确的ioredis写法
redis.set(`session:${userId}`, sessionData, "EX", 86400);

// 前端存储token
localStorage.setItem("token", receivedToken);

2. 接口缓存

减少数据库查询,提高响应速度:

// 后端缓存热门数据
redis.set("hot_articles", JSON.stringify(articles), 600); // 10分钟过期

// 正确的ioredis写法
redis.set("hot_articles", JSON.stringify(articles), "EX", 600);

3. 限流控制

防止恶意请求,保护 API 安全:

// 记录用户请求次数
redis.incr(`rate:${userId}`);
redis.expire(`rate:${userId}`, 60); // 60秒过期

4. 实时数据

结合 WebSocket 提供实时数据更新:

// 发布消息到Redis频道
redis.publish("notifications", JSON.stringify(newNotification));

// WebSocket服务器订阅并推送给前端
redisClient.subscribe("notifications");

数据库缓存模式详解

缓存存储格式选择

示例中使用的是哈希表(Hash)结构来缓存数据库查询结果:

// 将用户数据缓存为哈希表结构
await redisRepository.hset(`user:${userId}`, "name", "张三");
await redisRepository.hset(`user:${userId}`, "age", "25");

在 Redis 中形成的数据结构如下:

键: "user:1001" (示例)
值: {
  name: "张三",
  age: "25"
}

为什么选择哈希表而不是简单字符串?主要有三个原因:

  1. 字段级访问 - 可以单独获取或更新用户的某个属性(如hget user:1001 name)

  2. 内存效率 - 相比 JSON 字符串,哈希表在 Redis 内存管理上更高效

  3. 操作便捷 - 可以使用hgetall一次获取完整对象,同时用单个expire指令设置整个对象过期

批量设置哈希表数据

在实际应用中,通常需要一次性将数据库查询结果存入 Redis 缓存。上面的示例是逐个字段设置,效率较低:

// 逐个设置字段(效率低)
await redisRepository.hset(`user:${id}`, "name", user.name);
await redisRepository.hset(`user:${id}`, "age", user.age.toString());

Redis 提供了hmset命令,可以一次设置多个字段,大大提高性能:

// 批量设置多个字段(更高效)
await redisRepository.hmset(`user:${id}`, {
  name: user.name,
  age: user.age.toString(),
  email: user.email,
  role: user.role,
});

// 设置过期时间(单独操作)
await redisRepository.expire(`user:${id}`, 3600); // 1小时过期

数据库对象直接缓存

初学者可能会想这样直接存储数据库返回的对象:

// 看似方便,实际有问题
await redisRepository.hmset(`user:${user.id}`, user);

这种做法存在几个问题:

  1. 类型问题 - Redis 哈希表只能存储字符串值,而数据库对象可能包含数字、布尔值、日期等多种类型

  2. 复杂对象 - 嵌套对象、数组等无法直接存储,需要序列化

  3. 特殊字段 - 某些数据库返回的元数据字段可能不需要缓存

最佳实践:类型转换函数

推荐创建一个辅助函数处理对象转换:

// 准备对象用于Redis缓存的工具函数
function prepareForCache(obj) {
  const result = {};
  Object.entries(obj).forEach(([key, value]) => {
    if (value !== null && value !== undefined) {
      if (typeof value === "object") {
        // 对象和数组需要序列化
        result[key] = JSON.stringify(value);
      } else {
        // 其他类型转为字符串
        result[key] = String(value);
      }
    }
  });
  return result;
}

// 使用
const user = await database.getUser(id);
await redisRepository.hmset(`user:${user.id}`, prepareForCache(user));
await redisRepository.expire(`user:${user.id}`, 3600);

这种方式既保留了批量设置的效率优势,又解决了类型转换问题,是缓存数据库查询结果的推荐做法。

其他可选存储格式

除了哈希表,常见的 Redis 缓存格式还有:

1. JSON 字符串

// 将整个对象序列化为JSON字符串存储
redis.set("user:1001", JSON.stringify({ name: "张三", age: 25 }), 3600);

// 正确的ioredis写法
redis.set("user:1001", JSON.stringify({ name: "张三", age: 25 }), "EX", 3600);
  • 优点:简单直接,与 localStorage 使用方式类似
  • 缺点:无法单独更新对象的某个字段,需要完整读取和写入

2. 分字段存储

// 每个字段单独存储
redis.set("user:1001:name", "张三", 3600);
redis.set("user:1001:age", "25", 3600);

// 正确的ioredis写法
redis.set("user:1001:name", "张三", "EX", 3600);
redis.set("user:1001:age", "25", "EX", 3600);
  • 优点:字段完全独立,可以设置不同过期时间

  • 缺点:管理复杂,需要维护多个键的关联

缓存更新策略

Redis 缓存通常采用以下策略:

  1. 缓存穿透 - 对不存在的数据也缓存 null 值,防止频繁查询数据库

  2. 缓存击穿 - 对热点数据使用互斥锁或提前更新机制

  3. 缓存雪崩 - 设置随机过期时间,避免同时失效

// 缓存穿透处理示例
if (cachedUser === null) {
  // 明确缓存了null
  return (ctx.body = { error: "用户不存在" });
} else if (cachedUser === undefined) {
  // 未缓存
  const user = await database.getUser(id);
  if (!user) {
    // 缓存null值,避免反复查询
    await redis.set(`user:${id}`, "null", 300); // 短期缓存5分钟

    // 正确的ioredis写法
    // await redis.set(`user:${id}`, "null", "EX", 300);

    return (ctx.body = { error: "用户不存在" });
  }
  // 正常缓存数据...
}

总结

Redis 作为服务器端的高性能数据存储解决方案,远比 localStorage 强大。对前端开发者而言,了解 Redis 有助于:

  1. 更好地理解后端数据缓存机制

  2. 与后端开发者进行更有效的沟通

  3. 设计更高效的前后端交互方案

  4. 为全栈开发打下基础

虽然你可能不会直接操作 Redis,但理解其工作原理和使用场景,将帮助你成为更全面的开发者。

最后更新时间:
贡献者: 何风顺
上一页
在 Koa 中批量设置路由 JWT 校验的最佳实践
下一页
AI工具系统架构设计