Redis 缓存教程

学习使用 Upstash Redis 实现高性能缓存,提升应用程序的响应速度和用户体验

为什么需要 Redis 缓存?

Redis 是一个内存键值数据库,常用作缓存层来提升应用性能。 通过减少数据库查询次数,Redis 可以显著提高应用的响应速度和并发处理能力。

高性能

内存操作,微秒级响应时间

减少数据库压力

缓存热点数据,降低数据库负载

灵活过期

支持多种过期策略和数据结构

Upstash Redis 设置

Upstash 提供了无服务器的 Redis 服务,与 Next.js 完美集成。 它支持基于 HTTP 的连接,非常适合 Edge 环境。

lib/redis.ts
// lib/redis.ts
import { Redis } from '@upstash/redis';

export const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});

// 类型安全的缓存工具函数
export class CacheManager {
  static async get<T>(key: string): Promise<T | null> {
    try {
      const data = await redis.get(key);
      return data as T;
    } catch (error) {
      console.error('Redis get error:', error);
      return null;
    }
  }

  static async set<T>(
    key: string,
    value: T,
    options?: { ex?: number; px?: number }
  ): Promise<boolean> {
    try {
      await redis.set(key, value, options);
      return true;
    } catch (error) {
      console.error('Redis set error:', error);
      return false;
    }
  }

  static async del(key: string): Promise<boolean> {
    try {
      await redis.del(key);
      return true;
    } catch (error) {
      console.error('Redis del error:', error);
      return false;
    }
  }

  static async exists(key: string): Promise<boolean> {
    try {
      const result = await redis.exists(key);
      return result === 1;
    } catch (error) {
      console.error('Redis exists error:', error);
      return false;
    }
  }
}

环境变量配置

.env.local
# .env.local
UPSTASH_REDIS_REST_URL="https://your-redis-url.upstash.io"
UPSTASH_REDIS_REST_TOKEN="your_redis_token_here"

Redis 基本操作

Redis 支持多种数据结构,包括字符串、哈希、列表、集合等。 了解这些基本操作是使用 Redis 的基础。

Redis 基本操作
// 基本的 Redis 操作
import { redis } from '@/lib/redis';

// 字符串操作
await redis.set('user:1:name', '张三');
const userName = await redis.get('user:1:name');

// 带过期时间的缓存 (60秒)
await redis.set('session:abc123', { userId: 1, role: 'admin' }, { ex: 60 });

// 数字操作
await redis.set('page:views', 0);
await redis.incr('page:views'); // 增加1
await redis.incrby('page:views', 10); // 增加10

// 哈希操作
await redis.hset('user:1', {
  name: '张三',
  email: 'zhangsan@example.com',
  lastLogin: new Date().toISOString()
});

const user = await redis.hgetall('user:1');
const userEmail = await redis.hget('user:1', 'email');

// 列表操作
await redis.lpush('notifications', '新消息1');
await redis.lpush('notifications', '新消息2');
const notifications = await redis.lrange('notifications', 0, -1);

// 集合操作
await redis.sadd('user:1:tags', 'developer', 'typescript', 'react');
const userTags = await redis.smembers('user:1:tags');

// 有序集合操作
await redis.zadd('leaderboard', { score: 100, member: 'user:1' });
await redis.zadd('leaderboard', { score: 200, member: 'user:2' });
const topUsers = await redis.zrange('leaderboard', 0, 2, { withScores: true });

Redis 缓存操作演示

体验 Redis 的基本操作,包括添加、查询、过期等功能

缓存操作

缓存键列表 (3)

user:1
永不过期
posts:latest
永不过期
stats:views
永不过期

选择一个缓存键查看详情

缓存策略演示

了解不同的缓存策略及其适用场景

选择缓存策略

策略执行结果

Redis 实际功能测试

测试真实的Redis功能:验证码生成/验证、连接测试、限流保护等

Redis 功能测试

验证码生成与验证
标识符(邮箱/手机号):
输入验证码:
Redis连接测试
限流测试 (每60秒最多5次请求)
点击下方按钮多次,测试限流功能。连续点击超过5次后,将会收到限流提示。

Next.js API 路由集成

在 Next.js API 路由中集成 Redis 缓存,实现高性能的数据接口。 合理的缓存策略可以大幅提升 API 响应速度。

API 路由缓存集成
// API 路由中的缓存使用

// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { redis } from '@/lib/redis';
import { prisma } from '@/lib/prisma';

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const page = parseInt(searchParams.get('page') || '1');
  const limit = parseInt(searchParams.get('limit') || '10');

  const cacheKey = `posts:page:${page}:limit:${limit}`;

  try {
    // 尝试从缓存获取
    const cached = await redis.get(cacheKey);
    if (cached) {
      return NextResponse.json({
        data: cached,
        source: 'cache'
      });
    }

    // 从数据库获取
    const posts = await prisma.post.findMany({
      skip: (page - 1) * limit,
      take: limit,
      include: { author: true },
      orderBy: { createdAt: 'desc' }
    });

    // 缓存结果,5分钟过期
    await redis.set(cacheKey, posts, { ex: 300 });

    return NextResponse.json({
      data: posts,
      source: 'database'
    });
  } catch (error) {
    return NextResponse.json(
      { error: '获取文章失败' },
      { status: 500 }
    );
  }
}

// app/api/stats/route.ts
export async function GET() {
  const cacheKey = 'site:stats';

  // 使用 Redis 作为计数器
  const [totalViews, totalUsers] = await Promise.all([
    redis.get('stats:total_views') || 0,
    redis.get('stats:total_users') || 0,
  ]);

  // 实时统计也可以缓存短时间
  const cachedStats = await redis.get(cacheKey);
  if (cachedStats) {
    return NextResponse.json(cachedStats);
  }

  const stats = {
    totalViews,
    totalUsers,
    timestamp: new Date().toISOString()
  };

  // 缓存1分钟
  await redis.set(cacheKey, stats, { ex: 60 });

  return NextResponse.json(stats);
}

性能优化技巧

通过合理的缓存设计和优化技巧,可以进一步提升缓存的效率和可靠性。 这些技巧包括批量操作、缓存穿透保护、分布式锁等。

性能优化技巧
// 性能优化技巧

// 1. 批量操作使用 Pipeline
export async function batchUpdateCache(updates: Array<{key: string, value: unknown}>) {
  const pipeline = redis.pipeline();

  updates.forEach(({ key, value }) => {
    pipeline.set(key, value, { ex: 300 });
  });

  // 一次性执行所有命令
  await pipeline.exec();
}

// 2. 缓存穿透保护
export async function getCachedDataWithNullProtection<T>(
  key: string,
  fetchData: () => Promise<T | null>
): Promise<T | null> {
  // 检查缓存
  const cached = await redis.get(key);
  if (cached !== null) {
    return cached === 'NULL' ? null : cached as T;
  }

  // 获取数据
  const data = await fetchData();

  // 即使是 null 也要缓存,防止缓存穿透
  await redis.set(key, data || 'NULL', { ex: 60 });

  return data;
}

// 3. 分布式锁防止缓存击穿
export async function getCachedDataWithLock<T>(
  key: string,
  lockKey: string,
  fetchData: () => Promise<T>
): Promise<T> {
  // 检查缓存
  const cached = await redis.get(key);
  if (cached) return cached as T;

  // 尝试获取锁
  const lockAcquired = await redis.set(lockKey, '1', { nx: true, ex: 10 });

  if (lockAcquired) {
    try {
      // 再次检查缓存(双重检查)
      const recheck = await redis.get(key);
      if (recheck) return recheck as T;

      // 获取数据并缓存
      const data = await fetchData();
      await redis.set(key, data, { ex: 300 });
      return data;
    } finally {
      // 释放锁
      await redis.del(lockKey);
    }
  } else {
    // 等待锁释放后重试
    await new Promise(resolve => setTimeout(resolve, 100));
    return getCachedDataWithLock(key, lockKey, fetchData);
  }
}

// 4. 缓存更新策略
export async function invalidateRelatedCache(pattern: string) {
  // 获取匹配的键
  const keys = await redis.keys(pattern);

  if (keys.length > 0) {
    // 批量删除
    await redis.del(...keys);
  }
}

// 使用示例
export async function updatePost(id: number, data: Record<string, unknown>) {
  const updatedPost = await prisma.post.update({
    where: { id },
    data
  });

  // 删除相关缓存
  await Promise.all([
    redis.del(`post:${id}`),
    invalidateRelatedCache('posts:page:*'),
    invalidateRelatedCache('posts:author:*')
  ]);

  return updatedPost;
}

Redis 操作练习

尝试编写 Redis 操作代码,体验缓存的强大功能:

Redis 练习场

加载编辑器中...

Redis 最佳实践

性能优化

  • 使用 pipeline 进行批量操作
  • 合理设置过期时间,避免内存泄漏
  • 选择合适的数据结构
  • 避免大 key 和热 key 问题

缓存策略

  • 防止缓存穿透、击穿和雪崩
  • 实现优雅的缓存更新机制
  • 监控缓存命中率和性能指标
  • 制定缓存失效和容灾策略

准备好了吗?

现在你已经掌握了 Redis 缓存的核心概念,让我们继续学习阿里云 OSS 文件存储服务。

学习阿里云 OSS