Redis 缓存策略与分布式锁实战 2026 | 高并发架构核心组件

Redis 是高并发架构中不可或缺的组件。本文将系统讲解 Redis 缓存策略、缓存三大经典问题的解决方案、分布式锁的实现原理及生产环境最佳实践。
一、Redis 缓存模式
1.1 Cache-Aside(旁路缓存)
最常用的缓存模式,应用程序先查缓存,未命中再查数据库:
// Cache-Aside 模式实现
class CacheAsideService {
constructor(
private redis: RedisClient,
private db: Database
) {}
async getUser(id: string): Promise<User | null> {
const cacheKey = `user:${id}`
// 1. 先查缓存
const cached = await this.redis.get(cacheKey)
if (cached) {
return JSON.parse(cached)
}
// 2. 缓存未命中,查数据库
const user = await this.db.user.findById(id)
if (!user) {
return null
}
// 3. 写入缓存(设置过期时间)
await this.redis.setex(cacheKey, 3600, JSON.stringify(user))
return user
}
async updateUser(id: string, data: Partial<User>): Promise<void> {
// 1. 更新数据库
await this.db.user.update(id, data)
// 2. 删除缓存(而非更新缓存)
await this.redis.del(`user:${id}`)
}
}为什么删除缓存而不是更新缓存?
- 避免并发写入导致数据不一致
- 部分场景更新缓存计算成本高
- 懒加载思想,下次读取时再缓存
1.2 Write-Through(直写模式)
写入时同时更新缓存和数据库,由缓存组件协调:
class WriteThroughService {
constructor(
private redis: RedisClient,
private db: Database
) {}
async writeUser(user: User): Promise<void> {
// 同时写入缓存和数据库
const pipeline = this.redis.pipeline()
pipeline.set(`user:${user.id}`, JSON.stringify(user), 'EX', 3600)
pipeline.exec()
await this.db.user.upsert(user)
}
async readUser(id: string): Promise<User | null> {
// 缓存中一定有数据(因为写入时已同步)
const cached = await this.redis.get(`user:${id}`)
return cached ? JSON.parse(cached) : null
}
}1.3 Write-Behind(异步写模式)
先写缓存,异步批量写入数据库,性能最高但有一致性风险:
class WriteBehindService {
private writeQueue: Map<string, any> = new Map()
private flushTimer: NodeJS.Timeout
constructor(
private redis: RedisClient,
private db: Database
) {
// 每 5 秒批量刷入数据库
this.flushTimer = setInterval(() => this.flush(), 5000)
}
async writeUser(user: User): Promise<void> {
// 1. 立即写入缓存
await this.redis.set(`user:${user.id}`, JSON.stringify(user), 'EX', 3600)
// 2. 加入写入队列
this.writeQueue.set(user.id, user)
}
private async flush(): Promise<void> {
if (this.writeQueue.size === 0) return
const batch = Array.from(this.writeQueue.values())
this.writeQueue.clear()
try {
// 批量写入数据库
await this.db.user.batchUpsert(batch)
console.log(`Flushed ${batch.length} records to database`)
} catch (err) {
// 写入失败,重新加入队列
batch.forEach(item => this.writeQueue.set(item.id, item))
console.error('Flush failed:', err)
}
}
}1.4 缓存模式对比
| 模式 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| Cache-Aside | 最终一致 | 高 | 低 | 通用场景 |
| Write-Through | 强一致 | 中 | 中 | 一致性要求高 |
| Write-Behind | 弱一致 | 最高 | 高 | 日志、计数器 |
二、缓存三大问题
2.1 缓存穿透
问题:查询不存在的数据,缓存和数据库都没有,每次请求都打到数据库。
用户 → 缓存(未命中) → 数据库(未命中) → 返回 null
↓
恶意攻击:大量请求不存在的 ID → 数据库压力骤增解决方案 1:缓存空值
async getUser(id: string): Promise<User | null> {
const cacheKey = `user:${id}`
const cached = await this.redis.get(cacheKey)
if (cached !== null) {
if (cached === 'NULL') return null // 空值标记
return JSON.parse(cached)
}
const user = await this.db.user.findById(id)
if (!user) {
// 缓存空值,设置较短的过期时间(60秒)
await this.redis.setex(cacheKey, 60, 'NULL')
return null
}
await this.redis.setex(cacheKey, 3600, JSON.stringify(user))
return user
}解决方案 2:布隆过滤器
import BloomFilter from 'bloom-filter'
class BloomGuard {
private filter: BloomFilter
constructor(size: number = 1000000) {
this.filter = BloomFilter.create(size, 0.01) // 1% 误判率
}
// 初始化:加载所有存在的 ID
async init(db: Database) {
const ids = await db.user.getAllIds()
ids.forEach(id => this.filter.add(id))
}
mightExist(id: string): boolean {
return this.filter.contains(id)
}
}
// 使用
async getUser(id: string): Promise<User | null> {
// 布隆过滤器前置检查
if (!this.bloom.mightExist(id)) {
return null // 一定不存在
}
// 正常查缓存 → 数据库流程
return this.cacheAside.getUser(id)
}2.2 缓存击穿
问题:热点数据过期瞬间,大量并发请求同时打到数据库。
热点 key 过期 → 1000 个并发请求 → 同时查数据库 → 数据库崩溃解决方案 1:互斥锁
async getHotData(key: string): Promise<any> {
const cached = await this.redis.get(key)
if (cached) return JSON.parse(cached)
// 获取互斥锁
const lockKey = `lock:${key}`
const lockAcquired = await this.redis.set(
lockKey, '1', 'NX', 'EX', 10 // 10秒自动过期
)
if (lockAcquired) {
try {
// 再次检查缓存(可能已被其他请求填充)
const cached = await this.redis.get(key)
if (cached) return JSON.parse(cached)
// 查询数据库
const data = await this.db.query(key)
await this.redis.setex(key, 3600, JSON.stringify(data))
return data
} finally {
await this.redis.del(lockKey)
}
} else {
// 等待 50ms 后重试
await sleep(50)
return this.getHotData(key)
}
}解决方案 2:逻辑过期
interface CachedData<T> {
data: T
expire: number // 逻辑过期时间
}
async getWithLogicalExpiry(key: string): Promise<any> {
const cached = await this.redis.get(key)
if (!cached) return null
const parsed: CachedData<any> = JSON.parse(cached)
// 未逻辑过期,直接返回
if (Date.now() < parsed.expire) {
return parsed.data
}
// 逻辑过期,尝试异步刷新
const lockKey = `lock:${key}`
const lockAcquired = await this.redis.set(lockKey, '1', 'NX', 'EX', 10)
if (lockAcquired) {
// 异步刷新缓存
setImmediate(async () => {
try {
const data = await this.db.query(key)
const newData: CachedData<any> = {
data,
expire: Date.now() + 3600 * 1000
}
await this.redis.set(key, JSON.stringify(newData))
} finally {
await this.redis.del(lockKey)
}
})
}
// 返回旧数据(不阻塞用户)
return parsed.data
}2.3 缓存雪崩
问题:大量缓存同时过期,或 Redis 宕机,所有请求打到数据库。
解决方案 1:随机过期时间
async cacheData(key: string, data: any, ttl: number): Promise<void> {
// 基础 TTL + 随机偏移(0~300秒)
const randomTTL = ttl + Math.floor(Math.random() * 300)
await this.redis.setex(key, randomTTL, JSON.stringify(data))
}
// 批量缓存时设置不同过期时间
async cacheUsers(users: User[]): Promise<void> {
const pipeline = this.redis.pipeline()
users.forEach((user, index) => {
// 每个用户的过期时间略有不同
const ttl = 3600 + Math.floor(Math.random() * 600)
pipeline.setex(`user:${user.id}`, ttl, JSON.stringify(user))
})
await pipeline.exec()
}解决方案 2:多级缓存
class MultiLevelCache {
// L1: 本地内存缓存(最快)
private localCache = new Map<string, { data: any; expire: number }>()
// L2: Redis 缓存
constructor(private redis: RedisClient) {}
async get(key: string): Promise<any> {
// L1: 检查本地缓存
const local = this.localCache.get(key)
if (local && local.expire > Date.now()) {
return local.data
}
// L2: 检查 Redis
const cached = await this.redis.get(key)
if (cached) {
const data = JSON.parse(cached)
// 回填 L1(本地缓存 60 秒)
this.localCache.set(key, { data, expire: Date.now() + 60000 })
return data
}
return null
}
async set(key: string, data: any, ttl: number): Promise<void> {
// 写入 L1
this.localCache.set(key, { data, expire: Date.now() + 60000 })
// 写入 L2
await this.redis.setex(key, ttl, JSON.stringify(data))
}
}三、分布式锁
3.1 基础实现
class RedisLock {
constructor(private redis: RedisClient) {}
async acquire(key: string, ttl: number = 10): Promise<string | null> {
// 生成唯一标识(防止误解锁)
const lockId = crypto.randomUUID()
// SET key value NX EX ttl
const acquired = await this.redis.set(
`lock:${key}`, lockId, 'NX', 'EX', ttl
)
return acquired ? lockId : null
}
async release(key: string, lockId: string): Promise<boolean> {
// 使用 Lua 脚本保证原子性
const script = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`
const result = await this.redis.eval(
script, 1, `lock:${key}`, lockId
)
return result === 1
}
}3.2 带自动续期的分布式锁
class AutoRenewLock {
private renewTimers: Map<string, NodeJS.Timeout> = new Map()
constructor(private redis: RedisClient) {}
async acquire(
key: string,
ttl: number = 30,
timeout: number = 5000
): Promise<string | null> {
const lockId = crypto.randomUUID()
const startTime = Date.now()
// 尝试获取锁,带超时
while (Date.now() - startTime < timeout) {
const acquired = await this.redis.set(
`lock:${key}`, lockId, 'NX', 'EX', ttl
)
if (acquired) {
// 启动自动续期
this.startRenewal(key, lockId, ttl)
return lockId
}
// 等待 100ms 后重试
await sleep(100)
}
return null
}
private startRenewal(key: string, lockId: string, ttl: number): void {
// 每 ttl/3 秒续期一次
const interval = Math.floor(ttl * 1000 / 3)
const timer = setInterval(async () => {
const script = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("expire", KEYS[1], ARGV[2])
else
return 0
end
`
const result = await this.redis.eval(
script, 1, `lock:${key}`, lockId, String(ttl)
)
if (result === 0) {
// 锁已丢失,停止续期
this.stopRenewal(key)
}
}, interval)
this.renewTimers.set(key, timer)
}
private stopRenewal(key: string): void {
const timer = this.renewTimers.get(key)
if (timer) {
clearInterval(timer)
this.renewTimers.delete(key)
}
}
async release(key: string, lockId: string): Promise<void> {
this.stopRenewal(key)
const script = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`
await this.redis.eval(script, 1, `lock:${key}`, lockId)
}
}3.3 使用示例
// 库存扣减场景
async deductStock(productId: string, quantity: number): Promise<boolean> {
const lock = new AutoRenewLock(this.redis)
const lockKey = `stock:${productId}`
const lockId = await lock.acquire(lockKey, 30, 5000)
if (!lockId) {
throw new Error('获取锁超时,请重试')
}
try {
// 查询库存
const stock = await this.db.product.getStock(productId)
if (stock < quantity) {
return false
}
// 扣减库存
await this.db.product.updateStock(productId, stock - quantity)
return true
} finally {
await lock.release(lockKey, lockId)
}
}3.4 Redlock 算法
单点 Redis 锁在主从切换时可能丢失锁。Redlock 通过多个独立 Redis 节点提高可靠性:
class Redlock {
private servers: RedisClient[]
constructor(servers: RedisClient[]) {
this.servers = servers
}
async acquire(key: string, ttl: number = 10): Promise<string | null> {
const lockId = crypto.randomUUID()
const quorum = Math.floor(this.servers.length / 2) + 1
const startTime = Date.now()
// 向所有节点请求加锁
const results = await Promise.allSettled(
this.servers.map(server =>
server.set(`lock:${key}`, lockId, 'NX', 'EX', ttl)
)
)
// 统计成功数量
const acquired = results.filter(
r => r.status === 'fulfilled' && r.value
).length
// 计算耗时
const elapsed = (Date.now() - startTime) / 1000
if (acquired >= quorum && elapsed < ttl) {
return lockId
}
// 加锁失败,释放所有已获取的锁
await this.release(key, lockId)
return null
}
async release(key: string, lockId: string): Promise<void> {
const script = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`
await Promise.allSettled(
this.servers.map(server =>
server.eval(script, 1, `lock:${key}`, lockId)
)
)
}
}四、Redis 数据结构选型
4.1 常用数据结构对比
| 结构 | 场景 | 时间复杂度 | 示例 |
|---|---|---|---|
| String | 缓存、计数器 | O(1) | SET key value |
| Hash | 对象存储 | O(1) | HSET user:1 name "Alice" |
| List | 消息队列 | O(1)~O(N) | LPUSH queue msg |
| Set | 去重、标签 | O(1) | SADD tags "vue" |
| ZSet | 排行榜 | O(logN) | ZADD rank 100 "user1" |
| Stream | 消息流 | O(1) | XADD stream * key val |
| Bitmap | 签到 | O(1) | SETBIT sign:uid 100 1 |
| HyperLogLog | UV 统计 | O(1) | PFADD uv user1 |
4.2 排行榜实现
class LeaderboardService {
constructor(private redis: RedisClient) {}
// 添加分数
async addScore(gameId: string, userId: string, score: number): Promise<void> {
await this.redis.zadd(`leaderboard:${gameId}`, score, userId)
}
// 获取 Top N
async getTopN(gameId: string, n: number = 10): Promise<Array<{ userId: string; score: number }>> {
const results = await this.redis.zrevrange(
`leaderboard:${gameId}`, 0, n - 1, 'WITHSCORES'
)
const leaderboard: Array<{ userId: string; score: number }> = []
for (let i = 0; i < results.length; i += 2) {
leaderboard.push({
userId: results[i],
score: parseFloat(results[i + 1])
})
}
return leaderboard
}
// 获取用户排名
async getUserRank(gameId: string, userId: string): Promise<{ rank: number; score: number } | null> {
const rank = await this.redis.zrevrank(`leaderboard:${gameId}`, userId)
const score = await this.redis.zscore(`leaderboard:${gameId}`, userId)
if (rank === null || score === null) return null
return { rank: rank + 1, score: parseFloat(score) }
}
}4.3 限流器实现
class RateLimiter {
constructor(private redis: RedisClient) {}
// 滑动窗口限流
async isAllowed(
userId: string,
action: string,
limit: number,
windowSec: number
): Promise<boolean> {
const key = `rate:${action}:${userId}`
const now = Date.now()
const windowStart = now - windowSec * 1000
const pipeline = this.redis.pipeline()
// 移除窗口外的记录
pipeline.zremrangebyscore(key, 0, windowStart)
// 添加当前请求
pipeline.zadd(key, now, `${now}`)
// 统计窗口内请求数
pipeline.zcard(key)
// 设置过期时间
pipeline.expire(key, windowSec)
const results = await pipeline.exec()
const count = results![2][1] as number
return count <= limit
}
}
// 使用:每分钟最多 60 次请求
const allowed = await rateLimiter.isAllowed(userId, 'api_call', 60, 60)
if (!allowed) {
throw new Error('请求过于频繁')
}五、最佳实践
5.1 Key 命名规范
业务:对象:ID:字段
# 示例
user:1001:profile
order:2024:1001
cache:api:/users:page1
lock:stock:product-1001
rate:login:user-10015.2 Pipeline 批量操作
// 不好:多次往返
for (const id of userIds) {
await redis.get(`user:${id}`)
}
// 好:Pipeline 批量
const pipeline = redis.pipeline()
userIds.forEach(id => pipeline.get(`user:${id}`))
const results = await pipeline.exec()5.3 连接池配置
import Redis from 'ioredis'
const redis = new Redis({
host: '127.0.0.1',
port: 6379,
maxRetriesPerRequest: 3,
enableReadyCheck: true,
retryStrategy: (times) => Math.min(times * 100, 3000),
// 连接池
family: 4,
keepAlive: true,
connectionTimeout: 5000,
commandTimeout: 3000,
})
// 集群模式
const cluster = new Redis.Cluster(
[
{ host: '10.0.0.1', port: 6379 },
{ host: '10.0.0.2', port: 6379 },
{ host: '10.0.0.3', port: 6379 },
],
{
scaleReads: 'slave',
maxRedirections: 16,
retryDelayOnFailover: 200,
redisOptions: { password: process.env.REDIS_PASSWORD }
}
)六、总结
- ✅ 三种缓存模式(Cache-Aside、Write-Through、Write-Behind)
- ✅ 缓存穿透(空值缓存、布隆过滤器)
- ✅ 缓存击穿(互斥锁、逻辑过期)
- ✅ 缓存雪崩(随机 TTL、多级缓存)
- ✅ 分布式锁(基础锁、自动续期锁、Redlock 算法)
- ✅ 数据结构选型与实战(排行榜、限流器)
- ✅ 最佳实践(Key 命名、Pipeline、连接池)
Redis 是高并发系统的核心组件,掌握缓存策略和分布式锁是构建高可用架构的关键能力。
相关阅读: