BunRedis集成 BunRedis

这个技能专注于在Bun运行时中集成和使用Redis,支持多种客户端如ioredis和Upstash,用于缓存、发布订阅、会话存储和键值操作。适用于后端开发、实时数据处理和性能优化。关键词:Bun, Redis, 缓存, 发布订阅, 数据库集成, 后端开发。

后端开发 0 次安装 0 次浏览 更新于 3/8/2026

名称: Bun Redis 描述: 在Bun中使用Redis时使用(ioredis, Upstash),适用于缓存、发布/订阅、会话存储或键值操作。

Bun Redis

使用流行的Redis客户端与Bun集成Redis。

客户端选项

客户端 最佳用途 安装
ioredis 自托管Redis bun add ioredis
@upstash/redis 无服务器/边缘 bun add @upstash/redis
redis 官方Node客户端 bun add redis

ioredis 设置

import Redis from "ioredis";

// 默认连接
const redis = new Redis();

// 带选项
const redis = new Redis({
  host: "localhost",
  port: 6379,
  password: "secret",
  db: 0,
});

// 连接字符串
const redis = new Redis("redis://:password@localhost:6379/0");

// TLS 连接
const redis = new Redis({
  host: "redis.example.com",
  port: 6380,
  tls: {},
});

基本操作

import Redis from "ioredis";

const redis = new Redis();

// 字符串
await redis.set("name", "Alice");
await redis.set("count", "100");
await redis.setex("temp", 60, "expires in 60s"); // 带TTL

const name = await redis.get("name"); // "Alice"
const count = await redis.incr("count"); // 101
await redis.del("name");

// 检查存在性
const exists = await redis.exists("name"); // 0 或 1

// TTL
await redis.expire("key", 3600); // 设置1小时TTL
const ttl = await redis.ttl("key"); // 获取剩余TTL

数据结构

哈希

// 设置哈希字段
await redis.hset("user:1", {
  name: "Alice",
  email: "alice@example.com",
  age: "30",
});

// 获取单个字段
const name = await redis.hget("user:1", "name");

// 获取所有字段
const user = await redis.hgetall("user:1");
// { name: "Alice", email: "...", age: "30" }

// 递增字段
await redis.hincrby("user:1", "visits", 1);

列表

// 添加到列表
await redis.rpush("queue", "task1", "task2");
await redis.lpush("queue", "urgent");

// 从列表弹出
const task = await redis.lpop("queue"); // "urgent"
const blocking = await redis.blpop("queue", 5); // 等待5秒

// 范围
const items = await redis.lrange("queue", 0, -1);

集合

// 添加成员
await redis.sadd("tags", "javascript", "typescript", "bun");

// 检查成员资格
const isMember = await redis.sismember("tags", "bun"); // 1

// 获取所有成员
const tags = await redis.smembers("tags");

// 集合操作
await redis.sinter("tags1", "tags2"); // 交集
await redis.sunion("tags1", "tags2"); // 并集

有序集合

// 添加带分数
await redis.zadd("leaderboard", 100, "alice", 200, "bob", 150, "charlie");

// 按排名获取
const top3 = await redis.zrevrange("leaderboard", 0, 2, "WITHSCORES");

// 按分数范围获取
const highScores = await redis.zrangebyscore("leaderboard", 100, 200);

// 递增分数
await redis.zincrby("leaderboard", 50, "alice");

JSON (RedisJSON)

// 需要RedisJSON模块
await redis.call("JSON.SET", "user:1", "$", JSON.stringify({
  name: "Alice",
  settings: { theme: "dark" },
}));

const user = await redis.call("JSON.GET", "user:1");
const settings = await redis.call("JSON.GET", "user:1", "$.settings");

发布/订阅

import Redis from "ioredis";

// 发布者
const pub = new Redis();

// 订阅者
const sub = new Redis();

// 订阅频道
sub.subscribe("notifications", (err, count) => {
  console.log(`Subscribed to ${count} channels`);
});

// 处理消息
sub.on("message", (channel, message) => {
  console.log(`${channel}: ${message}`);
});

// 发布
await pub.publish("notifications", JSON.stringify({
  type: "alert",
  message: "Hello!",
}));

// 模式订阅
sub.psubscribe("user:*");
sub.on("pmessage", (pattern, channel, message) => {
  console.log(`${pattern} -> ${channel}: ${message}`);
});

事务

// Multi/Exec
const results = await redis
  .multi()
  .set("key1", "value1")
  .set("key2", "value2")
  .incr("counter")
  .exec();

// 管道(无原子性,性能更好)
const pipeline = redis.pipeline();
pipeline.set("key1", "value1");
pipeline.set("key2", "value2");
pipeline.incr("counter");
const results = await pipeline.exec();

Upstash Redis (无服务器)

import { Redis } from "@upstash/redis";

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

// 与ioredis相同的API
await redis.set("key", "value");
const value = await redis.get("key");

// 带自动JSON序列化
await redis.set("user", { name: "Alice", age: 30 });
const user = await redis.get<{ name: string; age: number }>("user");

缓存模式

缓存旁路

async function getUser(id: string) {
  // 检查缓存
  const cached = await redis.get(`user:${id}`);
  if (cached) {
    return JSON.parse(cached);
  }

  // 从数据库获取
  const user = await db.query.users.findFirst({
    where: eq(users.id, id),
  });

  // 缓存1小时
  if (user) {
    await redis.setex(`user:${id}`, 3600, JSON.stringify(user));
  }

  return user;
}

直写缓存

async function updateUser(id: string, data: UserUpdate) {
  // 更新数据库
  await db.update(users).set(data).where(eq(users.id, id));

  // 更新缓存
  const user = await db.query.users.findFirst({
    where: eq(users.id, id),
  });
  await redis.setex(`user:${id}`, 3600, JSON.stringify(user));

  return user;
}

限流

async function rateLimit(key: string, limit: number, window: number) {
  const current = await redis.incr(key);

  if (current === 1) {
    await redis.expire(key, window);
  }

  return current <= limit;
}

// 用法
const allowed = await rateLimit(`rate:${userId}`, 100, 60);
if (!allowed) {
  throw new Error("超过速率限制");
}

会话存储

import { Hono } from "hono";
import Redis from "ioredis";
import { v4 as uuid } from "uuid";

const redis = new Redis();
const app = new Hono();

app.use("*", async (c, next) => {
  const sessionId = c.req.header("X-Session-Id") || uuid();
  const session = await redis.hgetall(`session:${sessionId}`);

  c.set("session", session);
  c.set("sessionId", sessionId);

  await next();

  // 保存会话
  const updatedSession = c.get("session");
  if (Object.keys(updatedSession).length > 0) {
    await redis.hset(`session:${sessionId}`, updatedSession);
    await redis.expire(`session:${sessionId}`, 86400); // 24小时
  }
});

常见错误

错误 原因 修复
ECONNREFUSED Redis未运行 启动Redis服务器
NOAUTH 需要身份验证 提供密码
WRONGTYPE 错误的数据类型 检查键类型
OOM 内存不足 配置最大内存

何时加载参考资料

加载 references/clustering.md 当:

  • Redis集群设置
  • Sentinel配置
  • 高可用性模式

加载 references/lua-scripts.md 当:

  • 自定义Lua脚本
  • 原子操作
  • 复杂事务