使用 Claude Code 的 Redis 缓存:Cache-Aside、Write-Through 与 TTL 策略

发布: (2026年3月11日 GMT+8 12:57)
4 分钟阅读
原文: Dev.to

Source: Dev.to

缓存规则

Redis 缓存设计规则

模式

  • Cache-Aside:高读/低写数据(用户资料、产品目录)
  • Write-Through:需要强一致性的数据(余额、库存)
  • Pub/Sub:缓存失效传播(分布式环境)

TTL(必需)

  • 所有缓存必须设置 TTL(不允许永久缓存)
  • 主数据:TTL 1 小时
  • 会话/资料:TTL 15 分钟
  • 聚合:TTL 5 分钟

缓存键

  • 格式:{service}:{entity}:{id}
  • 示例:user:profile:123, product:detail:abc
  • 版本控制:user:v2:profile:123(模式变更时提升版本)

监控

  • 将缓存命中率作为指标进行跟踪
  • 为冷启动(未预热缓存)场景进行设计

## 缓存旁路模式

**需求**

- 缓存键:`user:profile:{userId}`
- TTL:15 分钟
- 缓存未命中时:从数据库获取并写入缓存
- 个人资料更新时:失效缓存
- 记录缓存命中/未命中

### 生成的服务 (`src/services/userCacheService.ts`)

```typescript
// src/services/userCacheService.ts
import { createClient } from 'redis';
import { prisma } from '../prisma/client';
import { logger } from '../logger';

const redis = createClient({ url: process.env.REDIS_URL });
const CACHE_TTL = 15 * 60; // seconds
const KEY_PREFIX = 'user:profile';

function cacheKey(userId: string): string {
  return `${KEY_PREFIX}:${userId}`;
}

export async function getUserProfile(userId: string): Promise {
  const key = cacheKey(userId);
  const cached = await redis.get(key);

  if (cached) {
    logger.debug({ userId, cacheHit: true }, 'User profile cache hit');
    return JSON.parse(cached) as UserProfile;
  }

  logger.debug({ userId, cacheHit: false }, 'User profile cache miss');

  const user = await prisma.user.findUnique({
    where: { id: userId },
    select: {
      id: true,
      name: true,
      email: true,
      avatarUrl: true,
      updatedAt: true,
    },
  });

  if (!user) return null;

  await redis.set(key, JSON.stringify(user), { EX: CACHE_TTL });
  return user;
}

export async function invalidateUserProfile(userId: string): Promise {
  await redis.del(cacheKey(userId));
  logger.info({ userId }, 'User profile cache invalidated');
}

更新时失效

// src/services/userService.ts
export async function updateUserProfile(userId: string, data: UpdateProfileInput) {
  const updated = await prisma.user.update({ where: { id: userId }, data });
  await invalidateUserProfile(userId);
  return updated;
}

写通模式

// src/services/inventoryCacheService.ts
import { prisma } from '../prisma/client';
import { redis } from '../redis/client';
import { logger } from '../logger';

// Inventory: consistency‑critical → Write‑Through
export async function updateInventory(productId: string, quantity: number): Promise {
  await prisma.$transaction(async (tx) => {
    await tx.inventory.update({ where: { productId }, data: { quantity } });
  });

  // After DB success, update cache too (Write‑Through)
  await redis.set(
    `inventory:${productId}`,
    JSON.stringify({ quantity, updatedAt: new Date() }),
    { EX: 5 * 60 } // 5 minutes TTL
  );

  logger.debug({ productId, quantity }, 'Inventory cache updated (write‑through)');
}

分布式缓存失效(发布/订阅)

// src/cache/pubsub.ts
import { redisPublisher as publisher, redisSubscriber as subscriber } from '../redis/client';
import { logger } from '../logger';

// Publish cache invalidation to other servers
export async function publishCacheInvalidation(channel: string, key: string): Promise {
  await publisher.publish(channel, JSON.stringify({ key, timestamp: Date.now() }));
}

// Subscribe and act on invalidation messages
export async function subscribeCacheInvalidation(): Promise {
  await subscriber.subscribe('cache:invalidate', async (message) => {
    const { key } = JSON.parse(message);
    await redis.del(key);
    logger.info({ key }, 'Cache invalidated via pub/sub');
  });
}

摘要

  • CLAUDE.md 对所有缓存强制 TTL,使用标准键格式和模式选择标准。
  • Cache‑Aside 减少读取负载;写入时使缓存失效以保持数据新鲜。
  • Write‑Through 在数据库写入时原子地更新缓存,确保强一致性。
  • Pub/Sub 在分布式服务器之间传播缓存失效。

如需更深入的代码审查(包括 TTL 空窗、缓存雪崩风险和一致性检查),请参阅 Code Review Packprompt-works.jp

Claude 代码工程师,专注于性能和缓存。

0 浏览
Back to Blog

相关文章

阅读更多 »