阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 缓存策略的全面应用

缓存策略的全面应用

作者:陈川 阅读数:52364人阅读 分类: Node.js

缓存策略的基本概念

缓存策略是提升应用性能的关键手段之一,通过减少重复计算和网络请求来优化响应速度。在Koa2中,缓存可以应用于多个层面,包括内存缓存、HTTP缓存和数据库查询缓存。不同的场景需要不同的缓存策略,合理选择能显著提升系统吞吐量。

内存缓存是最直接的实现方式,适合存储频繁访问且变化不大的数据。例如使用lru-cache这类LRU算法实现的缓存库:

const LRU = require('lru-cache')
const cache = new LRU({
  max: 500,
  maxAge: 1000 * 60 * 5 // 5分钟
})

// 中间件示例
app.use(async (ctx, next) => {
  const key = ctx.url
  const cached = cache.get(key)
  if (cached) {
    ctx.body = cached
    return
  }
  await next()
  cache.set(key, ctx.body)
})

HTTP缓存头设置

HTTP协议提供了完善的缓存控制机制,通过响应头可以精确控制客户端和代理服务器的缓存行为。Koa2中可以通过中间件灵活设置这些头信息:

app.use(async (ctx, next) => {
  await next()
  if (ctx.status === 200 && ctx.method === 'GET') {
    ctx.set('Cache-Control', 'public, max-age=3600')
    ctx.set('ETag', generateETag(ctx.body))
  }
})

强缓存与协商缓存的组合使用能有效减少网络传输。当资源未变更时,304 Not Modified响应可以节省大量带宽:

app.use(async (ctx, next) => {
  const ifNoneMatch = ctx.headers['if-none-match']
  const etag = generateETagForRequest(ctx)
  if (ifNoneMatch === etag) {
    ctx.status = 304
    return
  }
  await next()
  ctx.set('ETag', etag)
})

数据库查询缓存

ORM层缓存可以避免重复执行相同查询。Sequelize等ORM库提供了查询缓存功能:

const result = await Model.findAll({
  where: { status: 'active' },
  cache: true,
  cacheKey: 'active_users'
})

对于复杂查询,可以手动实现缓存逻辑:

app.use(async (ctx, next) => {
  const cacheKey = `query_${ctx.path}_${JSON.stringify(ctx.query)}`
  const cached = await redis.get(cacheKey)
  if (cached) {
    ctx.body = JSON.parse(cached)
    return
  }
  
  await next()
  
  if (ctx.status === 200) {
    await redis.setex(cacheKey, 300, JSON.stringify(ctx.body))
  }
})

页面级缓存策略

全页面缓存适用于内容变化不频繁的页面。Koa2中间件可以实现简单的HTML缓存:

const pageCache = {}
app.use(async (ctx, next) => {
  if (ctx.method !== 'GET' || !ctx.accepts('html')) {
    return next()
  }
  
  const url = ctx.url
  if (pageCache[url] && Date.now() - pageCache[url].timestamp < 60000) {
    ctx.type = 'text/html'
    ctx.body = pageCache[url].html
    return
  }
  
  await next()
  
  if (ctx.status === 200 && ctx.type === 'text/html') {
    pageCache[url] = {
      html: ctx.body,
      timestamp: Date.now()
    }
  }
})

对于动态内容,可以考虑片段缓存(ESI)或组件级缓存方案。

缓存失效与更新策略

缓存失效是缓存系统中最具挑战性的部分。常见的策略包括:

  1. 定时过期:设置固定的缓存时间
  2. 主动清除:数据变更时立即清除相关缓存
  3. 版本控制:通过URL或参数版本号强制更新

Koa2中实现主动清除的例子:

// 数据更新操作后清除缓存
router.post('/articles/:id', async (ctx) => {
  await updateArticle(ctx.params.id, ctx.request.body)
  await redis.del(`article_${ctx.params.id}`)
  await redis.del('article_list')
  ctx.status = 204
})

对于依赖关系复杂的场景,可以使用发布-订阅模式通知缓存失效:

const redis = require('redis')
const sub = redis.createClient()
const pub = redis.createClient()

sub.on('message', (channel, key) => {
  cache.del(key)
})

// 数据变更时发布消息
pub.publish('cache_invalidate', `user_${userId}`)

分布式缓存实践

在集群环境下,内存缓存需要共享存储。Redis是常见的分布式缓存解决方案:

const redis = require('redis')
const client = redis.createClient()

app.use(async (ctx, next) => {
  const key = `view_${ctx.path}`
  const cached = await client.get(key)
  if (cached) {
    ctx.body = JSON.parse(cached)
    return
  }
  
  await next()
  
  if (ctx.status === 200) {
    await client.setex(key, 60, JSON.stringify(ctx.body))
  }
})

缓存雪崩防护可以通过随机过期时间实现:

function getCacheTTL() {
  const base = 3600 // 1小时
  const random = Math.floor(Math.random() * 600) // 0-10分钟随机
  return base + random
}

缓存性能监控

完善的监控能帮助优化缓存策略。可以收集以下指标:

  1. 缓存命中率
  2. 缓存加载时间
  3. 内存使用情况

Koa2中间件实现简单的监控:

const cacheStats = {
  hits: 0,
  misses: 0
}

app.use(async (ctx, next) => {
  const start = Date.now()
  const key = ctx.url
  const cached = cache.get(key)
  
  if (cached) {
    cacheStats.hits++
    ctx.body = cached
    ctx.set('X-Cache', 'HIT')
    return
  }
  
  cacheStats.misses++
  await next()
  
  if (ctx.status === 200) {
    cache.set(key, ctx.body)
  }
  
  ctx.set('X-Cache-Time', `${Date.now() - start}ms`)
})

// 暴露统计端点
router.get('/cache-stats', (ctx) => {
  ctx.body = cacheStats
})

高级缓存模式

对于特殊场景,可以考虑更高级的缓存模式:

  1. 写穿透:先更新缓存再更新数据库
  2. 读穿透:缓存未命中时从数据库加载
  3. 缓存预热:启动时加载热点数据

Koa2实现读穿透的例子:

app.use(async (ctx, next) => {
  const key = `user_${ctx.params.id}`
  let data = await redis.get(key)
  
  if (!data) {
    data = await db.query('SELECT * FROM users WHERE id = ?', [ctx.params.id])
    if (data) {
      await redis.setex(key, 3600, JSON.stringify(data))
    }
  } else {
    data = JSON.parse(data)
  }
  
  ctx.body = data
})

缓存预热可以在应用启动时执行:

async function warmUpCache() {
  const hotData = await db.query('SELECT * FROM products ORDER BY views DESC LIMIT 100')
  await Promise.all(
    hotData.map(item => 
      redis.setex(`product_${item.id}`, 86400, JSON.stringify(item))
    )
  )
}

app.on('listening', warmUpCache)

缓存与安全考虑

缓存可能引入安全风险,需要注意:

  1. 敏感数据不应缓存
  2. 区分用户私有缓存
  3. 防止缓存投毒攻击

Koa2中处理用户私有缓存:

app.use(async (ctx, next) => {
  const userToken = ctx.cookies.get('token')
  const cacheKey = userToken ? `${userToken}_${ctx.url}` : ctx.url
  
  const cached = cache.get(cacheKey)
  if (cached) {
    ctx.body = cached
    return
  }
  
  await next()
  
  if (ctx.status === 200) {
    cache.set(cacheKey, ctx.body)
  }
})

对于API响应,Vary头可以确保不同内容类型的正确缓存:

ctx.set('Vary', 'Accept-Encoding, Accept-Language')

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌