likes
comments
collection
share

第十三章:Node.js 实战入门指南 - Redis实战入门

作者站长头像
站长
· 阅读数 14

非常抱歉,因为工作确实太忙了,快有一个月没有更新了!

上一个章节我们讲的是 第十二章:Node.js 实战入门指南 - Express 数据库集成和连接(MySQL、MongoDB、PostgreSQL、Redis)

这个章节讲redis这是一个非常重要的技术,应用于各种场景,也是我们后面实战项目中必不可少的内容

Redis是一个开源的高性能键值存储数据库,广泛用于构建各种实时应用和缓存解决方案。下面是一些Redis在实战中常见的应用场景和用法:

我们使用ioredis 来实现实现案例和代码示例:

npm install ioredis

1. 缓存

Redis最常见的用途之一是作为缓存层,用于存储频繁读取的数据,以减轻后端数据库的负担,加快读取速度,并提高应用性能。通过设置合适的过期时间,可以控制缓存数据的有效期,保证缓存数据的新鲜性。

const Redis = require("ioredis");
const redis = new Redis();

// 设置缓存数据
redis.set("user:123", JSON.stringify({ name: "John", age: 30 }), "EX", 3600); // 设置过期时间

// 获取缓存数据
redis.get("user:123").then((data) => {
  if (data) {
    const user = JSON.parse(data);
    console.log(user);
  } else {
    // 如果缓存不存在,从后端数据库获取数据并更新缓存
    // ...
  }
});

2. 会话存储

可以使用Redis来存储用户会话信息,特别是在分布式环境下,这样可以确保用户在任意一台服务器上的登录状态和会话信息都是一致的。

const session = require("express-session");
const RedisStore = require("connect-redis")(session);
const Redis = require("ioredis");
const redis = new Redis();

app.use(
  session({
    store: new RedisStore({ client: redis }),
    secret: "your-secret-key",
    resave: false,
    saveUninitialized: true,
  })
);

3. 计数器和统计

Redis支持原子性操作,因此可以用于实时统计、计数器以及计数排行榜等功能。比如,记录网站的PV(页面浏览量)和UV(独立访客数)等

const Redis = require("ioredis");
const redis = new Redis();

// 每次访问增加PV计数器
redis.incr("website:pv");

// 获取PV计数器的值
redis.get("website:pv").then((pvCount) => {
  console.log("PV Count:", pvCount);
});

4. 发布/订阅

Redis提供了发布/订阅功能,用于构建实时消息系统和事件通知系统。发布者可以将消息发布到指定的频道,而订阅者可以监听并接收感兴趣的频道上的消息。

const Redis = require("ioredis");
const subscriber = new Redis();
const publisher = new Redis();

// 订阅频道
subscriber.subscribe("news-channel");

// 接收消息
subscriber.on("message", (channel, message) => {
  console.log(`Received message from channel ${channel}: ${message}`);
});

// 发布消息
publisher.publish("news-channel", "Breaking news: Redis is awesome!");

5. 地理位置信息存储

通过Redis的地理位置功能,可以存储和查询地理位置信息,实现附近的人、附近的商店等应用。

const Redis = require("ioredis");
const redis = new Redis();
// 存储地理位置信息
redis.geoadd("locations", 13.361389, 38.115556, "Palermo");
redis.geoadd("locations", 15.087269, 37.502669, "Catania");

// 查询附近的地点
redis.georadius("locations", 15, 37, 200, "km").then((locations) => {
  console.log("Nearby locations:", locations);
});

6. 分布式锁

在分布式系统中,经常需要实现互斥访问共享资源的功能。Redis提供了分布式锁的功能,可以确保在分布式环境下,只有一个客户端可以访问某个共享资源,从而避免竞争条件。

const Redis = require("ioredis");
const redis = new Redis();

// 获取锁
async function acquireLock(key, timeout) {
  const lockValue = Date.now() + timeout + 1;
  const result = await redis.setnx(key, lockValue);

  if (result === 1 || Date.now() > parseInt(await redis.get(key), 10)) {
    await redis.set(key, lockValue);
    return true;
  }

  return false;
}

// 释放锁
async function releaseLock(key) {
  await redis.del(key);
}

// 使用锁
async function someFunction() {
  const lockKey = "my-lock";
  const lockTimeout = 5000;

  if (await acquireLock(lockKey, lockTimeout)) {
    try {
      // 执行需要互斥访问的操作
      // ...
    } finally {
      await releaseLock(lockKey);
    }
  } else {
    // 未能获取锁,可以选择重试或采取其他策略
  }
}

7. 消息队列

使用Redis的列表结构,可以构建简单的消息队列,用于解耦系统中不同组件的通信,实现异步处理和削峰填谷。

const Redis = require("ioredis");
const redis = new Redis();

// 将消息加入队列
redis.rpush("messages", "message1");
redis.rpush("messages", "message2");

// 处理消息
async function processMessages() {
  while (true) {
    const message = await redis.lpop("messages");
    if (!message) break;

    // 处理消息
    console.log("Processing message:", message);
  }
}

8. 限流

在高并发场景下,为了保护后端服务的稳定性,可以使用Redis的令牌桶或漏桶算法来实现请求的限流控制。

const Redis = require("ioredis");
const redis = new Redis();

// 限流函数
async function rateLimit(key, maxRequests, window) {
  const currentTimestamp = Date.now();
  const requests = await redis.zrangebyscore(key, currentTimestamp - window, currentTimestamp);

  if (requests.length < maxRequests) {
    await redis.zadd(key, currentTimestamp, currentTimestamp);
    return true;
  }

  return false;
}

// 使用限流
async function handleRequest() {
  const clientId = "client-123";
  const maxRequestsPerSecond = 10;
  const windowInSeconds = 1;

  if (await rateLimit(clientId, maxRequestsPerSecond, windowInSeconds * 1000)) {
    // 处理请求
    // ...
  } else {
    // 请求被限制
    // ...
  }
}

9. 持久化

Redis支持将内存中的数据持久化到磁盘,以便在服务器重启后能够恢复数据。

ioredis默认支持RDB持久化和AOF持久化,你可以在Redis的配置文件中进行相应的配置。不过,建议在生产环境中使用AOF持久化以确保更高的数据安全性。

10. 分布式缓存更新

在分布式系统中,如果多个应用实例同时操作相同的数据,可能会导致数据不一致的问题。这时可以使用Redis的发布/订阅功能来实现分布式缓存更新的通知机制。

// 订阅者
const Redis = require("ioredis");
const redis = new Redis();

redis.subscribe("cache-updates", (err, count) => {
  if (err) {
    console.error("Failed to subscribe:", err);
  } else {
    console.log("Subscribed to cache-updates channel");
  }
});

redis.on("message", (channel, message) => {
  console.log("Received cache update:", message);
  // 更新本地缓存
  // ...
});
// 发布者
const Redis = require("ioredis");
const redis = new Redis();

const data = { key: "value" };
redis.publish("cache-updates", JSON.stringify(data));

11. 会话超时

在会话存储中,用户的会话信息通常需要设置超时时间,以确保在一定时间内没有活动的会话自动过期。

const session = require("express-session");
const RedisStore = require("connect-redis")(session);
const redis = new Redis();

app.use(
  session({
    store: new RedisStore({ client: redis }),
    secret: "your-secret-key",
    resave: false,
    saveUninitialized: true,
    cookie: { maxAge: 3600000 }, // 1 hour expiration
  })
);

12. 延时任务

使用Redis的有序集合可以实现延时任务队列。将任务的执行时间作为分值(score),任务内容作为成员(member),然后通过定时检查有序集合,执行到期的任务。

const Redis = require("ioredis");
const redis = new Redis();

// 添加延时任务
function scheduleDelayedTask(task, delay) {
  const timestamp = Date.now() + delay;
  redis.zadd("delayed-tasks", timestamp, JSON.stringify(task));
}

// 定时检查并执行延时任务
setInterval(async () => {
  const currentTimestamp = Date.now();
  const tasks = await redis.zrangebyscore("delayed-tasks", 0, currentTimestamp);

  tasks.forEach((task) => {
    // 执行任务
    console.log("Executing delayed task:", JSON.parse(task));
    // ...
  });

  // 删除已执行的任务
  if (tasks.length > 0) {
    redis.zremrangebyscore("delayed-tasks", 0, currentTimestamp);
  }
}, 1000); // 每秒检查一次

13. 消息去重

使用Redis的集合结构可以很容易地实现消息去重,确保重复的消息不会被处理多次。

const Redis = require("ioredis");
const redis = new Redis();

async function processMessage(message) {
  if (await redis.sadd("processed-messages", message) === 1) {
    // 该消息尚未处理过
    // 处理消息
    console.log("Processing message:", message);
    // ...
  } else {
    // 该消息已经处理过,不再重复处理
    console.log("Message already processed:", message);
  }
}

14. 自动补全

使用Redis的有序集合和哈希表可以实现简单的自动补全功能,比如用户搜索时的搜索建议。

const Redis = require("ioredis");
const redis = new Redis();

// 添加搜索关键词及权重
redis.zadd("search:keywords", 1, "apple");
redis.zadd("search:keywords", 3, "banana");
redis.zadd("search:keywords", 2, "orange");

// 根据前缀获取搜索建议
async function getSearchSuggestions(prefix) {
  const suggestions = await redis.zrevrangebylex("search:keywords", `[${prefix}`, `(${prefix}\xff`);
  return suggestions;
}

// 获取搜索建议
getSearchSuggestions("app").then((suggestions) => {
  console.log("Search suggestions:", suggestions);
});

15. 场景计数

使用Redis的HyperLogLog结构可以实现场景计数,比如统计网站的独立IP访问数量。

const Redis = require("ioredis");
const redis = new Redis();

// 统计独立IP访问数量
function trackUniqueIP(ip) {
  redis.pfadd("website:unique-visitors", ip);
}

// 获取独立IP访问数量
redis.pfcount("website:unique-visitors").then((count) => {
  console.log("Unique visitors:", count);
});

这些示例继续展示了ioredis在更多应用场景下的使用方法。ioredis是一个强大的Redis客户端,提供了丰富的功能和灵活的接口,能够满足各种不同的实际需求。在实际应用中,根据具体情况选择合适的Redis数据结构和操作,以实现高性能、高可用性的分布式应用。