Heroku Redis #

Redis 概述 #

Heroku Redis 是 Heroku 提供的托管 Redis 服务,用于缓存、会话存储、消息队列等场景。

特点 #

特点 说明
完全托管 自动更新、监控
高可用 主从复制
持久化 支持 RDB 和 AOF
安全 TLS 加密连接
兼容 标准 Redis 协议

计划类型 #

计划 内存 连接数 价格/月
Mini 25MB 20 $15
Premium-0 1GB 40 $30
Premium-1 2.5GB 80 $60
Premium-2 6GB 160 $120
Premium-3 12GB 320 $240
Premium-5 50GB 500 $500

创建 Redis 实例 #

使用 CLI 创建 #

bash
# 创建 Mini Redis
heroku addons:create heroku-redis:mini

# 创建指定计划
heroku addons:create heroku-redis:premium-0

# 指定应用
heroku addons:create heroku-redis:mini --app myapp

查看 Redis 信息 #

bash
# 查看 Redis 信息
heroku redis:info

# 输出示例
# === redis-spherical-12345 (HEROKU_REDIS_MAROON_URL)
# Plan:                Mini
# Status:              Available
# Connections:         1/20
# Memory:              5MB/25MB
# Persistence:         Off
# Eviction Policy:     volatile-lru
# Created:             2024-01-15 10:00 UTC
# Version:             7.2.4

Redis URL #

bash
# 查看 Redis URL
heroku config:get REDIS_URL

# 输出格式
# redis://user:password@host:port

# 多个 Redis 实例
heroku config | grep REDIS

连接 Redis #

Node.js 连接 #

javascript
const Redis = require('ioredis');

const redis = new Redis(process.env.REDIS_URL, {
  tls: {
    rejectUnauthorized: false
  }
});

// 基本操作
await redis.set('key', 'value');
const value = await redis.get('key');

// 设置过期时间
await redis.set('key', 'value', 'EX', 3600); // 1小时后过期

// 删除键
await redis.del('key');

// 关闭连接
redis.quit();

Python 连接 #

python
import os
import redis

# 连接 Redis
r = redis.from_url(
    os.environ.get('REDIS_URL'),
    ssl_cert_reqs=None
)

# 基本操作
r.set('key', 'value')
value = r.get('key')

# 设置过期时间
r.setex('key', 3600, 'value')  # 1小时后过期

# 删除键
r.delete('key')

Ruby 连接 #

ruby
require 'redis'

# 连接 Redis
redis = Redis.new(url: ENV['REDIS_URL'], ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE })

# 基本操作
redis.set('key', 'value')
value = redis.get('key')

# 设置过期时间
redis.setex('key', 3600, 'value')

# 删除键
redis.del('key')

缓存使用 #

基本缓存模式 #

javascript
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);

async function getCachedData(key, fetchFn, ttl = 3600) {
  const cached = await redis.get(key);
  
  if (cached) {
    return JSON.parse(cached);
  }
  
  const data = await fetchFn();
  await redis.set(key, JSON.stringify(data), 'EX', ttl);
  
  return data;
}

// 使用示例
const user = await getCachedData(
  `user:${userId}`,
  () => fetchUserFromDB(userId),
  300 // 5分钟缓存
);

缓存装饰器 #

javascript
function cacheable(keyPrefix, ttl = 3600) {
  return function (target, propertyKey, descriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = async function (...args) {
      const key = `${keyPrefix}:${JSON.stringify(args)}`;
      const cached = await redis.get(key);
      
      if (cached) {
        return JSON.parse(cached);
      }
      
      const result = await originalMethod.apply(this, args);
      await redis.set(key, JSON.stringify(result), 'EX', ttl);
      
      return result;
    };
    
    return descriptor;
  };
}

// 使用示例
class UserService {
  @cacheable('user', 300)
  async getUser(id) {
    return await db.query('SELECT * FROM users WHERE id = $1', [id]);
  }
}

缓存失效 #

javascript
// 主动失效
async function updateUser(userId, data) {
  await db.query('UPDATE users SET ... WHERE id = $1', [userId]);
  await redis.del(`user:${userId}`);
}

// 批量失效
async function invalidateUserCache(userId) {
  const keys = await redis.keys(`user:${userId}:*`);
  if (keys.length > 0) {
    await redis.del(...keys);
  }
}

// 模式匹配删除
async function deleteByPattern(pattern) {
  let cursor = '0';
  do {
    const [nextCursor, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
    if (keys.length > 0) {
      await redis.del(...keys);
    }
    cursor = nextCursor;
  } while (cursor !== '0');
}

会话存储 #

Express Session #

javascript
const express = require('express');
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const Redis = require('ioredis');

const app = express();

const redisClient = new Redis(process.env.REDIS_URL);

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,
    httpOnly: true,
    maxAge: 1000 * 60 * 60 * 24 * 7 // 7天
  }
}));

Flask Session #

python
from flask import Flask
from flask_session import Session
import redis

app = Flask(__name__)

app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url(
    os.environ.get('REDIS_URL'),
    ssl_cert_reqs=None
)

Session(app)

Rails Session #

ruby
# config/initializers/session_store.rb
Rails.application.config.session_store :redis_store,
  servers: ENV['REDIS_URL'],
  expire_after: 7.days,
  key: '_myapp_session',
  tls: { verify_mode: OpenSSL::SSL::VERIFY_NONE }

消息队列 #

发布订阅 #

javascript
// 发布者
const publisher = new Redis(process.env.REDIS_URL);

await publisher.publish('notifications', JSON.stringify({
  type: 'new_message',
  data: { userId: 123, message: 'Hello' }
}));

// 订阅者
const subscriber = new Redis(process.env.REDIS_URL);

subscriber.subscribe('notifications');

subscriber.on('message', (channel, message) => {
  const data = JSON.parse(message);
  console.log(`Received: ${JSON.stringify(data)}`);
});

任务队列 #

javascript
const { Queue, Worker } = require('bullmq');

// 创建队列
const emailQueue = new Queue('emails', {
  connection: new Redis(process.env.REDIS_URL)
});

// 添加任务
await emailQueue.add('send-email', {
  to: 'user@example.com',
  subject: 'Welcome',
  body: 'Hello!'
});

// 处理任务
const worker = new Worker('emails', async job => {
  console.log(`Processing job ${job.id}`);
  await sendEmail(job.data);
}, {
  connection: new Redis(process.env.REDIS_URL)
});

worker.on('completed', job => {
  console.log(`Job ${job.id} completed`);
});

worker.on('failed', (job, err) => {
  console.error(`Job ${job.id} failed:`, err);
});

延迟任务 #

javascript
// 延迟执行
await emailQueue.add('send-email', data, {
  delay: 5 * 60 * 1000 // 5分钟后执行
});

// 定时执行
await emailQueue.add('send-email', data, {
  repeat: {
    every: 60 * 60 * 1000 // 每小时执行
  }
});

高级功能 #

原子操作 #

javascript
// 计数器
const count = await redis.incr('page:views');

// 带过期时间的计数器
await redis.multi()
  .incr('api:rate-limit:user:123')
  .expire('api:rate-limit:user:123', 60)
  .exec();

// 限流
async function rateLimit(key, limit, window) {
  const count = await redis.incr(key);
  if (count === 1) {
    await redis.expire(key, window);
  }
  return count <= limit;
}

分布式锁 #

javascript
const Redlock = require('redlock');

const redlock = new Redlock([new Redis(process.env.REDIS_URL)]);

async function withLock(resource, ttl, fn) {
  const lock = await redlock.acquire([resource], ttl);
  try {
    return await fn();
  } finally {
    await lock.release();
  }
}

// 使用示例
await withLock('user:123:email', 5000, async () => {
  // 执行需要锁保护的操作
  await sendEmail();
});

排行榜 #

javascript
// 添加分数
await redis.zadd('leaderboard', 100, 'user1');
await redis.zadd('leaderboard', 200, 'user2');
await redis.zadd('leaderboard', 150, 'user3');

// 获取排名
const rank = await redis.zrevrank('leaderboard', 'user1');

// 获取排行榜
const top10 = await redis.zrevrange('leaderboard', 0, 9, 'WITHSCORES');

Redis 管理 #

访问 Redis CLI #

bash
# 连接 Redis CLI
heroku redis:cli

# 执行命令
heroku redis:cli INFO
heroku redis:cli DBSIZE
heroku redis:cli KEYS "*"

监控 Redis #

bash
# 查看 Redis 信息
heroku redis:info

# 查看连接
heroku redis:connections

# 查看内存使用
heroku redis:cli INFO memory

清空数据 #

bash
# 清空当前数据库
heroku redis:cli FLUSHDB

# 清空所有数据库(谨慎使用)
heroku redis:cli FLUSHALL

性能优化 #

连接池配置 #

javascript
const Redis = require('ioredis');

const redis = new Redis(process.env.REDIS_URL, {
  tls: { rejectUnauthorized: false },
  maxRetriesPerRequest: 3,
  enableReadyCheck: true,
  enableOfflineQueue: true,
  connectionPoolSize: 10
});

批量操作 #

javascript
// 使用 Pipeline
const pipeline = redis.pipeline();
for (let i = 0; i < 100; i++) {
  pipeline.set(`key:${i}`, `value:${i}`);
}
await pipeline.exec();

// 使用 Multi(事务)
await redis.multi()
  .set('key1', 'value1')
  .set('key2', 'value2')
  .get('key1')
  .exec();

内存优化 #

javascript
// 使用 Hash 代替多个 Key
// 不好:多个 Key
await redis.set('user:1:name', 'John');
await redis.set('user:1:email', 'john@example.com');

// 好:使用 Hash
await redis.hset('user:1', 'name', 'John', 'email', 'john@example.com');

// 使用压缩
const zlib = require('zlib');
const compressed = zlib.deflateSync(JSON.stringify(data));
await redis.set('large-data', compressed.toString('base64'));

故障排查 #

连接问题 #

bash
# 检查 Redis 状态
heroku redis:info

# 检查连接数
heroku redis:cli INFO clients

# 查看日志
heroku logs --tail | grep redis

内存问题 #

bash
# 查看内存使用
heroku redis:cli INFO memory

# 查看大 Key
heroku redis:cli --bigkeys

# 查看键空间
heroku redis:cli INFO keyspace

性能问题 #

bash
# 查看慢查询
heroku redis:cli SLOWLOG GET 10

# 监控命令
heroku redis:cli MONITOR

下一步 #

Redis 掌握后,接下来学习 文件存储 了解如何处理静态文件!

最后更新:2026-03-28